Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ideas for making LiveQueryStore more powerful #14

Open
n1ru4l opened this issue Aug 19, 2020 · 7 comments
Open

Ideas for making LiveQueryStore more powerful #14

n1ru4l opened this issue Aug 19, 2020 · 7 comments
Labels
enhancement New feature or request help wanted Extra attention is needed

Comments

@n1ru4l
Copy link
Owner

n1ru4l commented Aug 19, 2020

The approach of calling liveQueryStore.triggerUpdate("Query.users") might not scale that well. It could return different results for different users. It does not address the unique field argument combinations.

Resource-Based Updates

If a user with id fgahex changes we ideally only want to update queries that select the given user.

Object types that have an id field could be automatically collected during query execution (and updated during re-execution).

Query

query user @live {
  me {
    __typename
    id
    login
  }
}

Execution result:

{
  "data": {
    "__typename": "User",
    "id": "fgahex",
    "login": "testuser"
  }
}

Will result in the following entry for the selected resources of that query: User.fgahex.
An update for all the live queries that select the given user could be scheduled via liveQueryStore.triggerUpdate("User.fgahex").

Things to figure out:

What is the best way of collecting resources?

This would require sth. like a middleware for each resolver that gathers the ids and attaches them to the result (as extensions?) that the liveQueryStore can pick up (maybe this?).

Another solution would be to push this to the user so he has to register it manually, by calling some function that is passed along the context object?

The information could also be extracted from the query response. However, that would have the implications that it needs at least the id extracted for each record (or even id + __typename for GraphQL servers that do not expose unique ids across object types). Since most client-side frameworks encourage using unique ids for identifying Objects anyways that shouldn't be that big of a limitation. An additional drawback would be that the whole result tree must be traversed.

Pros:

  • The server automatically keeps track of the resources a query selects.
  • Does work for resources that got replaced with objects of another id (by calling triggerUpdate for the previous resource).

Cons:

  • Big queries result in a lot of strings that are stored additionally in memory
  • Does only work for object types that have an id property.
  • Does probably not play that well with a list type.
  • Does not solve the field argument issue.
@n1ru4l n1ru4l added the enhancement New feature or request label Aug 22, 2020
@n1ru4l n1ru4l added the help wanted Extra attention is needed label Sep 14, 2020
@n1ru4l
Copy link
Owner Author

n1ru4l commented Sep 16, 2020

Hey @marcus-sa, thanks for your comment and showing interest 😊

Since subscriptions for several pub sub system already exist you could leverage the technical perspective of that.
For example in NestJS you can have programmatically object type defined fields as "ID"s.
If you took leverage of that and mapped in at server startup it would somehow solve it.

Do you mean traversing and gathering the information about the schema?

At client-side you've got GraphQL generator that could easily be modified to detect such custom directives and generate "live" queries.

I would like to not rely on a compiler on client-side to it accessible for more people.

I would like to work at this, since I've been thinking about this approach for some time.

I would love it if you could experiment with gathering resource identifiers or even better come up with a solution that works better than the current InMemoryLiveQueryStore 👍

I also just opened a PR for supporting fragments in the current implementation: #82

Maybe it could be possible to abstract the method for gathering the resource identifiers into an API so different use-cases can use different ways of gathering the resource identifiers? Does that make sense?

@n1ru4l
Copy link
Owner Author

n1ru4l commented Sep 17, 2020

I got too excited #94

@n1ru4l
Copy link
Owner Author

n1ru4l commented Sep 28, 2020

Resource identifier collection is now implemented as part of #94

@n1ru4l
Copy link
Owner Author

n1ru4l commented Sep 29, 2020

@sbatezat
Copy link

sbatezat commented Apr 29, 2022

Thanks for this great toolset!
I've got a suggestion...and also a question.

Suggestion

Following packages:

  • graphql-live-query-patch
  • graphql-live-query-patch-json-patch
  • graphql-live-query-patch-json-diff

Are each including functions targetting both client and server.
Unless there is something I misunderstood, the patch creation needs to be done on server side while it's the client which needs to apply the patch.

It's resulting to adding useless functions/code on the client and the server. My suggestion is to split each of those packages in two: one to "generate patches" and the other one to "apply patches".

For example, I don't need the applyPatch function (because my client is not written in JS - see bellow question) but I'm compelled to import it server side.

Question

I'm trying to port the client part of "graphql-live-query-patch" into Dart - because I've got a Flutter client.
I'm wondering about your implementation: why are you creating a new AsyncIterable through "repeater" instead of mapping the source result with the result of the "applyPatch" function?

EDIT: sorry for the stupid question, I just realized that AsyncIterableIterator is not mappable :/

@n1ru4l
Copy link
Owner Author

n1ru4l commented Apr 29, 2022

It's resulting to adding useless functions/code on the client and the server. My suggestion is to split each of those packages in two: one to "generate patches" and the other one to "apply patches".

This can be solved by doing modular imports, bundling, and dead-code elimination. I don't plan to split the logic into a client and server package.


Repeater.js is used because it is too easy ending up with memory leaks while fiddling with AsyncGenerators/AsyncIterables. There been a few memory leak within this library before, all related to that.

@sbatezat
Copy link

This can be solved by doing modular imports, bundling, and dead-code elimination. I don't plan to split the logic into a client and server package.

As you wish, but I think you miss my point by answering with modular imports/bundling solutions.
Don't you think it would be great to see multiple client implementations? If so, let's say I will make a Pull Request to implement patching on Flutter. It would make sense to duplicate existing interfaces, but it doesn't make any sense to implements patch generation on Flutter as it's a client framework (you always could argue that it's possible to use dart server side, but I think you get the point).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request help wanted Extra attention is needed
Projects
None yet
Development

No branches or pull requests

2 participants