Skip to content

Commit 81d546a

Browse files
authored
Custom scalars (#13270)
Feature branch for [custom scalars](#13227). <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added support for custom GraphQL scalars in the cache, including serialization, parsing, and field-level scalar mapping. * Added support for nested input object scalar configuration. * Expanded cache behavior so scalar values are handled consistently across reads, writes, diffs, queries, mutations, subscriptions, fragments, and refetch flows. * **Bug Fixes** * Improved consistency when cached scalar values are restored or extracted. * Updated variable handling so scalar values are serialized before requests are sent. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
2 parents 34018db + 12960d8 commit 81d546a

91 files changed

Lines changed: 18392 additions & 80 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

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

Lines changed: 107 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,18 @@ import type { FragmentType } from '@apollo/client/masking';
2020
import { getApolloCacheMemoryInternals } from '@apollo/client/utilities/internal';
2121
import type { GetDataState } from '@apollo/client';
2222
import { getInMemoryCacheMemoryInternals } from '@apollo/client/utilities/internal';
23+
import type { GraphQLScalarType } from 'graphql';
2324
import type { Incremental } from '@apollo/client/incremental';
2425
import type { InlineFragmentNode } from 'graphql';
2526
import type { IsAny } from '@apollo/client/utilities/internal';
27+
import type { IsLooselyEqual } from '@apollo/client/utilities/internal';
2628
import { isReference } from '@apollo/client/utilities';
2729
import type { NoInfer as NoInfer_2 } from '@apollo/client/utilities/internal';
2830
import { Observable } from 'rxjs';
2931
import type { OperationVariables } from '@apollo/client';
3032
import type { Prettify } from '@apollo/client/utilities/internal';
3133
import { Reference } from '@apollo/client/utilities';
34+
import type { RemoveIndexSignature } from '@apollo/client/utilities/internal';
3235
import type { SelectionSetNode } from 'graphql';
3336
import type { StoreObject } from '@apollo/client/utilities';
3437
import type { StoreValue } from '@apollo/client/utilities';
@@ -46,9 +49,17 @@ type AllFieldsModifier<Entity extends Record<string, any>> = Modifier<Entity[key
4649
export namespace ApolloCache {
4750
export type FromOptionValue<TData> = StoreObject | Reference | FragmentType<NoInfer_2<TData>> | string;
4851
// (undocumented)
52+
export type GetScalarType<TKey extends keyof ApolloCache.Scalars> = ApolloCache.Scalars[TKey] extends ({
53+
serialized: infer TSerialized;
54+
parsed: infer TParsed;
55+
}) ? Scalar<TSerialized, TParsed> : never;
56+
// (undocumented)
4957
export interface ObservableFragment<TData = unknown> extends Observable<ApolloCache.WatchFragmentResult<TData>> {
5058
getCurrentResult: () => ApolloCache.WatchFragmentResult<TData>;
5159
}
60+
// (undocumented)
61+
export interface Scalars {
62+
}
5263
export interface WatchFragmentOptions<TData = unknown, TVariables extends OperationVariables = OperationVariables> {
5364
fragment: DocumentNode | TypedDocumentNode<TData, TVariables>;
5465
fragmentName?: string;
@@ -92,6 +103,8 @@ export abstract class ApolloCache {
92103
// @internal @deprecated
93104
getMemoryInternals?: typeof getApolloCacheMemoryInternals;
94105
// (undocumented)
106+
getScalar<TKey extends keyof ApolloCache.Scalars>(key: TKey): ApolloCache.GetScalarType<TKey> | undefined;
107+
// (undocumented)
95108
identify(object: StoreObject | Reference): string | undefined;
96109
// (undocumented)
97110
lookupFragment(fragmentName: string): FragmentDefinitionNode | null;
@@ -117,6 +130,8 @@ export abstract class ApolloCache {
117130
abstract reset(options?: Cache_2.ResetOptions): Promise<void>;
118131
resolvesClientField?(typename: string, fieldName: string): boolean;
119132
abstract restore(serializedState: unknown): this;
133+
serializeVariables<TVariables extends OperationVariables = OperationVariables>(document: DocumentNode | TypedDocumentNode<any, TVariables>, variables: NoInfer_2<TVariables>): TVariables;
134+
serializeVariables<TVariables extends OperationVariables = OperationVariables>(document: DocumentNode | TypedDocumentNode<any, TVariables>, variables: NoInfer_2<TVariables> | undefined): TVariables | undefined;
120135
// (undocumented)
121136
transformDocument(document: DocumentNode): DocumentNode;
122137
// (undocumented)
@@ -488,6 +503,7 @@ export type FieldPolicy<TExisting = any, TIncoming = TExisting, TReadResult = TI
488503
keyArgs?: KeySpecifier | KeyArgsFunction | false;
489504
read?: FieldReadFunction<TExisting, TReadResult, TReadOptions>;
490505
merge?: FieldMergeFunction<TExisting, TIncoming, TMergeOptions> | boolean;
506+
scalar?: ScalarNames;
491507
};
492508

493509
// @public (undocumented)
@@ -553,9 +569,32 @@ export interface IgnoreModifier {
553569
// @public (undocumented)
554570
const _ignoreModifier: unique symbol;
555571

572+
// @public (undocumented)
573+
export namespace InMemoryCache {
574+
// Warning: (ae-forgotten-export) The symbol "KnownScalars" needs to be exported by the entry point index.d.ts
575+
//
576+
// (undocumented)
577+
export type ScalarsOption = {
578+
[ScalarName in keyof KnownScalars as IsLooselyEqual<KnownScalars[ScalarName]["serialized"], KnownScalars[ScalarName]["parsed"]> extends true ? ScalarName : never]?: KnownScalars[ScalarName] extends ({
579+
serialized: infer TSerialized;
580+
parsed: infer TParsed;
581+
}) ? Scalar<TSerialized, TParsed> : never;
582+
} & {
583+
[ScalarName in keyof KnownScalars as IsLooselyEqual<KnownScalars[ScalarName]["serialized"], KnownScalars[ScalarName]["parsed"]> extends true ? never : ScalarName]: KnownScalars[ScalarName] extends ({
584+
serialized: infer TSerialized;
585+
parsed: infer TParsed;
586+
}) ? Scalar<TSerialized, TParsed> : never;
587+
} & (ApolloCache.Scalars extends (Record<string, {
588+
serialized: infer TSerialized;
589+
parsed: infer TParsed;
590+
}>) ? Record<string, Scalar<TSerialized, TParsed>> : {});
591+
}
592+
556593
// @public (undocumented)
557594
export class InMemoryCache extends ApolloCache {
558-
constructor(config?: InMemoryCacheConfig);
595+
constructor(...args: {} extends InMemoryCache.ScalarsOption ? [
596+
config?: InMemoryCacheConfig
597+
] : [config: InMemoryCacheConfig]);
559598
// (undocumented)
560599
readonly assumeImmutableResults = true;
561600
batch<TUpdateResult>(options: Cache_2.BatchOptions<InMemoryCache, TUpdateResult>): TUpdateResult;
@@ -580,6 +619,8 @@ export class InMemoryCache extends ApolloCache {
580619
// @internal @deprecated
581620
getMemoryInternals?: typeof getInMemoryCacheMemoryInternals;
582621
// (undocumented)
622+
getScalar<TKey extends keyof ApolloCache.Scalars>(key: TKey): ApolloCache.GetScalarType<TKey> extends (Scalar<infer TSerialized, infer TParsed>) ? IsLooselyEqual<TSerialized, TParsed> extends true ? ApolloCache.GetScalarType<TKey> | undefined : ApolloCache.GetScalarType<TKey> : never;
623+
// (undocumented)
583624
identify(object: StoreObject | Reference): string | undefined;
584625
// (undocumented)
585626
lookupFragment(fragmentName: string): FragmentDefinitionNode | null;
@@ -609,6 +650,8 @@ export class InMemoryCache extends ApolloCache {
609650
restore(data: NormalizedCacheObject): this;
610651
// (undocumented)
611652
retain(rootId: string, optimistic?: boolean): number;
653+
serializeVariables<TVariables extends OperationVariables = OperationVariables>(document: DocumentNode | TypedDocumentNode<any, TVariables>, variables: NoInfer_2<TVariables>): TVariables;
654+
serializeVariables<TVariables extends OperationVariables = OperationVariables>(document: DocumentNode | TypedDocumentNode<any, TVariables>, variables: NoInfer_2<TVariables> | undefined): TVariables | undefined;
612655
// (undocumented)
613656
transformDocument(document: DocumentNode): DocumentNode;
614657
// (undocumented)
@@ -618,15 +661,30 @@ export class InMemoryCache extends ApolloCache {
618661
}
619662

620663
// @public (undocumented)
621-
export interface InMemoryCacheConfig extends ApolloReducerConfig {
622-
// (undocumented)
623-
fragments?: FragmentRegistryAPI;
624-
// (undocumented)
664+
export type InMemoryCacheConfig = ApolloReducerConfig & {
665+
resultCaching?: boolean;
625666
possibleTypes?: PossibleTypesMap;
667+
typePolicies?: TypePolicies;
668+
fragments?: FragmentRegistryAPI;
669+
inputObjects?: InputObjectsOption;
670+
} & ({} extends InMemoryCache.ScalarsOption ? InMemoryCache.ScalarsOption extends Record<string, never> ? {
671+
scalars?: Record<string, `Scalar types must be declared in ApolloCache.Scalars before usage. See https://www.apollographql.com/docs/react/data/typescript#declaring-scalar-types.`>;
672+
} : {
673+
scalars?: InMemoryCache.ScalarsOption;
674+
} : {
675+
scalars: InMemoryCache.ScalarsOption;
676+
});
677+
678+
// @public (undocumented)
679+
export interface InputObjectConfig {
626680
// (undocumented)
627-
resultCaching?: boolean;
681+
fields: Record<string, string>;
682+
}
683+
684+
// @public (undocumented)
685+
export interface InputObjectsOption {
628686
// (undocumented)
629-
typePolicies?: TypePolicies;
687+
[inputObjectName: string]: InputObjectConfig;
630688
}
631689

632690
// @public (undocumented)
@@ -664,6 +722,9 @@ type KeyFieldsFunction = (object: Readonly<StoreObject>, context: KeyFieldsConte
664722
// @public (undocumented)
665723
type KeySpecifier = ReadonlyArray<string | KeySpecifier>;
666724

725+
// @public (undocumented)
726+
type KnownScalars = RemoveIndexSignature<ApolloCache.Scalars>;
727+
667728
// @public (undocumented)
668729
class Layer extends EntityStore {
669730
constructor(id: string, parent: EntityStore, replay: (layer: EntityStore) => any, group: CacheGroup);
@@ -827,6 +888,8 @@ export class Policies {
827888
getMergeFunction(parentTypename: string | undefined, fieldName: string, childTypename: string | undefined): FieldMergeFunction | undefined;
828889
// (undocumented)
829890
getReadFunction(typename: string | undefined, fieldName: string): FieldReadFunction | undefined;
891+
// (undocumented)
892+
getScalarForField(typename: string, fieldName: string): Scalar<any, any> | undefined;
830893
// Warning: (ae-forgotten-export) The symbol "FieldSpecifier" needs to be exported by the entry point index.d.ts
831894
//
832895
// (undocumented)
@@ -931,6 +994,39 @@ class Root extends EntityStore {
931994
// @public (undocumented)
932995
type SafeReadonly<T> = T extends object ? Readonly<T> : T;
933996

997+
// @public (undocumented)
998+
export namespace Scalar {
999+
// (undocumented)
1000+
export interface Options<TSerialized, TParsed> {
1001+
// (undocumented)
1002+
is?(value: TSerialized | TParsed): boolean;
1003+
// (undocumented)
1004+
parse(serializedValue: TSerialized): NoInfer_2<TParsed>;
1005+
// (undocumented)
1006+
serialize(parsedValue: TParsed): NoInfer_2<TSerialized>;
1007+
}
1008+
}
1009+
1010+
// @public (undocumented)
1011+
export class Scalar<TSerialized, TParsed> {
1012+
constructor(options: Scalar.Options<TSerialized, TParsed>);
1013+
// (undocumented)
1014+
coerceToParsed(value: TSerialized | TParsed): TParsed;
1015+
// (undocumented)
1016+
coerceToSerialized(value: TSerialized | TParsed): TSerialized;
1017+
// (undocumented)
1018+
static fromGraphQLScalarType<TSerialized, TParsed>(scalarType: GraphQLScalarType<TParsed, TSerialized>, options?: Pick<Scalar.Options<NoInfer_2<TSerialized>, NoInfer_2<TParsed>>, "is">): Scalar<TSerialized, TParsed>;
1019+
// (undocumented)
1020+
is(value: TSerialized | TParsed): value is TParsed;
1021+
// (undocumented)
1022+
parse(value: TSerialized): TParsed;
1023+
// (undocumented)
1024+
serialize(value: TParsed): TSerialized;
1025+
}
1026+
1027+
// @public (undocumented)
1028+
type ScalarNames = keyof KnownScalars | (string extends keyof ApolloCache.Scalars ? string & {} : never);
1029+
9341030
// @public (undocumented)
9351031
type StorageType = Record<string, any>;
9361032

@@ -1013,9 +1109,10 @@ interface WriteContext extends ReadMergeModifyContext {
10131109

10141110
// Warnings were encountered during analysis:
10151111
//
1016-
// src/cache/inmemory/policies.ts:173:3 - (ae-forgotten-export) The symbol "KeySpecifier" needs to be exported by the entry point index.d.ts
1017-
// src/cache/inmemory/policies.ts:173:3 - (ae-forgotten-export) The symbol "KeyArgsFunction" needs to be exported by the entry point index.d.ts
1018-
// src/cache/inmemory/types.ts:135:3 - (ae-forgotten-export) The symbol "KeyFieldsFunction" needs to be exported by the entry point index.d.ts
1112+
// src/cache/inmemory/policies.ts:176:3 - (ae-forgotten-export) The symbol "KeySpecifier" needs to be exported by the entry point index.d.ts
1113+
// src/cache/inmemory/policies.ts:176:3 - (ae-forgotten-export) The symbol "KeyArgsFunction" needs to be exported by the entry point index.d.ts
1114+
// src/cache/inmemory/policies.ts:179:3 - (ae-forgotten-export) The symbol "ScalarNames" needs to be exported by the entry point index.d.ts
1115+
// src/cache/inmemory/types.ts:139:3 - (ae-forgotten-export) The symbol "KeyFieldsFunction" needs to be exported by the entry point index.d.ts
10191116

10201117
// (No @packageDocumentation comment for this package)
10211118

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

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ import type { IgnoreModifier } from '@apollo/client/cache';
5252
import type { Incremental } from '@apollo/client/incremental';
5353
import { InMemoryCache } from '@apollo/client/cache';
5454
import { InMemoryCacheConfig } from '@apollo/client/cache';
55+
import { InputObjectConfig } from '@apollo/client/cache';
56+
import { InputObjectsOption } from '@apollo/client/cache';
5557
import type { InternalTypes as InternalTypes_2 } from '@apollo/client';
5658
import type { InteropObservable } from 'rxjs';
5759
import type { IsAny } from '@apollo/client/utilities/internal';
@@ -87,6 +89,7 @@ import type { Reference as Reference_2 } from '@apollo/client/cache';
8789
import { RequestHandler } from '@apollo/client/link';
8890
import { resetCaches } from 'graphql-tag';
8991
import { rewriteURIForGET } from '@apollo/client/link/http';
92+
import { Scalar } from '@apollo/client/cache';
9093
import { selectHttpOptionsAndBody } from '@apollo/client/link/http';
9194
import { selectHttpOptionsAndBodyInternal } from '@apollo/client/link/http';
9295
import { selectURI } from '@apollo/client/link/http';
@@ -659,6 +662,10 @@ export { InMemoryCache }
659662

660663
export { InMemoryCacheConfig }
661664

665+
export { InputObjectConfig }
666+
667+
export { InputObjectsOption }
668+
662669
// Warning: (ae-forgotten-export) The symbol "RefetchQueriesIncludeShorthand" needs to be exported by the entry point index.d.ts
663670
//
664671
// @public (undocumented)
@@ -1000,7 +1007,7 @@ class QueryManager {
10001007
// (undocumented)
10011008
broadcastQueries(): void;
10021009
// (undocumented)
1003-
get cache(): ApolloCache;
1010+
get cache(): Cache_2.Implementation;
10041011
// (undocumented)
10051012
clearStore(options?: Cache_2.ResetOptions): Promise<void>;
10061013
// (undocumented)
@@ -1244,6 +1251,8 @@ export { resetCaches }
12441251

12451252
export { rewriteURIForGET }
12461253

1254+
export { Scalar }
1255+
12471256
export { selectHttpOptionsAndBody }
12481257

12491258
export { selectHttpOptionsAndBodyInternal }

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import type { SelectionSetNode } from 'graphql';
3333
import { StrongCache } from '@wry/caches';
3434
import type { Subscription } from 'rxjs';
3535
import type { Trie } from '@wry/trie';
36+
import type { TypeNode } from 'graphql';
3637
import type { TypeOverrides } from '@apollo/client';
3738
import { WeakCache } from '@wry/caches';
3839

@@ -343,6 +344,9 @@ export const getStoreKeyName: ((fieldName: string, args?: Record<string, any> |
343344
setStringify(s: typeof storeKeyNameStringify): (value: any) => string;
344345
};
345346

347+
// @internal @deprecated (undocumented)
348+
export function getUnwrappedType(node: TypeNode): string;
349+
346350
// @public (undocumented)
347351
const globalCaches: {
348352
print?: () => number;
@@ -370,6 +374,13 @@ export function isDocumentNode(value: unknown): value is DocumentNode;
370374
// @internal @deprecated (undocumented)
371375
export function isField(selection: SelectionNode): selection is FieldNode;
372376

377+
// @public
378+
export type IsLooselyEqual<A, B> = [
379+
A
380+
] extends [B] ? [
381+
B
382+
] extends [A] ? true : false : false;
383+
373384
// @internal @deprecated (undocumented)
374385
export function isNonEmptyArray<T>(value: ArrayLike<T> | null | undefined): value is Array<T>;
375386

0 commit comments

Comments
 (0)