Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
4070d60
Add scalar option to FieldPolicy
jerelmiller Jun 4, 2026
a31d83b
Use 2026 as year
jerelmiller Jun 4, 2026
3c1c848
Add scalar to internal type
jerelmiller Jun 4, 2026
daa6efe
Set scalar on field policy
jerelmiller Jun 4, 2026
be56aff
Add failing tests for custom scalars
jerelmiller Jun 4, 2026
a56e229
Add scalar coercion to policies
jerelmiller Jun 4, 2026
44703bc
Better handle parsing scalars from nested arrays
jerelmiller Jun 4, 2026
383f3d7
Don't try and coerce null
jerelmiller Jun 4, 2026
08bb78b
Handle object-based scalar values
jerelmiller Jun 4, 2026
e41cb41
Add test for primitive to primitive transform
jerelmiller Jun 4, 2026
390d3a8
Add test for scalar in object array
jerelmiller Jun 4, 2026
33688db
Add test to ensure multiple scalars work
jerelmiller Jun 4, 2026
9cce6f4
Add test for named/inline fragment
jerelmiller Jun 4, 2026
bbad0b6
Add tests for union type
jerelmiller Jun 4, 2026
48a4e1f
Add test for interface type
jerelmiller Jun 4, 2026
5b6c2f6
Add kitchen sink test
jerelmiller Jun 4, 2026
f8e62f1
Add changeset
jerelmiller Jun 4, 2026
e052437
Add dev-only warning for scalars configured on non-scalar fields
jerelmiller Jun 4, 2026
be6aaca
Remove outdated comment
jerelmiller Jun 4, 2026
8c5053b
Add test for cache.watchFragment
jerelmiller Jun 4, 2026
1a7c14c
Move KnownScalars type to types file
jerelmiller Jun 4, 2026
1d70656
Provide better autocomplete for scalars that extends Record<string, ...>
jerelmiller Jun 4, 2026
d524093
Update type tests with scalar config
jerelmiller Jun 4, 2026
1fa24f2
Remove unused import
jerelmiller Jun 4, 2026
a5719a4
Add test for scalar field but no implementation
jerelmiller Jun 4, 2026
0f5158b
Update api report
jerelmiller Jun 4, 2026
7cc03c8
Add test to ensure addTypePolicies deep merges scalar field
jerelmiller Jun 4, 2026
c25065e
Add test for parsing scalar value from alias
jerelmiller Jun 5, 2026
653a5f7
Add tests for parsing scalar values from fields with arguments
jerelmiller Jun 5, 2026
99498a2
Simplify options for maybeCoerceScalarValue
jerelmiller Jun 5, 2026
9a45136
Rename to maybeCoerceToScalarValue
jerelmiller Jun 5, 2026
1ed51e1
Rerun api report
jerelmiller Jun 5, 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
23 changes: 20 additions & 3 deletions .api-reports/api-report-cache.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,14 @@ export { canonicalStringify }
// @public (undocumented)
type CanReadFunction = (value: StoreValue) => boolean;

// @public (undocumented)
interface CoerceValueOptions {
// (undocumented)
field: FieldNode;
// (undocumented)
typename: string;
}

// @public (undocumented)
export function createFragmentRegistry(...fragments: DocumentNode[]): FragmentRegistryAPI;

Expand Down Expand Up @@ -495,6 +503,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 @@ -872,6 +881,10 @@ export class Policies {
hasKeyArgs(typename: string | undefined, fieldName: string): boolean;
// (undocumented)
identify(object: StoreObject, partialContext?: Partial<KeyFieldsContext>): [string?, StoreObject?];
// Warning: (ae-forgotten-export) The symbol "CoerceValueOptions" needs to be exported by the entry point index.d.ts
//
// (undocumented)
maybeCoerceToScalarValue(value: StoreValue, options: CoerceValueOptions): any;
// (undocumented)
readField<V = StoreValue>(options: ReadFieldOptions, context: ReadMergeModifyContext): SafeReadonly<V> | undefined;
// (undocumented)
Expand Down Expand Up @@ -996,6 +1009,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 +1094,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:175:3 - (ae-forgotten-export) The symbol "KeySpecifier" needs to be exported by the entry point index.d.ts
// src/cache/inmemory/policies.ts:175:3 - (ae-forgotten-export) The symbol "KeyArgsFunction" needs to be exported by the entry point index.d.ts
// src/cache/inmemory/policies.ts:178: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
27 changes: 22 additions & 5 deletions .api-reports/api-report.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -946,6 +946,14 @@ class ClientAwarenessLink extends ApolloLink {
constructor(options?: ClientAwarenessLink.Options);
}

// @public (undocumented)
interface CoerceValueOptions {
// (undocumented)
field: FieldNode;
// (undocumented)
typename: string;
}

// @public (undocumented)
export namespace CombinedGraphQLErrors {
// (undocumented)
Expand Down Expand Up @@ -1327,6 +1335,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 @@ -2371,6 +2380,10 @@ class Policies {
hasKeyArgs(typename: string | undefined, fieldName: string): boolean;
// (undocumented)
identify(object: StoreObject, partialContext?: Partial<KeyFieldsContext>): [string?, StoreObject?];
// Warning: (ae-forgotten-export) The symbol "CoerceValueOptions" needs to be exported by the entry point index.d.ts
//
// (undocumented)
maybeCoerceToScalarValue(value: StoreValue, options: CoerceValueOptions): any;
// Warning: (ae-forgotten-export) The symbol "ReadFieldOptions" needs to be exported by the entry point index.d.ts
//
// (undocumented)
Expand Down Expand Up @@ -2823,6 +2836,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 +3155,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:103:3 - (ae-forgotten-export) The symbol "FragmentMap" needs to be exported by the entry point index.d.ts
// src/cache/inmemory/policies.ts:175:3 - (ae-forgotten-export) The symbol "KeySpecifier" needs to be exported by the entry point index.d.ts
// src/cache/inmemory/policies.ts:175:3 - (ae-forgotten-export) The symbol "KeyArgsFunction" needs to be exported by the entry point index.d.ts
// src/cache/inmemory/policies.ts:178: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
28 changes: 28 additions & 0 deletions .changeset/strong-shoes-sell.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
---
"@apollo/client": minor
---

Adds a `scalar` option to `InMemoryCache` field policies that dictates whether to parse the raw value using the custom scalar definition. Scalar parsing is now performed on cache reads.

```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",
},
},
},
},
});
```
Original file line number Diff line number Diff line change
Expand Up @@ -209,3 +209,26 @@ 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",
},
},
},
},
});
});
17 changes: 17 additions & 0 deletions integration-tests/type-tests/customScalars/all-any/empty/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,3 +152,20 @@ 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",
},
},
},
},
});
});
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,27 @@ 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",
},
},
},
},
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -150,3 +150,20 @@ 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",
},
},
},
},
});
});
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,26 @@ 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",
},
},
},
},
});
});
Loading
Loading