n1ru4l / graphql-live-query

Realtime GraphQL Live Queries with JavaScript
MIT License
436 stars 36 forks source link

Ideas for making LiveQueryStore more powerful #14

Open n1ru4l opened 4 years ago

n1ru4l commented 4 years ago

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:

Cons:

n1ru4l commented 4 years ago

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: https://github.com/n1ru4l/graphql-live-queries/pull/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 commented 4 years ago

I got too excited https://github.com/n1ru4l/graphql-live-queries/pull/94

n1ru4l commented 3 years ago

Resource identifier collection is now implemented as part of https://github.com/n1ru4l/graphql-live-queries/pull/94

n1ru4l commented 3 years ago

I wrote an article about the functionality https://dev.to/n1ru4l/collecting-graphql-live-query-resource-identifier-with-graphql-tools-5fm5

sbatezat commented 2 years ago

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

Suggestion

Following packages:

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 commented 2 years ago

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 commented 2 years ago

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).