Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
78427c0
Add getScalarForField method
jerelmiller Jul 29, 2026
f16bdf0
Implement getScalarForField in InMemoryCache
jerelmiller Jul 29, 2026
40775c5
Process scalar fields for no-cache queries
jerelmiller Jul 29, 2026
1286e86
Extract coerceScalarFieldsToParsed utility
jerelmiller Jul 29, 2026
bf7a0fa
Remove unneeded check
jerelmiller Jul 29, 2026
c452cce
Ensure result keeps __typename
jerelmiller Jul 29, 2026
f8bb441
Add test suite for new utility
jerelmiller Jul 29, 2026
7b1af79
Handle arrays
jerelmiller Jul 29, 2026
a320a34
Minor refactoring to reduce redundancy
jerelmiller Jul 29, 2026
34df76d
Rename function
jerelmiller Jul 29, 2026
dfe64ce
Handle null scalar values
jerelmiller Jul 29, 2026
663f6b4
Rename variable
jerelmiller Jul 29, 2026
3eed480
Handle type conditions
jerelmiller Jul 29, 2026
0f568ff
Add more test cases for fragments
jerelmiller Jul 29, 2026
4da78e5
Add a getRootTypename method to the cache
jerelmiller Jul 29, 2026
0fb5a2e
Handle root fields
jerelmiller Jul 29, 2026
f7d7785
Drop check for typename
jerelmiller Jul 29, 2026
2f676c6
Add additional test for objects without typename
jerelmiller Jul 29, 2026
a68041e
Traverse fragment instead of matching and ignore fields not on object
jerelmiller Jul 29, 2026
fd64266
Inline root typename
jerelmiller Jul 29, 2026
2ef0119
Add another edge case for objects without typenames
jerelmiller Jul 29, 2026
5de3f2f
Remove redundant assignment
jerelmiller Jul 29, 2026
9d48a8b
Reduce duplication in field processing
jerelmiller Jul 29, 2026
9dcd60c
Use workSet to iterate selections
jerelmiller Jul 29, 2026
b24b2d4
Don't assume root typename for 3rd party caches
jerelmiller Jul 29, 2026
6363d13
Add more test cases
jerelmiller Jul 29, 2026
957ab2b
Add changeset
jerelmiller Jul 29, 2026
d807173
Update exports snapshot
jerelmiller Jul 29, 2026
770e224
Remove unused imports
jerelmiller Jul 29, 2026
2bc7587
Update api report
jerelmiller Jul 29, 2026
734c0c9
Remove unneeded .failing
jerelmiller Jul 29, 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
11 changes: 9 additions & 2 deletions .api-reports/api-report-cache.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import type { IsLooselyEqual } from '@apollo/client/utilities/internal';
import { isReference } from '@apollo/client/utilities';
import type { NoInfer as NoInfer_2 } from '@apollo/client/utilities/internal';
import { Observable } from 'rxjs';
import type { OperationTypeNode } from 'graphql';
import type { OperationVariables } from '@apollo/client';
import type { Prettify } from '@apollo/client/utilities/internal';
import { Reference } from '@apollo/client/utilities';
Expand Down Expand Up @@ -110,7 +111,10 @@ export abstract class ApolloCache {
// @internal @deprecated
getMemoryInternals?: typeof getApolloCacheMemoryInternals;
// (undocumented)
getRootTypename(operation: OperationTypeNode): string | undefined;
// (undocumented)
getScalar<TKey extends keyof ApolloCache.Scalars>(key: TKey): ApolloCache.GetScalarType<TKey> | undefined;
getScalarForField(typename: string, fieldName: string): Scalar<unknown, unknown> | undefined;
// (undocumented)
identify(object: StoreObject | Reference): string | undefined;
// (undocumented)
Expand Down Expand Up @@ -664,7 +668,10 @@ export class InMemoryCache extends ApolloCache {
// @internal @deprecated
getMemoryInternals?: typeof getInMemoryCacheMemoryInternals;
// (undocumented)
getRootTypename(operation: OperationTypeNode): string;
// (undocumented)
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;
getScalarForField(typename: string, fieldName: string): Scalar<unknown, unknown> | undefined;
// (undocumented)
identify(object: StoreObject | Reference): string | undefined;
// (undocumented)
Expand Down Expand Up @@ -1154,8 +1161,8 @@ interface WriteContext extends ReadMergeModifyContext {

// Warnings were encountered during analysis:
//
// src/cache/core/cache.ts:204:7 - (ae-incompatible-release-tags) The symbol "[handleIncrementalSymbol]" is marked as @public, but its signature references "DiffIncrementalInfo" which is marked as @internal
// src/cache/inmemory/inMemoryCache.ts:456:7 - (ae-incompatible-release-tags) The symbol "[handleIncrementalSymbol]" is marked as @public, but its signature references "DiffIncrementalInfo" which is marked as @internal
// src/cache/core/cache.ts:205:7 - (ae-incompatible-release-tags) The symbol "[handleIncrementalSymbol]" is marked as @public, but its signature references "DiffIncrementalInfo" which is marked as @internal
// src/cache/inmemory/inMemoryCache.ts:469:7 - (ae-incompatible-release-tags) The symbol "[handleIncrementalSymbol]" is marked as @public, but its signature references "DiffIncrementalInfo" which is marked as @internal
// 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:179:3 - (ae-forgotten-export) The symbol "ScalarNames" needs to be exported by the entry point index.d.ts
// src/cache/inmemory/types.ts:147:3 - (ae-forgotten-export) The symbol "KeyFieldsFunction" needs to be exported by the entry point index.d.ts
Expand Down
2 changes: 1 addition & 1 deletion .api-reports/api-report-core.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -1376,7 +1376,7 @@ export const windowFocusSource: RefetchEventManager.EventSource<Event>;
//
// src/core/ApolloClient.ts:635: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
// src/core/QueryManager.ts:195:5 - (ae-forgotten-export) The symbol "MutationStoreValue" needs to be exported by the entry point index.d.ts
// src/core/QueryManager.ts:196:5 - (ae-forgotten-export) The symbol "MutationStoreValue" needs to be exported by the entry point index.d.ts

// (No @packageDocumentation comment for this package)

Expand Down
8 changes: 6 additions & 2 deletions .api-reports/api-report-utilities_internal.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@

```ts

import type { ApolloCache } from '@apollo/client';
import type { ApolloCache } from '@apollo/client/cache';
import type { ApolloCache as ApolloCache_2 } from '@apollo/client';
import type { ApolloClient } from '@apollo/client';
import type { ASTNode } from 'graphql';
import type { DataValue } from '@apollo/client';
Expand Down Expand Up @@ -86,6 +87,9 @@ export type ClassicSignature = SignatureStyle extends "classic" ? unknown : neve
// @internal @deprecated
export function cloneDeep<T>(value: T): T;

// @public (undocumented)
export function coerceScalarFieldsToParsed(result: Record<string, any>, query: DocumentNode, cache: ApolloCache): Record<string, any>;

// @public
export function combineLatestBatched<T>(observables: Array<Observable<T> & {
dirty?: boolean;
Expand Down Expand Up @@ -425,7 +429,7 @@ export function makeStreamInfoTrie(): StreamInfoTrie;
export function makeUniqueId(prefix: string): string;

// @public (undocumented)
export const mapObservableFragmentMemoized: <From, To>(observable: ApolloCache.ObservableFragment<From>, _cacheKey: symbol, mapFn: (from: ApolloCache.WatchFragmentResult<From>) => ApolloCache.WatchFragmentResult<To>) => ApolloCache.ObservableFragment<To>;
export const mapObservableFragmentMemoized: <From, To>(observable: ApolloCache_2.ObservableFragment<From>, _cacheKey: symbol, mapFn: (from: ApolloCache_2.WatchFragmentResult<From>) => ApolloCache_2.WatchFragmentResult<To>) => ApolloCache_2.ObservableFragment<To>;

// @internal @deprecated (undocumented)
export function maybeDeepFreeze<T>(obj: T): T;
Expand Down
12 changes: 9 additions & 3 deletions .api-reports/api-report.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,10 @@ export abstract class ApolloCache {
// @internal @deprecated
getMemoryInternals?: typeof getApolloCacheMemoryInternals;
// (undocumented)
getRootTypename(operation: OperationTypeNode): string | undefined;
// (undocumented)
getScalar<TKey extends keyof ApolloCache.Scalars>(key: TKey): ApolloCache.GetScalarType<TKey> | undefined;
getScalarForField(typename: string, fieldName: string): Scalar<unknown, unknown> | undefined;
// (undocumented)
identify(object: StoreObject | Reference): string | undefined;
// (undocumented)
Expand Down Expand Up @@ -1696,7 +1699,10 @@ export class InMemoryCache extends ApolloCache {
// @internal @deprecated
getMemoryInternals?: typeof getInMemoryCacheMemoryInternals;
// (undocumented)
getRootTypename(operation: OperationTypeNode): string;
// (undocumented)
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;
getScalarForField(typename: string, fieldName: string): Scalar<unknown, unknown> | undefined;
// (undocumented)
identify(object: StoreObject | Reference): string | undefined;
// (undocumented)
Expand Down Expand Up @@ -3251,8 +3257,8 @@ interface WriteContext extends ReadMergeModifyContext {

// Warnings were encountered during analysis:
//
// src/cache/core/cache.ts:129:11 - (ae-forgotten-export) The symbol "MissingTree" needs to be exported by the entry point index.d.ts
// src/cache/core/cache.ts:204:7 - (ae-forgotten-export) The symbol "DiffIncrementalInfo" needs to be exported by the entry point index.d.ts
// src/cache/core/cache.ts:130:11 - (ae-forgotten-export) The symbol "MissingTree" needs to be exported by the entry point index.d.ts
// src/cache/core/cache.ts:205:7 - (ae-forgotten-export) The symbol "DiffIncrementalInfo" 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
Expand All @@ -3262,7 +3268,7 @@ interface WriteContext extends ReadMergeModifyContext {
// 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:635: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
// src/core/QueryManager.ts:195:5 - (ae-forgotten-export) The symbol "MutationStoreValue" needs to be exported by the entry point index.d.ts
// src/core/QueryManager.ts:196:5 - (ae-forgotten-export) The symbol "MutationStoreValue" needs to be exported by the entry point index.d.ts
// src/local-state/LocalState.ts:149:5 - (ae-forgotten-export) The symbol "LocalState" needs to be exported by the entry point index.d.ts
// src/local-state/LocalState.ts:202:7 - (ae-forgotten-export) The symbol "LocalState" needs to be exported by the entry point index.d.ts
// src/local-state/LocalState.ts:245:7 - (ae-forgotten-export) The symbol "LocalState" needs to be exported by the entry point index.d.ts
Expand Down
5 changes: 5 additions & 0 deletions .changeset/nasty-keys-brush.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@apollo/client": minor
---

Parse scalar fields for `no-cache` queries.
1 change: 1 addition & 0 deletions src/__tests__/__snapshots__/exports.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -446,6 +446,7 @@ Array [
"canonicalStringify",
"checkDocument",
"cloneDeep",
"coerceScalarFieldsToParsed",
"combineLatestBatched",
"compact",
"createFragmentMap",
Expand Down
13 changes: 13 additions & 0 deletions src/cache/core/cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import type {
DocumentNode,
FragmentDefinitionNode,
InlineFragmentNode,
OperationTypeNode,
} from "graphql";
import { wrap } from "optimism";
import {
Expand Down Expand Up @@ -263,6 +264,10 @@ export abstract class ApolloCache {
return null;
}

public getRootTypename(operation: OperationTypeNode): string | undefined {
return;
}

// Custom scalars API

public getScalar<TKey extends keyof ApolloCache.Scalars>(
Expand All @@ -271,6 +276,14 @@ export abstract class ApolloCache {
return;
}

/** Get a scalar instance for a field in a type */
public getScalarForField(
typename: string,
fieldName: string
): Scalar<unknown, unknown> | undefined {
return;
}

/**
* Serializes scalar values in the variables object
*/
Expand Down
13 changes: 13 additions & 0 deletions src/cache/inmemory/inMemoryCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type {
DocumentNode,
FragmentDefinitionNode,
InlineFragmentNode,
OperationTypeNode,
} from "graphql";
import type { OptimisticWrapperFunction } from "optimism";
import { wrap } from "optimism";
Expand Down Expand Up @@ -206,6 +207,10 @@ export class InMemoryCache extends ApolloCache {
);
}

public getRootTypename(operation: OperationTypeNode): string {
return this.policies.rootTypenamesById[`ROOT_${operation.toUpperCase()}`];
}

public getScalar<TKey extends keyof ApolloCache.Scalars>(
key: TKey
): ApolloCache.GetScalarType<TKey> extends (
Expand All @@ -220,6 +225,14 @@ export class InMemoryCache extends ApolloCache {
return this.config.scalars?.[key as string] as any;
}

/** Get a scalar instance for a field in a type */
public getScalarForField(
typename: string,
fieldName: string
): Scalar<unknown, unknown> | undefined {
return this.policies.getScalarForField(typename, fieldName);
}

/**
* {@inheritDoc @apollo/client/cache!ApolloCache#serializeVariables:member(1)}
*/
Expand Down
21 changes: 20 additions & 1 deletion src/core/QueryManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ import type { ExtensionsWithStreamInfo } from "@apollo/client/utilities/internal
import {
AutoCleanedWeakCache,
checkDocument,
coerceScalarFieldsToParsed,
extensionsSymbol,
filterMap,
getDefaultValues,
Expand Down Expand Up @@ -1756,7 +1757,25 @@ export class QueryManager {
return { fromLink: true, observable: resultsFromLink() };

case "no-cache":
return { fromLink: true, observable: resultsFromLink() };
return {
fromLink: true,
observable: resultsFromLink().pipe(
map((notification) => {
if (
notification.kind === "N" &&
notification.value.data != null
) {
notification.value.data = coerceScalarFieldsToParsed(
notification.value.data,
query,
this.cache
) as TData;
}

return notification;
})
),
};

case "standby":
return { fromLink: false, observable: EMPTY };
Expand Down
87 changes: 42 additions & 45 deletions src/core/__tests__/client.query/customScalars.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -397,57 +397,54 @@ test("parses network custom scalar fields with a network-only fetch policy", asy
});
});

test.failing(
"parses custom scalar fields with a no-cache fetch policy",
async () => {
const query = gql`
query Event {
event {
id
startDate
}
test("parses custom scalar fields with a no-cache fetch policy", async () => {
const query = gql`
query Event {
event {
id
startDate
}
`;
const client = new ApolloClient({
cache: new InMemoryCache({
scalars: { Date: dateScalar },
typePolicies: {
Event: {
fields: {
startDate: { scalar: "Date" },
},
}
`;
const client = new ApolloClient({
cache: new InMemoryCache({
scalars: { Date: dateScalar },
typePolicies: {
Event: {
fields: {
startDate: { scalar: "Date" },
},
},
}),
link: new ApolloLink(() =>
of({
data: {
event: {
__typename: "Event",
id: "1",
startDate: "2026-01-01",
},
},
}),
link: new ApolloLink(() =>
of({
data: {
event: {
__typename: "Event",
id: "1",
startDate: "2026-01-01",
},
}).pipe(delay(20))
),
});

await expect(
client.query({
query,
fetchPolicy: "no-cache",
})
).resolves.toStrictEqualTyped({
data: {
event: {
__typename: "Event",
id: "1",
startDate: new Date(2026, 0, 1),
},
}).pipe(delay(20))
),
});

await expect(
client.query({
query,
fetchPolicy: "no-cache",
})
).resolves.toStrictEqualTyped({
data: {
event: {
__typename: "Event",
id: "1",
startDate: new Date(2026, 0, 1),
},
});
}
);
},
});
});

test("preserves referential identity when fetching identical serialized scalar values", async () => {
const query = gql`
Expand Down
Loading
Loading