Skip to content

Commit

Permalink
Allow keyFields and keyArgs functions to return false (#7900)
Browse files Browse the repository at this point in the history
Previously these functions were not allowed to return false even though
false is supported.

Co-authored-by: Ben Newman <ben@apollographql.com>
  • Loading branch information
CarsonF and benjamn committed Mar 25, 2021
1 parent bc14e87 commit a896f34
Show file tree
Hide file tree
Showing 4 changed files with 99 additions and 8 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
## Apollo Client 3.3.14 (not yet released)

### Improvements

- Adjust TypeScript types to allow `keyFields` and `keyArgs` functions to return `false`. <br/>
[@CarsonF](https://github.com/CarsonF) and [@benjamn](https://github.com/benjamn) in [#7900](https://github.com/apollographql/apollo-client/pull/7900)

## Apollo Client 3.3.13

### Improvements
Expand Down
75 changes: 75 additions & 0 deletions src/cache/inmemory/__tests__/policies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4465,6 +4465,81 @@ describe("type policies", function () {
});
});

it("allows keyFields and keyArgs functions to return false", function () {
const cache = new InMemoryCache({
typePolicies: {
Person: {
keyFields() {
return false;
},
fields: {
height: {
keyArgs() {
return false;
},
merge(_, height, { args }) {
if (args) {
if (args.units === "feet") {
return height;
}
if (args.units === "meters") {
// Convert to feet so we don't have to remember the units.
return height * 3.28084;
}
}
throw new Error("unexpected units: " + args);
},
},
},
},
},
});

const query = gql`
query GetUser ($units: string) {
people {
id
height(units: $units)
}
}
`;

cache.writeQuery({
query,
variables: {
units: "meters",
},
data: {
people: [{
__typename: "Person",
id: 12345,
height: 1.75,
}, {
__typename: "Person",
id: 23456,
height: 2,
}],
},
});

expect(cache.extract()).toEqual({
ROOT_QUERY: {
__typename: "Query",
// An array of non-normalized objects, not Reference objects.
people: [{
__typename: "Person",
// No serialized units argument, just "height".
height: 5.74147,
id: 12345,
}, {
__typename: "Person",
height: 6.56168,
id: 23456,
}],
},
});
});

it("can read from foreign references using read helper", function () {
const cache = new InMemoryCache({
typePolicies: {
Expand Down
21 changes: 15 additions & 6 deletions src/cache/inmemory/policies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,9 @@ type KeyFieldsContext = {
export type KeyFieldsFunction = (
object: Readonly<StoreObject>,
context: KeyFieldsContext,
) => KeySpecifier | ReturnType<IdGetter>;
) => KeySpecifier | false | ReturnType<IdGetter>;

type KeyFieldsResult = Exclude<ReturnType<KeyFieldsFunction>, KeySpecifier>;

// TODO Should TypePolicy be a generic type, with a TObject or TEntity
// type parameter?
Expand Down Expand Up @@ -103,7 +105,9 @@ export type KeyArgsFunction = (
field: FieldNode | null;
variables?: Record<string, any>;
},
) => KeySpecifier | ReturnType<IdGetter>;
) => KeySpecifier | false | ReturnType<IdGetter>;

type KeyArgsResult = Exclude<ReturnType<KeyArgsFunction>, KeySpecifier>;

export type FieldPolicy<
// The internal representation used to store the field's data in the
Expand Down Expand Up @@ -337,7 +341,7 @@ export class Policies {
fragmentMap,
};

let id: string | undefined;
let id: KeyFieldsResult;

const policy = typename && this.getTypePolicy(typename);
let keyFn = policy && policy.keyFn || this.config.dataIdFromObject;
Expand All @@ -351,8 +355,7 @@ export class Policies {
}
}

id = id && String(id);

id = id ? String(id) : void 0;
return context.keyObject ? [id, context.keyObject] : [id];
}

Expand Down Expand Up @@ -674,7 +677,7 @@ export class Policies {
public getStoreFieldName(fieldSpec: FieldSpecifier): string {
const { typename, fieldName } = fieldSpec;
const policy = this.getFieldPolicy(typename, fieldName, false);
let storeFieldName: string | undefined;
let storeFieldName: KeyArgsResult;

let keyFn = policy && policy.keyFn;
if (keyFn && typename) {
Expand Down Expand Up @@ -704,6 +707,12 @@ export class Policies {
: getStoreKeyName(fieldName, argsFromFieldSpecifier(fieldSpec));
}

// Returning false from a keyArgs function is like configuring
// keyArgs: false, but more dynamic.
if (storeFieldName === false) {
return fieldName;
}

// Make sure custom field names start with the actual field.name.value
// of the field, so we can always figure out which properties of a
// StoreObject correspond to which original field names.
Expand Down
4 changes: 2 additions & 2 deletions src/cache/inmemory/writeToStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ export class StoreWriter {

variables = {
...getDefaultValues(operationDefinition),
...variables,
...variables!,
};

const ref = this.processSelectionSet({
Expand Down Expand Up @@ -178,7 +178,7 @@ export class StoreWriter {
// If typename was not passed in, infer it. Note that typename is
// always passed in for tricky-to-infer cases such as "Query" for
// ROOT_QUERY.
const typename =
const typename: string | undefined =
(dataId && policies.rootTypenamesById[dataId]) ||
getTypenameFromResult(result, selectionSet, context.fragmentMap) ||
(dataId && context.store.get(dataId, "__typename") as string);
Expand Down

0 comments on commit a896f34

Please sign in to comment.