From edff808abdda0ec858e6971e4e4f7a4e88f17156 Mon Sep 17 00:00:00 2001 From: Ben Newman Date: Fri, 8 May 2020 19:06:40 -0400 Subject: [PATCH] Terminology: arguments instead of variables. When calling cache.evict, we do not have a FieldNode, so we cannot supply variables to be combined with the field's arguments, but must instead provide the fully-resolved arguments object. To make this work, the context argument of KeyArgsFunction no longer provides context.variables, since that information is not always available. The arguments passed as the first argument to the KeyArgsFunction should be sufficient to compute the field identity. --- src/cache/inmemory/__tests__/entityStore.ts | 13 ++++++++ src/cache/inmemory/__tests__/policies.ts | 5 +-- src/cache/inmemory/entityStore.ts | 21 +++++++++---- src/cache/inmemory/inMemoryCache.ts | 8 +++-- src/cache/inmemory/policies.ts | 35 +++++++++++++-------- src/utilities/graphql/storeUtils.ts | 27 ---------------- 6 files changed, 59 insertions(+), 50 deletions(-) diff --git a/src/cache/inmemory/__tests__/entityStore.ts b/src/cache/inmemory/__tests__/entityStore.ts index 02763428ae0..b001c2860f3 100644 --- a/src/cache/inmemory/__tests__/entityStore.ts +++ b/src/cache/inmemory/__tests__/entityStore.ts @@ -1212,6 +1212,19 @@ describe('EntityStore', () => { cache.evict('ROOT_QUERY', 'authorOfBook', {}); + expect(cache.extract()).toEqual({ + ROOT_QUERY: { + __typename: "Query", + "authorOfBook({\"isbn\":\"2\"})": { + __typename: "Author", + name: "Isaac Asimov", + hobby: "chemistry", + }, + }, + }); + + cache.evict('ROOT_QUERY', 'authorOfBook');; + expect(cache.extract()).toEqual({ ROOT_QUERY: { __typename: "Query", diff --git a/src/cache/inmemory/__tests__/policies.ts b/src/cache/inmemory/__tests__/policies.ts index 364a17fc772..77e6649e21c 100644 --- a/src/cache/inmemory/__tests__/policies.ts +++ b/src/cache/inmemory/__tests__/policies.ts @@ -593,15 +593,16 @@ describe("type policies", function () { keyArgs(args, context) { expect(context.typename).toBe("Thread"); expect(context.fieldName).toBe("comments"); - expect(context.field.name.value).toBe("comments"); - expect(context.variables).toEqual({}); + expect(context.field!.name.value).toBe("comments"); expect(context.policies).toBeInstanceOf(Policies); if (typeof args!.limit === "number") { if (typeof args!.offset === "number") { + expect(args).toEqual({ offset: 0, limit: 2 }); return ["offset", "limit"]; } if (args!.beforeId) { + expect(args).toEqual({ beforeId: "asdf", limit: 2 }); return ["beforeId", "limit"]; } } diff --git a/src/cache/inmemory/entityStore.ts b/src/cache/inmemory/entityStore.ts index e30e4ca3626..da8b7959742 100644 --- a/src/cache/inmemory/entityStore.ts +++ b/src/cache/inmemory/entityStore.ts @@ -179,21 +179,30 @@ export abstract class EntityStore implements NormalizedCache { // fieldNameFromStoreName helper function. If called with a fieldName // and variables, removes all fields of that entity whose names match fieldName // and whose arguments when cached exactly match the variables passed. - public delete(dataId: string, fieldName?: string, variables?: Record) { - const storeFieldName = fieldName && variables ? - this.policies.getStoreFieldName(dataId, fieldName, variables) : fieldName; + public delete( + dataId: string, + fieldName?: string, + args?: Record, + ) { + const storeFieldName = fieldName && args + ? this.policies.getStoreFieldName(dataId, fieldName, args) + : fieldName; return this.modify(dataId, storeFieldName ? { [storeFieldName]: delModifier, } : delModifier); } - public evict(dataId: string, fieldName?: string, variables?: Record): boolean { + public evict( + dataId: string, + fieldName?: string, + args?: Record, + ): boolean { let evicted = false; if (hasOwn.call(this.data, dataId)) { - evicted = this.delete(dataId, fieldName, variables); + evicted = this.delete(dataId, fieldName, args); } if (this instanceof Layer) { - evicted = this.parent.evict(dataId, fieldName, variables) || evicted; + evicted = this.parent.evict(dataId, fieldName, args) || evicted; } // Always invalidate the field to trigger rereading of watched // queries, even if no cache data was modified by the eviction, diff --git a/src/cache/inmemory/inMemoryCache.ts b/src/cache/inmemory/inMemoryCache.ts index 51f0da5e18a..34820a92aa8 100644 --- a/src/cache/inmemory/inMemoryCache.ts +++ b/src/cache/inmemory/inMemoryCache.ts @@ -224,8 +224,12 @@ export class InMemoryCache extends ApolloCache { return this.policies.identify(object)[0]; } - public evict(dataId: string, fieldName?: string, variables?: Record): boolean { - const evicted = this.optimisticData.evict(dataId, fieldName, variables); + public evict( + dataId: string, + fieldName?: string, + args?: Record, + ): boolean { + const evicted = this.optimisticData.evict(dataId, fieldName, args); this.broadcastWatches(); return evicted; } diff --git a/src/cache/inmemory/policies.ts b/src/cache/inmemory/policies.ts index 8cff21fadc3..4e763d34657 100644 --- a/src/cache/inmemory/policies.ts +++ b/src/cache/inmemory/policies.ts @@ -13,7 +13,6 @@ import { getFragmentFromSelection, } from '../../utilities/graphql/fragments'; import { - fieldNodeFromName, isField, getTypenameFromResult, storeKeyNameFromField, @@ -22,6 +21,7 @@ import { argumentsObjectFromField, Reference, isReference, + getStoreKeyName, } from '../../utilities/graphql/storeUtils'; import { canUseWeakMap } from '../../utilities/common/canUse'; import { IdGetter } from "./types"; @@ -82,8 +82,7 @@ export type KeyArgsFunction = ( context: { typename: string; fieldName: string; - field: FieldNode; - variables: Record; + field: FieldNode | null; policies: Policies; }, ) => KeySpecifier | ReturnType; @@ -432,17 +431,27 @@ export class Policies { public getStoreFieldName( typename: string | undefined, nameOrField: string | FieldNode, - variables: Record, + // If nameOrField is a string, argsOrVars should be an object of + // arguments. If nameOrField is a FieldNode, argsOrVars should be the + // variables to use when computing the arguments of the field. + argsOrVars: Record, ): string { - const field = typeof nameOrField === 'string' ? fieldNodeFromName(nameOrField, variables) : nameOrField; - const fieldName = field.name.value; + let field: FieldNode | null; + let fieldName: string; + if (typeof nameOrField === "string") { + field = null; + fieldName = nameOrField; + } else { + field = nameOrField; + fieldName = field.name.value; + } const policy = this.getFieldPolicy(typename, fieldName, false); let storeFieldName: string | undefined; let keyFn = policy && policy.keyFn; if (keyFn && typename) { - const args = argumentsObjectFromField(field, variables); - const context = { typename, fieldName, field, variables, policies: this }; + const args = field ? argumentsObjectFromField(field, argsOrVars) : argsOrVars; + const context = { typename, fieldName, field, policies: this }; while (keyFn) { const specifierOrString = keyFn(args, context); if (Array.isArray(specifierOrString)) { @@ -457,7 +466,9 @@ export class Policies { } if (storeFieldName === void 0) { - storeFieldName = storeKeyNameFromField(field, variables); + storeFieldName = field + ? storeKeyNameFromField(field, argsOrVars) + : getStoreKeyName(fieldName, argsOrVars); } // Make sure custom field names start with the actual field.name.value @@ -708,11 +719,9 @@ function keyArgsFnFromSpecifier( specifier: KeySpecifier, ): KeyArgsFunction { return (args, context) => { - const field = context.field; - const fieldName = field.name.value; - return args ? `${fieldName}:${ + return args ? `${context.fieldName}:${ JSON.stringify(computeKeyObject(args, specifier)) - }` : fieldName; + }` : context.fieldName; }; } diff --git a/src/utilities/graphql/storeUtils.ts b/src/utilities/graphql/storeUtils.ts index 56d51c9b2e4..516ff28dde4 100644 --- a/src/utilities/graphql/storeUtils.ts +++ b/src/utilities/graphql/storeUtils.ts @@ -291,30 +291,3 @@ export function isInlineFragment( } export type VariableValue = (node: VariableNode) => any; - -export function fieldNodeFromName(fieldName: string, variables: Record): FieldNode { - return { - kind: 'Field', - name: { - kind: "Name", - value: fieldName, - }, - arguments: Object.keys(variables).reduce((args, key) => ([ - ...args, - { - kind: 'Argument', - name: { - kind: 'Name', - value: key, - }, - value: { - kind: 'Variable', - name: { - kind: 'Name', - value: key, - } - } - } - ]), []) - }; -}