Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
84 commits
Select commit Hold shift + click to select a range
1d8b11d
Add scalar option to FieldPolicy
jerelmiller Jun 4, 2026
ba2f61b
Use 2026 as year
jerelmiller Jun 4, 2026
58898ee
Add scalar to internal type
jerelmiller Jun 4, 2026
22fb795
Set scalar on field policy
jerelmiller Jun 4, 2026
8afe099
Add failing tests for custom scalars
jerelmiller Jun 4, 2026
ce9c806
Add scalar coercion to policies
jerelmiller Jun 4, 2026
57726f9
Better handle parsing scalars from nested arrays
jerelmiller Jun 4, 2026
46bbdf0
Don't try and coerce null
jerelmiller Jun 4, 2026
2192a3f
Handle object-based scalar values
jerelmiller Jun 4, 2026
45522d5
Add test for primitive to primitive transform
jerelmiller Jun 4, 2026
2a435d2
Add test for scalar in object array
jerelmiller Jun 4, 2026
abede49
Add test to ensure multiple scalars work
jerelmiller Jun 4, 2026
9f77b9e
Add test for named/inline fragment
jerelmiller Jun 4, 2026
44bcc67
Add tests for union type
jerelmiller Jun 4, 2026
7465d90
Add test for interface type
jerelmiller Jun 4, 2026
92fa896
Add kitchen sink test
jerelmiller Jun 4, 2026
0a20a22
Add changeset
jerelmiller Jun 4, 2026
4ce0ebf
Add dev-only warning for scalars configured on non-scalar fields
jerelmiller Jun 4, 2026
d9ec3d9
Remove outdated comment
jerelmiller Jun 4, 2026
584ded7
Add test for cache.watchFragment
jerelmiller Jun 4, 2026
6320ddf
Move KnownScalars type to types file
jerelmiller Jun 4, 2026
b323aeb
Provide better autocomplete for scalars that extends Record<string, ...>
jerelmiller Jun 4, 2026
eb8f3b8
Update type tests with scalar config
jerelmiller Jun 4, 2026
9df3afd
Remove unused import
jerelmiller Jun 4, 2026
2fa16e0
Add test for scalar field but no implementation
jerelmiller Jun 4, 2026
79bb57b
Update api report
jerelmiller Jun 4, 2026
a671aca
Add test to ensure addTypePolicies deep merges scalar field
jerelmiller Jun 4, 2026
fe7ae37
Add tests for validating value remains unchanged
jerelmiller Jun 4, 2026
5748c88
Handle coercing array values
jerelmiller Jun 4, 2026
380e1d2
Add test for validating 2d array
jerelmiller Jun 4, 2026
cb53dde
Validate null is handled correctly
jerelmiller Jun 4, 2026
98012fc
Add test for object scalar
jerelmiller Jun 4, 2026
c12cc3e
Add test for cache.writeFragment
jerelmiller Jun 4, 2026
e301e28
Add more test cases for complex use cases
jerelmiller Jun 4, 2026
f8c303f
Coerce to serialized value on returned value from merge function
jerelmiller Jun 4, 2026
dd85b35
Add test to validate parsed value returned from merge is serialized
jerelmiller Jun 4, 2026
f12adaf
Add changeset
jerelmiller Jun 4, 2026
7d8688c
Handle arrays and nested arrays when merge function is present
jerelmiller Jun 4, 2026
e22f434
Fix type error
jerelmiller Jun 4, 2026
cb19fe8
Rerun api report
jerelmiller Jun 4, 2026
b1d3af2
Add test for parsing scalar value from alias
jerelmiller Jun 5, 2026
13e8ed5
Add tests for parsing scalar values from fields with arguments
jerelmiller Jun 5, 2026
a3d1547
Simplify options for maybeCoerceScalarValue
jerelmiller Jun 5, 2026
9e8c6e7
Rename to maybeCoerceToScalarValue
jerelmiller Jun 5, 2026
d133202
Rerun api report
jerelmiller Jun 5, 2026
b7faf7c
Add basic serialization for writeQuery
jerelmiller Jun 4, 2026
b54b327
Add tests for cache writes with aliases and variables
jerelmiller Jun 5, 2026
c31e7c1
Simplify args to coerceToSerializedValue
jerelmiller Jun 5, 2026
7745373
Refactor a getScalarForField and use in coerce methods
jerelmiller Jun 5, 2026
6535f3a
Rerun api report
jerelmiller Jun 5, 2026
568a038
Allow fieldName or field option in coerce methods
jerelmiller Jun 9, 2026
68b3fa3
Handle scalar serialization for cache.modify
jerelmiller Jun 9, 2026
795e215
Rename method
jerelmiller Jun 9, 2026
e6f40f2
Check raw cache data value
jerelmiller Jun 9, 2026
df13796
Store the parsed value in the data store instead of the serialized value
jerelmiller Jun 9, 2026
ed62532
Serialize values returned from extract()
jerelmiller Jun 9, 2026
0df80be
Fix issue when DELETE is returned from cache.modify
jerelmiller Jun 9, 2026
4dfad66
Add more test cases for cache.extract
jerelmiller Jun 9, 2026
ffb8a0a
Fix issue with non-normalized array
jerelmiller Jun 9, 2026
d1ec85d
Move logic to entityStore
jerelmiller Jun 9, 2026
788b6f8
Fix serialization when root typename is missing
jerelmiller Jun 9, 2026
93cac03
Handle root typename missing
jerelmiller Jun 9, 2026
3871819
Add additional test cases for custom root typename
jerelmiller Jun 9, 2026
7b4e038
Preserve existing scalar with addTypePolicies
jerelmiller Jun 9, 2026
38d9fc2
Reorganize method signature
jerelmiller Jun 9, 2026
96f5b19
Fix issue with scalar coersion on arrays
jerelmiller Jun 9, 2026
1925be5
Refactor maybeCoerce methods into similar impl
jerelmiller Jun 9, 2026
807796e
Handle parsing with implicit root typename from cache.modify
jerelmiller Jun 9, 2026
3b3ce39
Remove unused imports
jerelmiller Jun 9, 2026
a9c8404
Update api report
jerelmiller Jun 9, 2026
42800b9
Add test to check referential equality
jerelmiller Jun 9, 2026
1a194f9
Make serializeStoreObject more generic
jerelmiller Jun 9, 2026
892329e
Update cache.restore to parse scalar values before storing them
jerelmiller Jun 9, 2026
29c876f
Add test to ensure referential equality is maintained
jerelmiller Jun 9, 2026
8093ca3
Let merge handle coercion
jerelmiller Jun 9, 2026
6bdd540
Maintain referential equality when no field changes
jerelmiller Jun 9, 2026
3628a90
Revert coercion in readFromStore
jerelmiller Jun 9, 2026
346d50f
Refine changesets
jerelmiller Jun 9, 2026
f073d14
Add additional type in type check
jerelmiller Jun 9, 2026
99f627a
Rename arg
jerelmiller Jun 9, 2026
c1860ab
Remove double coercion when writing data to the cache
jerelmiller Jun 10, 2026
8144496
Fix calling coerceToValue more than once accidentally
jerelmiller Jun 10, 2026
889ab23
Remove unused maybeCoerce* methods in Policies
jerelmiller Jun 10, 2026
260a649
Swap args
jerelmiller Jun 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions .api-reports/api-report-cache.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -495,6 +495,7 @@ export type FieldPolicy<TExisting = any, TIncoming = TExisting, TReadResult = TI
keyArgs?: KeySpecifier | KeyArgsFunction | false;
read?: FieldReadFunction<TExisting, TReadResult, TReadOptions>;
merge?: FieldMergeFunction<TExisting, TIncoming, TMergeOptions> | boolean;
scalar?: ScalarNames;
};

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

// @public (undocumented)
type ScalarNames = keyof KnownScalars | (string extends keyof ApolloCache.Scalars ? string & {} : never);

// @public (undocumented)
type StorageType = Record<string, any>;

Expand Down Expand Up @@ -1078,9 +1084,10 @@ interface WriteContext extends ReadMergeModifyContext {

// Warnings were encountered during analysis:
//
// src/cache/inmemory/policies.ts:173:3 - (ae-forgotten-export) The symbol "KeySpecifier" needs to be exported by the entry point index.d.ts
// src/cache/inmemory/policies.ts:173:3 - (ae-forgotten-export) The symbol "KeyArgsFunction" needs to be exported by the entry point index.d.ts
// src/cache/inmemory/types.ts:136:3 - (ae-forgotten-export) The symbol "KeyFieldsFunction" needs to be exported by the entry point index.d.ts
// src/cache/inmemory/policies.ts:176:3 - (ae-forgotten-export) The symbol "KeySpecifier" needs to be exported by the entry point index.d.ts
// src/cache/inmemory/policies.ts:176:3 - (ae-forgotten-export) The symbol "KeyArgsFunction" needs to be exported by the entry point index.d.ts
// src/cache/inmemory/policies.ts:179:3 - (ae-forgotten-export) The symbol "ScalarNames" needs to be exported by the entry point index.d.ts
// src/cache/inmemory/types.ts:139:3 - (ae-forgotten-export) The symbol "KeyFieldsFunction" needs to be exported by the entry point index.d.ts

// (No @packageDocumentation comment for this package)

Expand Down
17 changes: 12 additions & 5 deletions .api-reports/api-report.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -1327,6 +1327,7 @@ export type FieldPolicy<TExisting = any, TIncoming = TExisting, TReadResult = TI
keyArgs?: KeySpecifier | KeyArgsFunction | false;
read?: FieldReadFunction<TExisting, TReadResult, TReadOptions>;
merge?: FieldMergeFunction<TExisting, TIncoming, TMergeOptions> | boolean;
scalar?: ScalarNames;
};

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

