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

feat(fe2): greatly improved DX for apollo cache modification #2831

Merged
merged 18 commits into from
Sep 3, 2024

Conversation

fabis94
Copy link
Contributor

@fabis94 fabis94 commented Aug 30, 2024

(Built this when I had some downtime about a week ago, hope you like it :) )

There's 2 big parts to this:

  1. A fully & automatically type-checked modifyObjectField() function. You no longer need to specify any generic arguments, it will figure out what part of the cache you're updating based on the cache reference and field name you pass into the function.
  2. Improved and simplified helpers for common activities in modifyObjectFIeld(), most importantly introduced createUpdatedValue that updates the cached value at specific paths (e.g. items.[0].name) ONLY if such a value exists in the cache. This makes it easier to ensure we only update cached values, not introduce new ones (and get an invalid state that way - e.g. a totalCount of 0, when it was undefined before)

Demo of all of the TS auto typing you get out of the box in type name, field name, field value, variable value resolution
Code_pwLJH7zfr7

getCacheId() blocks invalid type names:
image

readField() allows reading fields of Reference objects, and its now fully typed - meaning it will understand what kind of reference are you specifying and only support fields that exist on that type
image
image

Reference objects are typed - you can no longer mismatch references, e.g. put a User reference where a Project reference is expected:
image
image

({ variables, details: { DELETE } }) => {
if (variables.id === workspaceId) return DELETE
({ variables, helpers: { evict } }) => {
if (variables.id === workspaceId) return evict()
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Return evict() replaces return DELETE

@@ -0,0 +1,59 @@
const { reduce } = require('lodash')
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Had to introduce a custom GQL codegen plugin to get this to work

* createUpdatedValue() helper to build a new value with updated fields.
*/
value: ReadonlyDeep<ModifyObjectFieldValue<Type, Field>>
helpers: {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Access all of the helpers you need from the helpers object in the modifyObjectField() callback

*
* This function operates on a deeply cloned value that is safe to mutate
*/
createUpdatedValue: (
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Simplifies this:

     modifyObjectField(
                cache,
                workspaceCacheId,
                'projects',
                ({ value, helpers: { ref } }) => {
                  const newItems = isUndefined(value?.items)
                    ? undefined
                    : [ref('Project', newProject.id), ...value.items]

                  const newTotalCount = isUndefined(value?.totalCount)
                    ? undefined
                    : (value.totalCount || 0) + 1

                  return {
                    ...value,
                    ...(isUndefined(newItems) ? {} : { items: newItems }),
                    ...(isUndefined(newTotalCount) ? {} : { totalCount: newTotalCount })
                  }
                }
              )

Into this:

modifyObjectField(
                cache,
                workspaceCacheId,
                'projects',
                ({ helpers: { ref, createUpdatedValue } }) => {
                  return createUpdatedValue(({ update }) => {
                    update('items', (items) => [
                      ref('Project', newProject.id),
                      ...items
                    ])
                    update('totalCount', (count) => count + 1)
                  })
                }
              )

update() calls are properly typed as well, and will only allow you to specify property paths that actually exist in value, and the property value in the callback is gonna be properly typed too
image
image

])
update('totalCount', (count) => count + 1)
}),
{ autoEvictFiltered: true }
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Option that automatically evicts all fields that have variables w/ filters in them. You usually want to do this when adding a new item to a list - it may be hard to tell if the item fits the filtered query, and it'll just be easier to evict that version of the query and refetch it

cc @cdriesler

Copy link
Contributor

@Mikehrn Mikehrn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice 😄 Thanks for the visuals and clear explanations as well!

@fabis94 fabis94 merged commit 596ccf8 into main Sep 3, 2024
22 of 24 checks passed
@fabis94 fabis94 deleted the fabians/fe2-better-cache-utilities branch September 3, 2024 07:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants