Skip to content

Commit ccaf686

Browse files
authored
[Custom scalars] Handle scalar parsing during cache reads and writes (#13259)
_Part of the custom scalars work: https://github.com/apollographql/apollo-client/issues/13227_ Adds scalar value serialization for scalar fields during cache writes. This allows users to provide either the parsed or serialized value to the cache and have it work as expected.
1 parent ed86234 commit ccaf686

25 files changed

Lines changed: 4430 additions & 28 deletions

File tree

.api-reports/api-report-cache.api.md

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -495,6 +495,7 @@ export type FieldPolicy<TExisting = any, TIncoming = TExisting, TReadResult = TI
495495
keyArgs?: KeySpecifier | KeyArgsFunction | false;
496496
read?: FieldReadFunction<TExisting, TReadResult, TReadOptions>;
497497
merge?: FieldMergeFunction<TExisting, TIncoming, TMergeOptions> | boolean;
498+
scalar?: ScalarNames;
498499
};
499500

500501
// @public (undocumented)
@@ -864,6 +865,8 @@ export class Policies {
864865
getMergeFunction(parentTypename: string | undefined, fieldName: string, childTypename: string | undefined): FieldMergeFunction | undefined;
865866
// (undocumented)
866867
getReadFunction(typename: string | undefined, fieldName: string): FieldReadFunction | undefined;
868+
// (undocumented)
869+
getScalarForField(typename: string, fieldName: string): Scalar<any, any> | undefined;
867870
// Warning: (ae-forgotten-export) The symbol "FieldSpecifier" needs to be exported by the entry point index.d.ts
868871
//
869872
// (undocumented)
@@ -996,6 +999,9 @@ export class Scalar<TSerialized, TParsed> {
996999
serialize(value: TParsed): TSerialized;
9971000
}
9981001

1002+
// @public (undocumented)
1003+
type ScalarNames = keyof KnownScalars | (string extends keyof ApolloCache.Scalars ? string & {} : never);
1004+
9991005
// @public (undocumented)
10001006
type StorageType = Record<string, any>;
10011007

@@ -1078,9 +1084,10 @@ interface WriteContext extends ReadMergeModifyContext {
10781084

10791085
// Warnings were encountered during analysis:
10801086
//
1081-
// src/cache/inmemory/policies.ts:173:3 - (ae-forgotten-export) The symbol "KeySpecifier" needs to be exported by the entry point index.d.ts
1082-
// src/cache/inmemory/policies.ts:173:3 - (ae-forgotten-export) The symbol "KeyArgsFunction" needs to be exported by the entry point index.d.ts
1083-
// src/cache/inmemory/types.ts:136:3 - (ae-forgotten-export) The symbol "KeyFieldsFunction" needs to be exported by the entry point index.d.ts
1087+
// src/cache/inmemory/policies.ts:176:3 - (ae-forgotten-export) The symbol "KeySpecifier" needs to be exported by the entry point index.d.ts
1088+
// src/cache/inmemory/policies.ts:176:3 - (ae-forgotten-export) The symbol "KeyArgsFunction" needs to be exported by the entry point index.d.ts
1089+
// src/cache/inmemory/policies.ts:179:3 - (ae-forgotten-export) The symbol "ScalarNames" needs to be exported by the entry point index.d.ts
1090+
// src/cache/inmemory/types.ts:139:3 - (ae-forgotten-export) The symbol "KeyFieldsFunction" needs to be exported by the entry point index.d.ts
10841091

10851092
// (No @packageDocumentation comment for this package)
10861093

.api-reports/api-report.api.md

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1327,6 +1327,7 @@ export type FieldPolicy<TExisting = any, TIncoming = TExisting, TReadResult = TI
13271327
keyArgs?: KeySpecifier | KeyArgsFunction | false;
13281328
read?: FieldReadFunction<TExisting, TReadResult, TReadOptions>;
13291329
merge?: FieldMergeFunction<TExisting, TIncoming, TMergeOptions> | boolean;
1330+
scalar?: ScalarNames;
13301331
};
13311332

13321333
// @public (undocumented)
@@ -2363,6 +2364,8 @@ class Policies {
23632364
getMergeFunction(parentTypename: string | undefined, fieldName: string, childTypename: string | undefined): FieldMergeFunction | undefined;
23642365
// (undocumented)
23652366
getReadFunction(typename: string | undefined, fieldName: string): FieldReadFunction | undefined;
2367+
// (undocumented)
2368+
getScalarForField(typename: string, fieldName: string): Scalar<any, any> | undefined;
23662369
// Warning: (ae-forgotten-export) The symbol "FieldSpecifier" needs to be exported by the entry point index.d.ts
23672370
//
23682371
// (undocumented)
@@ -2823,6 +2826,9 @@ export class Scalar<TSerialized, TParsed> {
28232826
serialize(value: TParsed): TSerialized;
28242827
}
28252828

2829+
// @public (undocumented)
2830+
type ScalarNames = keyof KnownScalars | (string extends keyof ApolloCache.Scalars ? string & {} : never);
2831+
28262832
// Warning: (ae-forgotten-export) The symbol "HttpConfig" needs to be exported by the entry point index.d.ts
28272833
//
28282834
// @public (undocumented)
@@ -3139,11 +3145,12 @@ interface WriteContext extends ReadMergeModifyContext {
31393145
// Warnings were encountered during analysis:
31403146
//
31413147
// src/cache/core/cache.ts:127:11 - (ae-forgotten-export) The symbol "MissingTree" needs to be exported by the entry point index.d.ts
3142-
// src/cache/inmemory/policies.ts:101:3 - (ae-forgotten-export) The symbol "FragmentMap" needs to be exported by the entry point index.d.ts
3143-
// src/cache/inmemory/policies.ts:173:3 - (ae-forgotten-export) The symbol "KeySpecifier" needs to be exported by the entry point index.d.ts
3144-
// src/cache/inmemory/policies.ts:173:3 - (ae-forgotten-export) The symbol "KeyArgsFunction" needs to be exported by the entry point index.d.ts
3145-
// src/cache/inmemory/types.ts:136:3 - (ae-forgotten-export) The symbol "KeyFieldsFunction" needs to be exported by the entry point index.d.ts
3146-
// src/cache/inmemory/types.ts:143:3 - (ae-forgotten-export) The symbol "FragmentRegistryAPI" needs to be exported by the entry point index.d.ts
3148+
// src/cache/inmemory/policies.ts:104:3 - (ae-forgotten-export) The symbol "FragmentMap" needs to be exported by the entry point index.d.ts
3149+
// src/cache/inmemory/policies.ts:176:3 - (ae-forgotten-export) The symbol "KeySpecifier" needs to be exported by the entry point index.d.ts
3150+
// src/cache/inmemory/policies.ts:176:3 - (ae-forgotten-export) The symbol "KeyArgsFunction" needs to be exported by the entry point index.d.ts
3151+
// src/cache/inmemory/policies.ts:179:3 - (ae-forgotten-export) The symbol "ScalarNames" needs to be exported by the entry point index.d.ts
3152+
// src/cache/inmemory/types.ts:139:3 - (ae-forgotten-export) The symbol "KeyFieldsFunction" needs to be exported by the entry point index.d.ts
3153+
// src/cache/inmemory/types.ts:146:3 - (ae-forgotten-export) The symbol "FragmentRegistryAPI" needs to be exported by the entry point index.d.ts
31473154
// src/core/ApolloClient.ts:201:5 - (ae-forgotten-export) The symbol "IgnoreModifier" needs to be exported by the entry point index.d.ts
31483155
// src/core/ApolloClient.ts:633:5 - (ae-forgotten-export) The symbol "NextFetchPolicyContext" needs to be exported by the entry point index.d.ts
31493156
// src/core/ObservableQuery.ts:375:5 - (ae-forgotten-export) The symbol "QueryManager" needs to be exported by the entry point index.d.ts

.changeset/strong-shoes-sell.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
---
2+
"@apollo/client": minor
3+
---
4+
5+
Adds a `scalar` option to `InMemoryCache` field policies that tells the cache which scalar to use when parsing or serializing the field value.
6+
7+
```ts
8+
import { Scalar } from "@apollo/client";
9+
10+
new InMemoryCache({
11+
scalars: {
12+
DateTime: new Scalar({
13+
parse: (dateString) => new Date(dateString),
14+
serialize: (date) => date.toISOString(),
15+
}),
16+
},
17+
typePolicies: {
18+
Event: {
19+
fields: {
20+
startTime: {
21+
// Parse this field using the DateTime scalar
22+
scalar: "DateTime",
23+
},
24+
},
25+
},
26+
},
27+
});
28+
```
29+
30+
This scalar definition is now used to properly parse or serialize the field value for cache reads and writes as well as `cache.extract()` and `cache.restore()`.

integration-tests/type-tests/customScalars/all-any/differentTypes/index.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,3 +209,29 @@ test("InMemoryCache.getScalar returns the resolved scalar for a declared scalar"
209209
Scalar<any, any> | undefined
210210
>();
211211
});
212+
213+
test("allows any scalar name in field policies", () => {
214+
new InMemoryCache({
215+
scalars: {
216+
DateTime: new Scalar({
217+
serialize: (value) => value.toISOString(),
218+
parse: (value) => new Date(value),
219+
}),
220+
},
221+
typePolicies: {
222+
Event: {
223+
fields: {
224+
startDate: {
225+
scalar: "DateTime",
226+
},
227+
metadata: {
228+
scalar: "JSONObject",
229+
},
230+
unknown: {
231+
scalar: "Unknown",
232+
},
233+
},
234+
},
235+
},
236+
});
237+
});

integration-tests/type-tests/customScalars/all-any/empty/index.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { ApolloCache, InMemoryCache, Scalar } from "@apollo/client/cache";
1+
import { InMemoryCache, Scalar } from "@apollo/client/cache";
22
import { expectTypeOf } from "expect-type";
33

44
declare function test(name: string, fn: () => void): void;
@@ -152,3 +152,23 @@ test("getScalar returns the resolved scalar or undefined", () => {
152152
Scalar<any, any> | undefined
153153
>();
154154
});
155+
156+
test("allows any scalar name in field policies", () => {
157+
new InMemoryCache({
158+
typePolicies: {
159+
Event: {
160+
fields: {
161+
startDate: {
162+
scalar: "DateTime",
163+
},
164+
metadata: {
165+
scalar: "JSONObject",
166+
},
167+
unknown: {
168+
scalar: "Unknown",
169+
},
170+
},
171+
},
172+
},
173+
});
174+
});

integration-tests/type-tests/customScalars/all-any/matchingTypes/index.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,3 +228,23 @@ test("getScalar returns the resolved scalar or undefined", () => {
228228
Scalar<any, any> | undefined
229229
>();
230230
});
231+
232+
test("allows any scalar name in field policies", () => {
233+
new InMemoryCache({
234+
typePolicies: {
235+
Event: {
236+
fields: {
237+
startDate: {
238+
scalar: "RelativeDate",
239+
},
240+
metadata: {
241+
scalar: "JSONObject",
242+
},
243+
unknown: {
244+
scalar: "Unknown",
245+
},
246+
},
247+
},
248+
},
249+
});
250+
});

integration-tests/type-tests/customScalars/all-any/mixed/index.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,3 +128,29 @@ test("getScalar resolves each scalar according to its declaration", () => {
128128
Scalar<any, any> | undefined
129129
>();
130130
});
131+
132+
test("allows any scalar name in field policies", () => {
133+
new InMemoryCache({
134+
scalars: {
135+
DateTime: new Scalar({
136+
serialize: (value) => value.toISOString(),
137+
parse: (value) => new Date(value),
138+
}),
139+
},
140+
typePolicies: {
141+
Event: {
142+
fields: {
143+
startDate: {
144+
scalar: "DateTime",
145+
},
146+
endDate: {
147+
scalar: "RelativeDate",
148+
},
149+
metadata: {
150+
scalar: "Unknown",
151+
},
152+
},
153+
},
154+
},
155+
});
156+
});

integration-tests/type-tests/customScalars/all-structured/differentTypes/index.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,30 @@ test("a transforming scalar that conflicts with the index cannot be configured",
3232
},
3333
});
3434
});
35+
36+
test("allows any scalar name in field policies", () => {
37+
new InMemoryCache({
38+
// @ts-expect-error `DateTime` is not assignable to index signature
39+
scalars: {
40+
DateTime: new Scalar({
41+
serialize: (value: Date) => value.toISOString(),
42+
parse: (value: string) => new Date(value),
43+
}),
44+
},
45+
typePolicies: {
46+
Event: {
47+
fields: {
48+
startDate: {
49+
scalar: "DateTime",
50+
},
51+
metadata: {
52+
scalar: "JSONObject",
53+
},
54+
unknown: {
55+
scalar: "Unknown",
56+
},
57+
},
58+
},
59+
},
60+
});
61+
});

integration-tests/type-tests/customScalars/all-structured/empty/index.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,3 +150,23 @@ test("getScalar returns the resolved scalar or undefined", () => {
150150
Scalar<string, string> | undefined
151151
>();
152152
});
153+
154+
test("allows any scalar name in field policies", () => {
155+
new InMemoryCache({
156+
typePolicies: {
157+
Event: {
158+
fields: {
159+
startDate: {
160+
scalar: "DateTime",
161+
},
162+
metadata: {
163+
scalar: "JSONObject",
164+
},
165+
unknown: {
166+
scalar: "Unknown",
167+
},
168+
},
169+
},
170+
},
171+
});
172+
});

integration-tests/type-tests/customScalars/all-structured/matchingTypes/index.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,3 +113,23 @@ test("getScalar resolves each scalar according to its declaration", () => {
113113
Scalar<string, string> | undefined
114114
>();
115115
});
116+
117+
test("allows any scalar name in field policies", () => {
118+
new InMemoryCache({
119+
typePolicies: {
120+
Event: {
121+
fields: {
122+
startDate: {
123+
scalar: "RelativeDate",
124+
},
125+
metadata: {
126+
scalar: "JSONObject",
127+
},
128+
unknown: {
129+
scalar: "Unknown",
130+
},
131+
},
132+
},
133+
},
134+
});
135+
});

0 commit comments

Comments
 (0)