// @public (undocumented)
type ScalarNames = keyof KnownScalars | (string extends keyof ApolloCache.Scalars ? string & {} : never);

// Warning: (ae-forgotten-export) The symbol "HttpConfig" needs to be exported by the entry point index.d.ts
//
// @public (undocumented)
Expand Down Expand Up @@ -3139,11 +3145,12 @@ interface WriteContext extends ReadMergeModifyContext {
// Warnings were encountered during analysis:
//
// src/cache/core/cache.ts:127:11 - (ae-forgotten-export) The symbol "MissingTree" needs to be exported by the entry point index.d.ts
// src/cache/inmemory/policies.ts:101:3 - (ae-forgotten-export) The symbol "FragmentMap" needs to be exported by the entry point index.d.ts
// src/cache/inmemory/policies.ts:173:3 - (ae-forgotten-export) The symbol "KeySpecifier" needs to be exported by the entry point index.d.ts
// src/cache/inmemory/policies.ts:173:3 - (ae-forgotten-export) The symbol "KeyArgsFunction" needs to be exported by the entry point index.d.ts
// src/cache/inmemory/types.ts:136:3 - (ae-forgotten-export) The symbol "KeyFieldsFunction" needs to be exported by the entry point index.d.ts
// src/cache/inmemory/types.ts:143:3 - (ae-forgotten-export) The symbol "FragmentRegistryAPI" needs to be exported by the entry point index.d.ts
// src/cache/inmemory/policies.ts:104:3 - (ae-forgotten-export) The symbol "FragmentMap" needs to be exported by the entry point index.d.ts
// src/cache/inmemory/policies.ts:176:3 - (ae-forgotten-export) The symbol "KeySpecifier" needs to be exported by the entry point index.d.ts
// src/cache/inmemory/policies.ts:176:3 - (ae-forgotten-export) The symbol "KeyArgsFunction" needs to be exported by the entry point index.d.ts
// src/cache/inmemory/policies.ts:179:3 - (ae-forgotten-export) The symbol "ScalarNames" needs to be exported by the entry point index.d.ts
// src/cache/inmemory/types.ts:139:3 - (ae-forgotten-export) The symbol "KeyFieldsFunction" needs to be exported by the entry point index.d.ts
// src/cache/inmemory/types.ts:146:3 - (ae-forgotten-export) The symbol "FragmentRegistryAPI" needs to be exported by the entry point index.d.ts
// src/core/ApolloClient.ts:201:5 - (ae-forgotten-export) The symbol "IgnoreModifier" needs to be exported by the entry point index.d.ts
// src/core/ApolloClient.ts:633:5 - (ae-forgotten-export) The symbol "NextFetchPolicyContext" needs to be exported by the entry point index.d.ts
// src/core/ObservableQuery.ts:375:5 - (ae-forgotten-export) The symbol "QueryManager" needs to be exported by the entry point index.d.ts
Expand Down
30 changes: 30 additions & 0 deletions .changeset/strong-shoes-sell.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
---
"@apollo/client": minor
---

Adds a `scalar` option to `InMemoryCache` field policies that tells the cache which scalar to use when parsing or serializing the field value.

```ts
import { Scalar } from "@apollo/client";

new InMemoryCache({
scalars: {
DateTime: new Scalar({
parse: (dateString) => new Date(dateString),
serialize: (date) => date.toISOString(),
}),
},
typePolicies: {
Event: {
fields: {
startTime: {
// Parse this field using the DateTime scalar
scalar: "DateTime",
},
},
},
},
});
```

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()`.
Original file line number Diff line number Diff line change
Expand Up @@ -209,3 +209,29 @@ test("InMemoryCache.getScalar returns the resolved scalar for a declared scalar"
Scalar<any, any> | undefined
>();
});

test("allows any scalar name in field policies", () => {
new InMemoryCache({
scalars: {
DateTime: new Scalar({
serialize: (value) => value.toISOString(),
parse: (value) => new Date(value),
}),
},
typePolicies: {
Event: {
fields: {
startDate: {
scalar: "DateTime",
},
metadata: {
scalar: "JSONObject",
},
unknown: {
scalar: "Unknown",
},
},
},
},
});
});
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ApolloCache, InMemoryCache, Scalar } from "@apollo/client/cache";
import { InMemoryCache, Scalar } from "@apollo/client/cache";
import { expectTypeOf } from "expect-type";

declare function test(name: string, fn: () => void): void;
Expand Down Expand Up @@ -152,3 +152,23 @@ test("getScalar returns the resolved scalar or undefined", () => {
Scalar<any, any> | undefined
>();
});

test("allows any scalar name in field policies", () => {
new InMemoryCache({
typePolicies: {
Event: {
fields: {
startDate: {
scalar: "DateTime",
},
metadata: {
scalar: "JSONObject",
},
unknown: {
scalar: "Unknown",
},
},
},
},
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -228,3 +228,23 @@ test("getScalar returns the resolved scalar or undefined", () => {
Scalar<any, any> | undefined
>();
});

test("allows any scalar name in field policies", () => {
new InMemoryCache({
typePolicies: {
Event: {
fields: {
startDate: {
scalar: "RelativeDate",
},
metadata: {
scalar: "JSONObject",
},
unknown: {
scalar: "Unknown",
},
},
},
},
});
});
26 changes: 26 additions & 0 deletions integration-tests/type-tests/customScalars/all-any/mixed/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,3 +128,29 @@ test("getScalar resolves each scalar according to its declaration", () => {
Scalar<any, any> | undefined
>();
});

test("allows any scalar name in field policies", () => {
new InMemoryCache({
scalars: {
DateTime: new Scalar({
serialize: (value) => value.toISOString(),
parse: (value) => new Date(value),
}),
},
typePolicies: {
Event: {
fields: {
startDate: {
scalar: "DateTime",
},
endDate: {
scalar: "RelativeDate",
},
metadata: {
scalar: "Unknown",
},
},
},
},
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,30 @@ test("a transforming scalar that conflicts with the index cannot be configured",
},
});
});

test("allows any scalar name in field policies", () => {
new InMemoryCache({
// @ts-expect-error `DateTime` is not assignable to index signature
scalars: {
DateTime: new Scalar({
serialize: (value: Date) => value.toISOString(),
parse: (value: string) => new Date(value),
}),
},
typePolicies: {
Event: {
fields: {
startDate: {
scalar: "DateTime",
},
metadata: {
scalar: "JSONObject",
},
unknown: {
scalar: "Unknown",
},
},
},
},
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -150,3 +150,23 @@ test("getScalar returns the resolved scalar or undefined", () => {
Scalar<string, string> | undefined
>();
});

test("allows any scalar name in field policies", () => {
new InMemoryCache({
typePolicies: {
Event: {
fields: {
startDate: {
scalar: "DateTime",
},
metadata: {
scalar: "JSONObject",
},
unknown: {
scalar: "Unknown",
},
},
},
},
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -113,3 +113,23 @@ test("getScalar resolves each scalar according to its declaration", () => {
Scalar<string, string> | undefined
>();
});

test("allows any scalar name in field policies", () => {
new InMemoryCache({
typePolicies: {
Event: {
fields: {
startDate: {
scalar: "RelativeDate",
},
metadata: {
scalar: "JSONObject",
},
unknown: {
scalar: "Unknown",
},
},
},
},
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,34 @@ test("a transforming scalar conflicting with the index blocks configuration", ()
},
});
});

test("allows any scalar name in field policies", () => {
new InMemoryCache({
// @ts-expect-error `DateTime` is not assignable to index signature
scalars: {
DateTime: new Scalar({
serialize: (value) => value.toISOString(),
parse: (value) => new Date(value),
}),
RelativeDate: new Scalar({
serialize: (value) => value,
parse: (value) => value,
}),
},
typePolicies: {
Event: {
fields: {
startDate: {
scalar: "DateTime",
},
endDate: {
scalar: "RelativeDate",
},
metadata: {
scalar: "Unknown",
},
},
},
},
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -210,3 +210,29 @@ test("InMemoryCache.getScalar returns the resolved scalar for a declared scalar"
Scalar<unknown, unknown> | undefined
>();
});

test("allows any scalar name in field policies", () => {
new InMemoryCache({
scalars: {
DateTime: new Scalar({
serialize: (value) => value.toISOString(),
parse: (value) => new Date(value),
}),
},
typePolicies: {
Event: {
fields: {
startDate: {
scalar: "DateTime",
},
metadata: {
scalar: "JSONObject",
},
unknown: {
scalar: "Unknown",
},
},
},
},
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -156,3 +156,23 @@ test("getScalar returns the resolved scalar or undefined", () => {
Scalar<unknown, unknown> | undefined
>();
});

test("allows any scalar name in field policies", () => {
new InMemoryCache({
typePolicies: {
Event: {
fields: {
startDate: {
scalar: "DateTime",
},
metadata: {
scalar: "JSONObject",
},
unknown: {
scalar: "Unknown",
},
},
},
},
});
});
Loading
Loading