Skip to content

Commit

Permalink
Terminology: arguments instead of variables.
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
benjamn committed May 8, 2020
1 parent 45a697a commit edff808
Show file tree
Hide file tree
Showing 6 changed files with 59 additions and 50 deletions.
13 changes: 13 additions & 0 deletions src/cache/inmemory/__tests__/entityStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
5 changes: 3 additions & 2 deletions src/cache/inmemory/__tests__/policies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"];
}
}
Expand Down
21 changes: 15 additions & 6 deletions src/cache/inmemory/entityStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, any>) {
const storeFieldName = fieldName && variables ?
this.policies.getStoreFieldName(dataId, fieldName, variables) : fieldName;
public delete(
dataId: string,
fieldName?: string,
args?: Record<string, any>,
) {
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<string, any>): boolean {
public evict(
dataId: string,
fieldName?: string,
args?: Record<string, any>,
): 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,
Expand Down
8 changes: 6 additions & 2 deletions src/cache/inmemory/inMemoryCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -224,8 +224,12 @@ export class InMemoryCache extends ApolloCache<NormalizedCacheObject> {
return this.policies.identify(object)[0];
}

public evict(dataId: string, fieldName?: string, variables?: Record<string, any>): boolean {
const evicted = this.optimisticData.evict(dataId, fieldName, variables);
public evict(
dataId: string,
fieldName?: string,
args?: Record<string, any>,
): boolean {
const evicted = this.optimisticData.evict(dataId, fieldName, args);
this.broadcastWatches();
return evicted;
}
Expand Down
35 changes: 22 additions & 13 deletions src/cache/inmemory/policies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import {
getFragmentFromSelection,
} from '../../utilities/graphql/fragments';
import {
fieldNodeFromName,
isField,
getTypenameFromResult,
storeKeyNameFromField,
Expand All @@ -22,6 +21,7 @@ import {
argumentsObjectFromField,
Reference,
isReference,
getStoreKeyName,
} from '../../utilities/graphql/storeUtils';
import { canUseWeakMap } from '../../utilities/common/canUse';
import { IdGetter } from "./types";
Expand Down Expand Up @@ -82,8 +82,7 @@ export type KeyArgsFunction = (
context: {
typename: string;
fieldName: string;
field: FieldNode;
variables: Record<string, any>;
field: FieldNode | null;
policies: Policies;
},
) => KeySpecifier | ReturnType<IdGetter>;
Expand Down Expand Up @@ -432,17 +431,27 @@ export class Policies {
public getStoreFieldName(
typename: string | undefined,
nameOrField: string | FieldNode,
variables: Record<string, any>,
// 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, any>,
): 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)) {
Expand All @@ -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
Expand Down Expand Up @@ -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;
};
}

Expand Down
27 changes: 0 additions & 27 deletions src/utilities/graphql/storeUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -291,30 +291,3 @@ export function isInlineFragment(
}

export type VariableValue = (node: VariableNode) => any;

export function fieldNodeFromName(fieldName: string, variables: Record<string, any>): 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,
}
}
}
]), [])
};
}

0 comments on commit edff808

Please sign in to comment.