grafoojs / grafoo

A GraphQL Client and Toolkit

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Persisted queries and better access to cache

adjourn opened this issue · comments

Im really intrigued by this library, mainly because of small size (Apollo adds almost 40Kb gzipped which is 1/3 of my bundle..) and simple core API to build my own thing with (e.g. on React hooks). Great job!

However I have few feature requests, I'd like to hear your opinion before even thinking about switching:

1. What do you think about reworking flush or adding a new API to access cache in app? Word flush seems odd in that context. Few options: method to read data by key or even expose map directly.

Direct access opens up so many possibilities which enables us to build pretty complex behaviors with these primitives, e.g. show partial data while loading when navigating from list view to detail view. I just tested it and 2 identical queries except with few different fields don't return the same item from cache (not sure if partial bug or intended, I never understood Apollo partial either).

2. Option to generate id for query objects and pass it to fetch function, then we're able to only send query id instead of whole query, less bytes to upload and ability to white-list (persisted queries). Preferably a hash based on query string, then we don't end up with same queries and different ids.

Neither of these features would really add any size to final bundle, persisted queries would be almost entirely babel-plugin feature and better cache access would be max few lines of code.

Cheers

Hey @adjourn, thanks for your feedback!

In client you have the read method that takes as parameters the query and variables. That's how you read from the cache. flush is not meant to be used for that matter. If you are using the react or preact integration is to use the client directly, which is exposed in the Consumer render function.

Now, if you have an idea of how this API could be like, I'm all ears. It would be rather easier, really, to grasp what kind of modifications I should have to do on core internals and answer you if it's doable or not.

You are right about the fact that queries with different fields are not the same in the cache. That was a thoughtful decision when I designed how the library would work, because the logic to merge this objects I came about at the time, if added, would exceed my bundle size budget.

For now my suggestion is that you try to gather your queries in your top level components and pass then down via props or context.

But I'll revisit this topic and give you an answer soon.

Persisted queries: I'm all in! And i think it's very doable. I'll do my research here to see what it takes.

Im planning to build my own React integration if I decide to switch (which I probably will, I really like this) because I have very specific demands for that API. But for core API I was thinking something like this:

// example usage
const item = client.readByKey('123'); // idFields: ['id']
const item = client.readByKey('123User'); // idFields: ['id', '__typename']
// example implementation
function readByKey(key) {
  // returns item if exists,
  // otherwise undefined
  return objectsMap[key];
}

It's pretty clear that flush is not meant to be used like that, name pretty much says it all but I mentioned it because it's currently the only way to access items by key. And to be clear, Im not super interested in the feature to merge same objects (if it adds a lot to bundle), if there was a way to get items by key, pretty much all my use cases would be covered. This is where other users' input would be great.

One of my example use case:

  1. Fetch and render list of items ({items: { id, title }})
  2. User clicks on one item, route changes to /item/123
  3. Build the key from url (e.g item is type, 123 is id, key is 123Item)
  4. Use client.readByKey(key) to read partial item from cache
  5. If exists: show partial data and small spinner, doesn't exists: show big spinner
  6. Fetch single item to get rest of the data ({item(id: "123") { id, title, desc, comments }}),
    could even use 2nd query ({item(id: "123") { desc, comments }}) if partial data exists

Other way would be to just return { execute, listen, write, read, flush, reset, objectsMap }; and let us use it to our heart's content but it kind of exposes internals and we probably don't want that.

It looks like a good idea! If you'd like to give it a shot with a PR I can support you, on the contrary I can land this maybe next week. I'll open an issue, just to let it mapped.

Out of topic I'm interested to know about your needs regarding the react integration. What are the demands you have that are unmet by @grafoo/react? Any feedback on that would be really useful.

Any thought on that @mogelbrod?

Lerna and TypeScript are new technologies for me. Would love to pick them up at some point in order to contribute something meatier, maybe you could add this little feature in order to get it merged a lot sooner?

I suggest you to add small contribution guide to readme (what commands to run in what order, etc). If you do that, it would be easier to pick up and I might give it a shot in few days.


Out of topic I'm interested to know about your needs regarding the react integration. What are the demands you have that are unmet by @grafoo/react? Any feedback on that would be really useful.

I've never liked render props because it creates a false hierarchy in code (indentation) and I try to avoid nesting where I can. It also adds a lot of noise to dev tools. Matter of preference I guess. Now that React has hooks, I use them where I can, including data fetching.

Other than that, I really don't have any other demands that generic library could meet: I plan to write data hooks coupled with internal systems like global state and custom behaviors like query hook that returns partial data (using previously mentioned readByKey) if loading === true.

Sounds good, especially the partial cache fetching! A resetByKey() would probably be nice to have along with the readByKey().

Agreed on the contribution guide as well - it took an hour or so to get a working dev environment up for this repository. From what I can remember I did the following (but they're likely suboptimal):

npm i # install deps
lerna bootstrap # initial setup

Whenever I changed something the only workflow that I could get running was:

lerna bootstrap # rebuild
lerna run test # run test (or another script)

I wasn't able to run tests from within a single package as babel and grafoo-bundle wasn't in the runtime path.

@adjourn I'll do it my self then.

About the integrations I'm planning to add hooks support for react and preact and will open a proper issue for that. So feel free to leave any suggestions there.

@mogelbrod I'm not quite sure if I understand your proposal for resetByKey. Could you explain further?

And I'll definitely add a contribution guide, great Idea.

Adding resetByKey() (which clears the cache for a key otherwise accessible by readByKey()) would keep the reset API in parity with the read*() variants (with the addition of readByKey()).

clear() and clearByKey()? might be better naming choices.

It removes an item with a given key from the cache?

I agree that clear is a better term then reset.

clear/delete/remove are all possible options for the cache removal, but reset no longer feels correct when extended into *ByKey() like you wrote. *ByQuery() is a potential future extension which I think should be its own function since they have pretty different behaviors.

👍 for cache expiration, I was thinking of that myself.