From f5420b0edf8bcb7ce08e988ea96e2940ac74150a Mon Sep 17 00:00:00 2001 From: Lenz Weber-Tronic Date: Fri, 15 Dec 2023 10:51:58 +0100 Subject: [PATCH] add central configuration for Apollo Client cache sizes (#11408) * `print`: use `WeakCache` instead of `WeakMap` * format * pull in memory testing tools from PR 11358 * Persisted Query Link: improve memory management * re-add accidentally removed dependency * update api * update size limit * size-limit * fix test failure * better cleanup of interval/timeout * apply formatting * remove unneccessary type * format again after updating prettier * add central confiuguration for Apollo Client cache sizes * resolve import cycle * add exports * reduce cache collection throttle timeout * typo in comment * fix circular import * size-limits * update type to remove `WeakKey` * api-extractor * work around ES5 class compat * update api-report * fix typo in comment * add more caches * add more caches * chores * add type export * update test * chores * formatting * adjust more tests * rename to `AutoCleaned*Cache`, mark @internal * Update src/utilities/caching/sizes.ts Co-authored-by: Jerel Miller * chores * size-limits * unify comment release tags * update exports * naming & lazy bundling approach through inlining * chores * size --------- Co-authored-by: Jerel Miller --- .api-reports/api-report-cache.md | 2 +- .api-reports/api-report-core.md | 8 +- .api-reports/api-report-react.md | 6 +- .api-reports/api-report-react_components.md | 6 +- .api-reports/api-report-react_context.md | 6 +- .api-reports/api-report-react_hoc.md | 6 +- .api-reports/api-report-react_hooks.md | 6 +- .api-reports/api-report-react_ssr.md | 6 +- .api-reports/api-report-testing.md | 6 +- .api-reports/api-report-testing_core.md | 6 +- .api-reports/api-report-utilities.md | 75 ++++- .api-reports/api-report.md | 8 +- .size-limits.json | 4 +- api-extractor.json | 5 + src/__tests__/__snapshots__/exports.ts.snap | 3 + src/cache/core/cache.ts | 14 +- src/cache/inmemory/__tests__/cache.ts | 17 +- src/cache/inmemory/__tests__/readFromStore.ts | 6 +- src/cache/inmemory/fragmentRegistry.ts | 29 +- src/cache/inmemory/inMemoryCache.ts | 7 +- src/cache/inmemory/readFromStore.ts | 12 +- src/cache/inmemory/types.ts | 5 + src/core/QueryManager.ts | 10 +- src/link/persisted-queries/index.ts | 16 +- .../removeTypenameFromVariables.ts | 34 ++- src/react/parser/index.ts | 13 +- src/utilities/caching/__tests__/sizes.test.ts | 11 + src/utilities/caching/caches.ts | 80 ++++++ src/utilities/caching/index.ts | 3 + src/utilities/caching/sizes.ts | 264 ++++++++++++++++++ src/utilities/common/canonicalStringify.ts | 13 +- src/utilities/graphql/DocumentTransform.ts | 3 +- src/utilities/graphql/print.ts | 15 +- src/utilities/index.ts | 8 + 34 files changed, 628 insertions(+), 85 deletions(-) create mode 100644 src/utilities/caching/__tests__/sizes.test.ts create mode 100644 src/utilities/caching/caches.ts create mode 100644 src/utilities/caching/index.ts create mode 100644 src/utilities/caching/sizes.ts diff --git a/.api-reports/api-report-cache.md b/.api-reports/api-report-cache.md index 50588f8f4c0..1f2efd837fc 100644 --- a/.api-reports/api-report-cache.md +++ b/.api-reports/api-report-cache.md @@ -551,7 +551,7 @@ export interface InMemoryCacheConfig extends ApolloReducerConfig { fragments?: FragmentRegistryAPI; // (undocumented) possibleTypes?: PossibleTypesMap; - // (undocumented) + // @deprecated (undocumented) resultCacheMaxSize?: number; // (undocumented) resultCaching?: boolean; diff --git a/.api-reports/api-report-core.md b/.api-reports/api-report-core.md index e16b21db97b..d2ec18f333b 100644 --- a/.api-reports/api-report-core.md +++ b/.api-reports/api-report-core.md @@ -1013,7 +1013,7 @@ export interface InMemoryCacheConfig extends ApolloReducerConfig { fragments?: FragmentRegistryAPI; // (undocumented) possibleTypes?: PossibleTypesMap; - // (undocumented) + // @deprecated (undocumented) resultCacheMaxSize?: number; // (undocumented) resultCaching?: boolean; @@ -2113,9 +2113,9 @@ interface WriteContext extends ReadMergeModifyContext { // src/cache/inmemory/types.ts:132:3 - (ae-forgotten-export) The symbol "KeyFieldsFunction" needs to be exported by the entry point index.d.ts // src/core/ObservableQuery.ts:113:5 - (ae-forgotten-export) The symbol "QueryManager" needs to be exported by the entry point index.d.ts // src/core/ObservableQuery.ts:114:5 - (ae-forgotten-export) The symbol "QueryInfo" needs to be exported by the entry point index.d.ts -// src/core/QueryManager.ts:120:5 - (ae-forgotten-export) The symbol "MutationStoreValue" needs to be exported by the entry point index.d.ts -// src/core/QueryManager.ts:154:5 - (ae-forgotten-export) The symbol "LocalState" needs to be exported by the entry point index.d.ts -// src/core/QueryManager.ts:395:7 - (ae-forgotten-export) The symbol "UpdateQueries" needs to be exported by the entry point index.d.ts +// src/core/QueryManager.ts:121:5 - (ae-forgotten-export) The symbol "MutationStoreValue" needs to be exported by the entry point index.d.ts +// src/core/QueryManager.ts:155:5 - (ae-forgotten-export) The symbol "LocalState" needs to be exported by the entry point index.d.ts +// src/core/QueryManager.ts:396:7 - (ae-forgotten-export) The symbol "UpdateQueries" needs to be exported by the entry point index.d.ts // src/core/watchQueryOptions.ts:260:2 - (ae-forgotten-export) The symbol "UpdateQueryFn" needs to be exported by the entry point index.d.ts // src/link/http/selectHttpOptionsAndBody.ts:128:32 - (ae-forgotten-export) The symbol "HttpQueryOptions" needs to be exported by the entry point index.d.ts diff --git a/.api-reports/api-report-react.md b/.api-reports/api-report-react.md index 7acf9ba2c5a..7a8606ad8fe 100644 --- a/.api-reports/api-report-react.md +++ b/.api-reports/api-report-react.md @@ -2250,9 +2250,9 @@ interface WatchQueryOptions(it: (...args: TArgs // src/core/LocalState.ts:46:5 - (ae-forgotten-export) The symbol "FragmentMap" needs to be exported by the entry point index.d.ts // src/core/ObservableQuery.ts:113:5 - (ae-forgotten-export) The symbol "QueryManager" needs to be exported by the entry point index.d.ts // src/core/ObservableQuery.ts:114:5 - (ae-forgotten-export) The symbol "QueryInfo" needs to be exported by the entry point index.d.ts -// src/core/QueryManager.ts:120:5 - (ae-forgotten-export) The symbol "MutationStoreValue" needs to be exported by the entry point index.d.ts -// src/core/QueryManager.ts:154:5 - (ae-forgotten-export) The symbol "LocalState" needs to be exported by the entry point index.d.ts -// src/core/QueryManager.ts:395:7 - (ae-forgotten-export) The symbol "UpdateQueries" needs to be exported by the entry point index.d.ts +// src/core/QueryManager.ts:121:5 - (ae-forgotten-export) The symbol "MutationStoreValue" needs to be exported by the entry point index.d.ts +// src/core/QueryManager.ts:155:5 - (ae-forgotten-export) The symbol "LocalState" needs to be exported by the entry point index.d.ts +// src/core/QueryManager.ts:396:7 - (ae-forgotten-export) The symbol "UpdateQueries" needs to be exported by the entry point index.d.ts // src/core/types.ts:154:3 - (ae-forgotten-export) The symbol "ApolloError" needs to be exported by the entry point index.d.ts // src/core/types.ts:156:3 - (ae-forgotten-export) The symbol "NetworkStatus" needs to be exported by the entry point index.d.ts // src/core/types.ts:174:3 - (ae-forgotten-export) The symbol "MutationQueryReducer" needs to be exported by the entry point index.d.ts diff --git a/.api-reports/api-report-testing_core.md b/.api-reports/api-report-testing_core.md index ad2f42b7825..ca6f6c60b4e 100644 --- a/.api-reports/api-report-testing_core.md +++ b/.api-reports/api-report-testing_core.md @@ -1580,9 +1580,9 @@ export function withWarningSpy(it: (...args: TArgs // src/core/LocalState.ts:46:5 - (ae-forgotten-export) The symbol "FragmentMap" needs to be exported by the entry point index.d.ts // src/core/ObservableQuery.ts:113:5 - (ae-forgotten-export) The symbol "QueryManager" needs to be exported by the entry point index.d.ts // src/core/ObservableQuery.ts:114:5 - (ae-forgotten-export) The symbol "QueryInfo" needs to be exported by the entry point index.d.ts -// src/core/QueryManager.ts:120:5 - (ae-forgotten-export) The symbol "MutationStoreValue" needs to be exported by the entry point index.d.ts -// src/core/QueryManager.ts:154:5 - (ae-forgotten-export) The symbol "LocalState" needs to be exported by the entry point index.d.ts -// src/core/QueryManager.ts:395:7 - (ae-forgotten-export) The symbol "UpdateQueries" needs to be exported by the entry point index.d.ts +// src/core/QueryManager.ts:121:5 - (ae-forgotten-export) The symbol "MutationStoreValue" needs to be exported by the entry point index.d.ts +// src/core/QueryManager.ts:155:5 - (ae-forgotten-export) The symbol "LocalState" needs to be exported by the entry point index.d.ts +// src/core/QueryManager.ts:396:7 - (ae-forgotten-export) The symbol "UpdateQueries" needs to be exported by the entry point index.d.ts // src/core/types.ts:154:3 - (ae-forgotten-export) The symbol "ApolloError" needs to be exported by the entry point index.d.ts // src/core/types.ts:156:3 - (ae-forgotten-export) The symbol "NetworkStatus" needs to be exported by the entry point index.d.ts // src/core/types.ts:174:3 - (ae-forgotten-export) The symbol "MutationQueryReducer" needs to be exported by the entry point index.d.ts diff --git a/.api-reports/api-report-utilities.md b/.api-reports/api-report-utilities.md index 9ccfda99640..65911cc6b02 100644 --- a/.api-reports/api-report-utilities.md +++ b/.api-reports/api-report-utilities.md @@ -22,12 +22,14 @@ import type { Observer } from 'zen-observable-ts'; import type { OperationDefinitionNode } from 'graphql'; import type { SelectionNode } from 'graphql'; import type { SelectionSetNode } from 'graphql'; +import { StrongCache } from '@wry/caches'; import type { Subscriber } from 'zen-observable-ts'; import { Trie } from '@wry/trie'; import { TypedDocumentNode } from '@graphql-typed-document-node/core'; import type { ValueNode } from 'graphql'; import type { VariableDefinitionNode } from 'graphql'; import type { VariableNode } from 'graphql'; +import { WeakCache } from '@wry/caches'; // @public (undocumented) export const addTypenameToDocument: ((doc: TNode) => TNode) & { @@ -324,6 +326,18 @@ export type AsStoreObject(observable: Observable, mapFn: (value: V) => R | PromiseLike, catchFn?: (error: any) => R | PromiseLike): Observable; +// @internal +export const AutoCleanedStrongCache: typeof StrongCache; + +// @internal (undocumented) +export type AutoCleanedStrongCache = StrongCache; + +// @internal +export const AutoCleanedWeakCache: typeof WeakCache; + +// @internal (undocumented) +export type AutoCleanedWeakCache = WeakCache; + // Warning: (ae-forgotten-export) The symbol "InMemoryCache" needs to be exported by the entry point index.d.ts // // @public (undocumented) @@ -444,6 +458,27 @@ class CacheGroup { resetCaching(): void; } +// @public +export interface CacheSizes { + "cache.fragmentQueryDocuments": number; + "documentTransform.cache": number; + "fragmentRegistry.findFragmentSpreads": number; + "fragmentRegistry.lookup": number; + "fragmentRegistry.transform": number; + "inMemoryCache.executeSelectionSet": number; + "inMemoryCache.executeSubSelectedArray": number; + "inMemoryCache.maybeBroadcastWatch": number; + "PersistedQueryLink.persistedQueryHashes": number; + "queryManager.getDocumentInfo": number; + "removeTypenameFromVariables.getVariableDefinitions": number; + canonicalStringify: number; + parser: number; + print: number; +} + +// @public +export const cacheSizes: Partial; + // @public (undocumented) const enum CacheWriteBehavior { // (undocumented) @@ -667,6 +702,38 @@ type DeepPartialReadonlySet = {} & ReadonlySet>; // @public (undocumented) type DeepPartialSet = {} & Set>; +// @public (undocumented) +export const enum defaultCacheSizes { + // (undocumented) + "cache.fragmentQueryDocuments" = 1000, + // (undocumented) + "documentTransform.cache" = 2000, + // (undocumented) + "fragmentRegistry.findFragmentSpreads" = 4000, + // (undocumented) + "fragmentRegistry.lookup" = 1000, + // (undocumented) + "fragmentRegistry.transform" = 2000, + // (undocumented) + "inMemoryCache.executeSelectionSet" = 10000, + // (undocumented) + "inMemoryCache.executeSubSelectedArray" = 5000, + // (undocumented) + "inMemoryCache.maybeBroadcastWatch" = 5000, + // (undocumented) + "PersistedQueryLink.persistedQueryHashes" = 2000, + // (undocumented) + "queryManager.getDocumentInfo" = 2000, + // (undocumented) + "removeTypenameFromVariables.getVariableDefinitions" = 2000, + // (undocumented) + canonicalStringify = 1000, + // (undocumented) + parser = 1000, + // (undocumented) + print = 2000 +} + // @public (undocumented) interface DefaultContext extends Record { } @@ -1198,7 +1265,7 @@ interface InMemoryCacheConfig extends ApolloReducerConfig { // // (undocumented) possibleTypes?: PossibleTypesMap; - // (undocumented) + // @deprecated (undocumented) resultCacheMaxSize?: number; // (undocumented) resultCaching?: boolean; @@ -2463,9 +2530,9 @@ interface WriteContext extends ReadMergeModifyContext { // src/core/LocalState.ts:71:3 - (ae-forgotten-export) The symbol "ApolloClient" needs to be exported by the entry point index.d.ts // src/core/ObservableQuery.ts:113:5 - (ae-forgotten-export) The symbol "QueryManager" needs to be exported by the entry point index.d.ts // src/core/ObservableQuery.ts:114:5 - (ae-forgotten-export) The symbol "QueryInfo" needs to be exported by the entry point index.d.ts -// src/core/QueryManager.ts:120:5 - (ae-forgotten-export) The symbol "MutationStoreValue" needs to be exported by the entry point index.d.ts -// src/core/QueryManager.ts:154:5 - (ae-forgotten-export) The symbol "LocalState" needs to be exported by the entry point index.d.ts -// src/core/QueryManager.ts:395:7 - (ae-forgotten-export) The symbol "UpdateQueries" needs to be exported by the entry point index.d.ts +// src/core/QueryManager.ts:121:5 - (ae-forgotten-export) The symbol "MutationStoreValue" needs to be exported by the entry point index.d.ts +// src/core/QueryManager.ts:155:5 - (ae-forgotten-export) The symbol "LocalState" needs to be exported by the entry point index.d.ts +// src/core/QueryManager.ts:396:7 - (ae-forgotten-export) The symbol "UpdateQueries" needs to be exported by the entry point index.d.ts // src/core/types.ts:154:3 - (ae-forgotten-export) The symbol "ApolloError" needs to be exported by the entry point index.d.ts // src/core/types.ts:156:3 - (ae-forgotten-export) The symbol "NetworkStatus" needs to be exported by the entry point index.d.ts // src/core/types.ts:174:3 - (ae-forgotten-export) The symbol "MutationQueryReducer" needs to be exported by the entry point index.d.ts diff --git a/.api-reports/api-report.md b/.api-reports/api-report.md index 5a22a0eefdd..d3549849065 100644 --- a/.api-reports/api-report.md +++ b/.api-reports/api-report.md @@ -1199,7 +1199,7 @@ export interface InMemoryCacheConfig extends ApolloReducerConfig { fragments?: FragmentRegistryAPI; // (undocumented) possibleTypes?: PossibleTypesMap; - // (undocumented) + // @deprecated (undocumented) resultCacheMaxSize?: number; // (undocumented) resultCaching?: boolean; @@ -2897,9 +2897,9 @@ interface WriteContext extends ReadMergeModifyContext { // src/cache/inmemory/types.ts:132:3 - (ae-forgotten-export) The symbol "KeyFieldsFunction" needs to be exported by the entry point index.d.ts // src/core/ObservableQuery.ts:113:5 - (ae-forgotten-export) The symbol "QueryManager" needs to be exported by the entry point index.d.ts // src/core/ObservableQuery.ts:114:5 - (ae-forgotten-export) The symbol "QueryInfo" needs to be exported by the entry point index.d.ts -// src/core/QueryManager.ts:120:5 - (ae-forgotten-export) The symbol "MutationStoreValue" needs to be exported by the entry point index.d.ts -// src/core/QueryManager.ts:154:5 - (ae-forgotten-export) The symbol "LocalState" needs to be exported by the entry point index.d.ts -// src/core/QueryManager.ts:395:7 - (ae-forgotten-export) The symbol "UpdateQueries" needs to be exported by the entry point index.d.ts +// src/core/QueryManager.ts:121:5 - (ae-forgotten-export) The symbol "MutationStoreValue" needs to be exported by the entry point index.d.ts +// src/core/QueryManager.ts:155:5 - (ae-forgotten-export) The symbol "LocalState" needs to be exported by the entry point index.d.ts +// src/core/QueryManager.ts:396:7 - (ae-forgotten-export) The symbol "UpdateQueries" needs to be exported by the entry point index.d.ts // src/core/watchQueryOptions.ts:260:2 - (ae-forgotten-export) The symbol "UpdateQueryFn" needs to be exported by the entry point index.d.ts // src/link/http/selectHttpOptionsAndBody.ts:128:32 - (ae-forgotten-export) The symbol "HttpQueryOptions" needs to be exported by the entry point index.d.ts // src/react/hooks/useBackgroundQuery.ts:30:3 - (ae-forgotten-export) The symbol "FetchMoreFunction" needs to be exported by the entry point index.d.ts diff --git a/.size-limits.json b/.size-limits.json index 1ac8be3319e..f6a846be06c 100644 --- a/.size-limits.json +++ b/.size-limits.json @@ -1,4 +1,4 @@ { - "dist/apollo-client.min.cjs": 38535, - "import { ApolloClient, InMemoryCache, HttpLink } from \"dist/index.js\" (production)": 32310 + "dist/apollo-client.min.cjs": 38831, + "import { ApolloClient, InMemoryCache, HttpLink } from \"dist/index.js\" (production)": 32603 } diff --git a/api-extractor.json b/api-extractor.json index 5a0a74befa3..b257d21f772 100644 --- a/api-extractor.json +++ b/api-extractor.json @@ -119,6 +119,11 @@ "logLevel": "none" }, + "ae-internal-missing-underscore": { + "logLevel": "none", + "addToApiReportFile": false + }, + "ae-unresolved-link": { "logLevel": "warning", "addToApiReportFile": true diff --git a/src/__tests__/__snapshots__/exports.ts.snap b/src/__tests__/__snapshots__/exports.ts.snap index 70229c88a17..ba76b853b7c 100644 --- a/src/__tests__/__snapshots__/exports.ts.snap +++ b/src/__tests__/__snapshots__/exports.ts.snap @@ -383,6 +383,8 @@ Array [ exports[`exports of public entry points @apollo/client/utilities 1`] = ` Array [ + "AutoCleanedStrongCache", + "AutoCleanedWeakCache", "Concast", "DEV", "DeepMerger", @@ -392,6 +394,7 @@ Array [ "argumentsObjectFromField", "asyncMap", "buildQueryFromSelectionSet", + "cacheSizes", "canUseAsyncIteratorSymbol", "canUseDOM", "canUseLayoutEffect", diff --git a/src/cache/core/cache.ts b/src/cache/core/cache.ts index 44f283a2723..b90871cdd82 100644 --- a/src/cache/core/cache.ts +++ b/src/cache/core/cache.ts @@ -2,9 +2,14 @@ import type { DocumentNode } from "graphql"; import { wrap } from "optimism"; import type { StoreObject, Reference } from "../../utilities/index.js"; -import { getFragmentQueryDocument } from "../../utilities/index.js"; +import { + cacheSizes, + defaultCacheSizes, + getFragmentQueryDocument, +} from "../../utilities/index.js"; import type { DataProxy } from "./types/DataProxy.js"; import type { Cache } from "./types/Cache.js"; +import { WeakCache } from "@wry/caches"; export type Transaction = (c: ApolloCache) => void; @@ -137,7 +142,12 @@ export abstract class ApolloCache implements DataProxy { // Make sure we compute the same (===) fragment query document every // time we receive the same fragment in readFragment. - private getFragmentDoc = wrap(getFragmentQueryDocument); + private getFragmentDoc = wrap(getFragmentQueryDocument, { + max: + cacheSizes["cache.fragmentQueryDocuments"] || + defaultCacheSizes["cache.fragmentQueryDocuments"], + cache: WeakCache, + }); public readFragment( options: Cache.ReadFragmentOptions, diff --git a/src/cache/inmemory/__tests__/cache.ts b/src/cache/inmemory/__tests__/cache.ts index edaa63fb4d4..2d426ed7207 100644 --- a/src/cache/inmemory/__tests__/cache.ts +++ b/src/cache/inmemory/__tests__/cache.ts @@ -19,6 +19,7 @@ import { StoreWriter } from "../writeToStore"; import { ObjectCanon } from "../object-canon"; import { TypePolicies } from "../policies"; import { spyOnConsole } from "../../../testing/internal"; +import { defaultCacheSizes } from "../../../utilities"; disableFragmentWarnings(); @@ -2119,15 +2120,17 @@ describe("Cache", () => { }); describe("resultCacheMaxSize", () => { - const defaultMaxSize = Math.pow(2, 16); - it("uses default max size on caches if resultCacheMaxSize is not configured", () => { const cache = new InMemoryCache(); - expect(cache["maybeBroadcastWatch"].options.max).toBe(defaultMaxSize); + expect(cache["maybeBroadcastWatch"].options.max).toBe( + defaultCacheSizes["inMemoryCache.maybeBroadcastWatch"] + ); expect(cache["storeReader"]["executeSelectionSet"].options.max).toBe( - defaultMaxSize + defaultCacheSizes["inMemoryCache.executeSelectionSet"] + ); + expect(cache["getFragmentDoc"].options.max).toBe( + defaultCacheSizes["cache.fragmentQueryDocuments"] ); - expect(cache["getFragmentDoc"].options.max).toBe(defaultMaxSize); }); it("configures max size on caches when resultCacheMaxSize is set", () => { @@ -2137,7 +2140,9 @@ describe("resultCacheMaxSize", () => { expect(cache["storeReader"]["executeSelectionSet"].options.max).toBe( resultCacheMaxSize ); - expect(cache["getFragmentDoc"].options.max).toBe(defaultMaxSize); + expect(cache["getFragmentDoc"].options.max).toBe( + defaultCacheSizes["cache.fragmentQueryDocuments"] + ); }); }); diff --git a/src/cache/inmemory/__tests__/readFromStore.ts b/src/cache/inmemory/__tests__/readFromStore.ts index 972e252215c..2af1138cef8 100644 --- a/src/cache/inmemory/__tests__/readFromStore.ts +++ b/src/cache/inmemory/__tests__/readFromStore.ts @@ -17,14 +17,16 @@ import { isReference, TypedDocumentNode, } from "../../../core"; +import { defaultCacheSizes } from "../../../utilities"; describe("resultCacheMaxSize", () => { const cache = new InMemoryCache(); - const defaultMaxSize = Math.pow(2, 16); it("uses default max size on caches if resultCacheMaxSize is not configured", () => { const reader = new StoreReader({ cache }); - expect(reader["executeSelectionSet"].options.max).toBe(defaultMaxSize); + expect(reader["executeSelectionSet"].options.max).toBe( + defaultCacheSizes["inMemoryCache.executeSelectionSet"] + ); }); it("configures max size on caches when resultCacheMaxSize is set", () => { diff --git a/src/cache/inmemory/fragmentRegistry.ts b/src/cache/inmemory/fragmentRegistry.ts index 12cade01aea..28463594c9d 100644 --- a/src/cache/inmemory/fragmentRegistry.ts +++ b/src/cache/inmemory/fragmentRegistry.ts @@ -9,7 +9,12 @@ import { visit } from "graphql"; import { wrap } from "optimism"; import type { FragmentMap } from "../../utilities/index.js"; -import { getFragmentDefinitions } from "../../utilities/index.js"; +import { + cacheSizes, + defaultCacheSizes, + getFragmentDefinitions, +} from "../../utilities/index.js"; +import { WeakCache } from "@wry/caches"; export interface FragmentRegistryAPI { register(...fragments: DocumentNode[]): this; @@ -68,11 +73,29 @@ class FragmentRegistry implements FragmentRegistryAPI { const proto = FragmentRegistry.prototype; this.invalidate = (this.lookup = wrap(proto.lookup.bind(this), { makeCacheKey: (arg) => arg, + max: + cacheSizes["fragmentRegistry.lookup"] || + defaultCacheSizes["fragmentRegistry.lookup"], })).dirty; // This dirty function is bound to the wrapped lookup method. - this.transform = wrap(proto.transform.bind(this)); - this.findFragmentSpreads = wrap(proto.findFragmentSpreads.bind(this)); + this.transform = wrap(proto.transform.bind(this), { + cache: WeakCache, + max: + cacheSizes["fragmentRegistry.transform"] || + defaultCacheSizes["fragmentRegistry.transform"], + }); + this.findFragmentSpreads = wrap(proto.findFragmentSpreads.bind(this), { + cache: WeakCache, + max: + cacheSizes["fragmentRegistry.findFragmentSpreads"] || + defaultCacheSizes["fragmentRegistry.findFragmentSpreads"], + }); } + /* + * Note: + * This method is only memoized so it can serve as a dependency to `tranform`, + * so calling `invalidate` will invalidate cache entries for `transform`. + */ public lookup(fragmentName: string): FragmentDefinitionNode | null { return this.registry[fragmentName] || null; } diff --git a/src/cache/inmemory/inMemoryCache.ts b/src/cache/inmemory/inMemoryCache.ts index f00fed8767e..6e001cd9137 100644 --- a/src/cache/inmemory/inMemoryCache.ts +++ b/src/cache/inmemory/inMemoryCache.ts @@ -18,6 +18,8 @@ import { DocumentTransform, canonicalStringify, print, + cacheSizes, + defaultCacheSizes, } from "../../utilities/index.js"; import type { InMemoryCacheConfig, NormalizedCacheObject } from "./types.js"; import { StoreReader } from "./readFromStore.js"; @@ -124,7 +126,10 @@ export class InMemoryCache extends ApolloCache { return this.broadcastWatch(c, options); }, { - max: this.config.resultCacheMaxSize, + max: + this.config.resultCacheMaxSize || + cacheSizes["inMemoryCache.maybeBroadcastWatch"] || + defaultCacheSizes["inMemoryCache.maybeBroadcastWatch"], makeCacheKey: (c: Cache.WatchOptions) => { // Return a cache key (thus enabling result caching) only if we're // currently using a data store that can track cache dependencies. diff --git a/src/cache/inmemory/readFromStore.ts b/src/cache/inmemory/readFromStore.ts index a280948b87e..cc1ec9f0f6b 100644 --- a/src/cache/inmemory/readFromStore.ts +++ b/src/cache/inmemory/readFromStore.ts @@ -29,6 +29,8 @@ import { canUseWeakMap, compact, canonicalStringify, + cacheSizes, + defaultCacheSizes, } from "../../utilities/index.js"; import type { Cache } from "../core/types/Cache.js"; import type { @@ -193,7 +195,10 @@ export class StoreReader { return this.execSelectionSetImpl(options); }, { - max: this.config.resultCacheMaxSize, + max: + this.config.resultCacheMaxSize || + cacheSizes["inMemoryCache.executeSelectionSet"] || + defaultCacheSizes["inMemoryCache.executeSelectionSet"], keyArgs: execSelectionSetKeyArgs, // Note that the parameters of makeCacheKey are determined by the // array returned by keyArgs. @@ -219,7 +224,10 @@ export class StoreReader { return this.execSubSelectedArrayImpl(options); }, { - max: this.config.resultCacheMaxSize, + max: + this.config.resultCacheMaxSize || + cacheSizes["inMemoryCache.executeSubSelectedArray"] || + defaultCacheSizes["inMemoryCache.executeSubSelectedArray"], makeCacheKey({ field, array, context }) { if (supportsResultCaching(context.store)) { return context.store.makeCacheKey(field, array, context.varString); diff --git a/src/cache/inmemory/types.ts b/src/cache/inmemory/types.ts index 5a8d66ec300..207a802feb4 100644 --- a/src/cache/inmemory/types.ts +++ b/src/cache/inmemory/types.ts @@ -137,6 +137,11 @@ export interface InMemoryCacheConfig extends ApolloReducerConfig { resultCaching?: boolean; possibleTypes?: PossibleTypesMap; typePolicies?: TypePolicies; + /** + * @deprecated + * Please use `cacheSizes` instead. + * TODO: write docs page, add link here + */ resultCacheMaxSize?: number; canonizeResults?: boolean; fragments?: FragmentRegistryAPI; diff --git a/src/core/QueryManager.ts b/src/core/QueryManager.ts index 84e01b89023..ba70751e3c8 100644 --- a/src/core/QueryManager.ts +++ b/src/core/QueryManager.ts @@ -4,11 +4,11 @@ import type { DocumentNode } from "graphql"; // TODO(brian): A hack until this issue is resolved (https://github.com/graphql/graphql-js/issues/3356) type OperationTypeNode = any; import { equal } from "@wry/equality"; -import { WeakCache } from "@wry/caches"; import type { ApolloLink, FetchResult } from "../link/core/index.js"; import { execute } from "../link/core/index.js"; import { + defaultCacheSizes, hasDirectives, isExecutionPatchIncrementalResult, isExecutionPatchResult, @@ -100,6 +100,7 @@ interface TransformCacheEntry { import type { DefaultOptions } from "./ApolloClient.js"; import { Trie } from "@wry/trie"; +import { AutoCleanedWeakCache, cacheSizes } from "../utilities/index.js"; export class QueryManager { public cache: ApolloCache; @@ -662,10 +663,13 @@ export class QueryManager { return this.documentTransform.transformDocument(document); } - private transformCache = new WeakCache< + private transformCache = new AutoCleanedWeakCache< DocumentNode, TransformCacheEntry - >(/** TODO: decide on a maximum size (will do all max sizes in a combined separate PR) */); + >( + cacheSizes["queryManager.getDocumentInfo"] || + defaultCacheSizes["queryManager.getDocumentInfo"] + ); public getDocumentInfo(document: DocumentNode) { const { transformCache } = this; diff --git a/src/link/persisted-queries/index.ts b/src/link/persisted-queries/index.ts index 95c4129c802..400af11d6f5 100644 --- a/src/link/persisted-queries/index.ts +++ b/src/link/persisted-queries/index.ts @@ -12,7 +12,11 @@ import type { import { Observable, compact, isNonEmptyArray } from "../../utilities/index.js"; import type { NetworkError } from "../../errors/index.js"; import type { ServerError } from "../utils/index.js"; -import { WeakCache } from "@wry/caches"; +import { + cacheSizes, + AutoCleanedWeakCache, + defaultCacheSizes, +} from "../../utilities/index.js"; export const VERSION = 1; @@ -94,7 +98,9 @@ function operationDefinesMutation(operation: Operation) { export const createPersistedQueryLink = ( options: PersistedQueryLink.Options ) => { - let hashesByQuery: WeakCache> | undefined; + let hashesByQuery: + | AutoCleanedWeakCache> + | undefined; function resetHashCache() { hashesByQuery = undefined; } @@ -140,8 +146,10 @@ export const createPersistedQueryLink = ( return getHashPromise(query); } if (!hashesByQuery) { - hashesByQuery = - new WeakCache(/** TODO: decide on a maximum size (will do all max sizes in a combined separate PR) */); + hashesByQuery = new AutoCleanedWeakCache( + cacheSizes["PersistedQueryLink.persistedQueryHashes"] || + defaultCacheSizes["PersistedQueryLink.persistedQueryHashes"] + ); } let hash = hashesByQuery.get(query)!; if (!hash) hashesByQuery.set(query, (hash = getHashPromise(query))); diff --git a/src/link/remove-typename/removeTypenameFromVariables.ts b/src/link/remove-typename/removeTypenameFromVariables.ts index b8c173b15d2..b713a5b4138 100644 --- a/src/link/remove-typename/removeTypenameFromVariables.ts +++ b/src/link/remove-typename/removeTypenameFromVariables.ts @@ -2,8 +2,14 @@ import { wrap } from "optimism"; import type { DocumentNode, TypeNode } from "graphql"; import { Kind, visit } from "graphql"; import { ApolloLink } from "../core/index.js"; -import { stripTypename, isPlainObject } from "../../utilities/index.js"; +import { + stripTypename, + isPlainObject, + cacheSizes, + defaultCacheSizes, +} from "../../utilities/index.js"; import type { OperationVariables } from "../../core/index.js"; +import { WeakCache } from "@wry/caches"; export const KEEP = "__KEEP"; @@ -95,17 +101,25 @@ function maybeStripTypename( return value; } -const getVariableDefinitions = wrap((document: DocumentNode) => { - const definitions: Record = {}; +const getVariableDefinitions = wrap( + (document: DocumentNode) => { + const definitions: Record = {}; - visit(document, { - VariableDefinition(node) { - definitions[node.variable.name.value] = unwrapType(node.type); - }, - }); + visit(document, { + VariableDefinition(node) { + definitions[node.variable.name.value] = unwrapType(node.type); + }, + }); - return definitions; -}); + return definitions; + }, + { + max: + cacheSizes["removeTypenameFromVariables.getVariableDefinitions"] || + defaultCacheSizes["removeTypenameFromVariables.getVariableDefinitions"], + cache: WeakCache, + } +); function unwrapType(node: TypeNode): string { switch (node.kind) { diff --git a/src/react/parser/index.ts b/src/react/parser/index.ts index 3c5dc3abc8d..5cca2c066f8 100644 --- a/src/react/parser/index.ts +++ b/src/react/parser/index.ts @@ -1,4 +1,3 @@ -import { WeakCache } from "@wry/caches"; import { invariant } from "../../utilities/globals/index.js"; import type { @@ -7,6 +6,11 @@ import type { VariableDefinitionNode, OperationDefinitionNode, } from "graphql"; +import { + AutoCleanedWeakCache, + cacheSizes, + defaultCacheSizes, +} from "../../utilities/index.js"; export enum DocumentType { Query, @@ -22,7 +26,7 @@ export interface IDocumentDefinition { let cache: | undefined - | WeakCache< + | AutoCleanedWeakCache< DocumentNode, { name: string; @@ -50,8 +54,9 @@ export function operationName(type: DocumentType) { // This parser is mostly used to safety check incoming documents. export function parser(document: DocumentNode): IDocumentDefinition { if (!cache) { - cache = - new WeakCache(/** TODO: decide on a maximum size (will do all max sizes in a combined separate PR) */); + cache = new AutoCleanedWeakCache( + cacheSizes.parser || defaultCacheSizes.parser + ); } const cached = cache.get(document); if (cached) return cached; diff --git a/src/utilities/caching/__tests__/sizes.test.ts b/src/utilities/caching/__tests__/sizes.test.ts new file mode 100644 index 00000000000..8339c8950f3 --- /dev/null +++ b/src/utilities/caching/__tests__/sizes.test.ts @@ -0,0 +1,11 @@ +import { expectTypeOf } from "expect-type"; +import type { CacheSizes, defaultCacheSizes } from "../sizes"; + +test.skip("type tests", () => { + expectTypeOf().toMatchTypeOf< + keyof typeof defaultCacheSizes + >(); + expectTypeOf().toMatchTypeOf< + keyof CacheSizes + >(); +}); diff --git a/src/utilities/caching/caches.ts b/src/utilities/caching/caches.ts new file mode 100644 index 00000000000..6acbdb88d34 --- /dev/null +++ b/src/utilities/caching/caches.ts @@ -0,0 +1,80 @@ +import type { CommonCache } from "@wry/caches"; +import { WeakCache, StrongCache } from "@wry/caches"; + +const scheduledCleanup = new WeakSet>(); +function schedule(cache: CommonCache) { + if (!scheduledCleanup.has(cache)) { + scheduledCleanup.add(cache); + setTimeout(() => { + cache.clean(); + scheduledCleanup.delete(cache); + }, 100); + } +} +/** + * @internal + * A version of WeakCache that will auto-schedule a cleanup of the cache when + * a new item is added. + * Throttled to once per 100ms. + * + * @privateRemarks + * Should be used throughout the rest of the codebase instead of WeakCache, + * with the notable exception of usage in `wrap` from `optimism` - that one + * already handles cleanup and should remain a `WeakCache`. + */ +export const AutoCleanedWeakCache = function ( + max?: number | undefined, + dispose?: ((value: any, key: any) => void) | undefined +) { + /* + Some builds of `WeakCache` are function prototypes, some are classes. + This library still builds with an ES5 target, so we can't extend the + real classes. + Instead, we have to use this workaround until we switch to a newer build + target. + */ + const cache = new WeakCache(max, dispose); + cache.set = function (key: any, value: any) { + schedule(this); + return WeakCache.prototype.set.call(this, key, value); + }; + return cache; +} as any as typeof WeakCache; +/** + * @internal + */ +export type AutoCleanedWeakCache = WeakCache; + +/** + * @internal + * A version of StrongCache that will auto-schedule a cleanup of the cache when + * a new item is added. + * Throttled to once per 100ms. + * + * @privateRemarks + * Should be used throughout the rest of the codebase instead of StrongCache, + * with the notable exception of usage in `wrap` from `optimism` - that one + * already handles cleanup and should remain a `StrongCache`. + */ +export const AutoCleanedStrongCache = function ( + max?: number | undefined, + dispose?: ((value: any, key: any) => void) | undefined +) { + /* + Some builds of `StrongCache` are function prototypes, some are classes. + This library still builds with an ES5 target, so we can't extend the + real classes. + Instead, we have to use this workaround until we switch to a newer build + target. + */ + const cache = new StrongCache(max, dispose); + cache.set = function (key: any, value: any) { + schedule(this); + return StrongCache.prototype.set.call(this, key, value); + }; + return cache; +} as any as typeof StrongCache; +/** + * @internal + */ +export type AutoCleanedStrongCache = StrongCache; diff --git a/src/utilities/caching/index.ts b/src/utilities/caching/index.ts new file mode 100644 index 00000000000..159dc27fcfd --- /dev/null +++ b/src/utilities/caching/index.ts @@ -0,0 +1,3 @@ +export { AutoCleanedStrongCache, AutoCleanedWeakCache } from "./caches.js"; +export type { CacheSizes } from "./sizes.js"; +export { cacheSizes, defaultCacheSizes } from "./sizes.js"; diff --git a/src/utilities/caching/sizes.ts b/src/utilities/caching/sizes.ts new file mode 100644 index 00000000000..998537740a3 --- /dev/null +++ b/src/utilities/caching/sizes.ts @@ -0,0 +1,264 @@ +import { global } from "../globals/index.js"; + +declare global { + interface Window { + [cacheSizeSymbol]?: Partial; + } +} + +/** + * The cache sizes used by various Apollo Client caches. + * + * Note that these caches are all derivative and if an item is cache-collected, + * it's not the end of the world - the cached item will just be recalculated. + * + * As a result, these cache sizes should not be chosen to hold every value ever + * encountered, but rather to hold a reasonable number of values that can be + * assumed to be on the screen at any given time. + * + * We assume a "base value" of 1000 here, which is already very generous. + * In most applications, it will be very unlikely that 1000 different queries + * are on screen at the same time. + */ +export interface CacheSizes { + /** + * Cache size for the [`print`](../../utilities/graphql/print.ts) function. + * + * @defaultValue + * Defaults to `2000`. + * + * @remarks + * This method is called from the `QueryManager` and various `Link`s, + * always with the "serverQuery", so the server-facing part of a transformed + * DocumentNode. + */ + print: number; + /** + * Cache size for the [`parser`](../../react/parser/index.ts) function. + * + * @defaultValue + * Defaults to `1000`. + * + * @remarks + * This function is used directly in HOCs, and nowadays mainly accessed by + * calling `verifyDocumentType` from various hooks. + * It is called with a user-provided DocumentNode. + */ + parser: number; + /** + * Cache size for the `performWork` method of each [`DocumentTransform`](../../utilities/graphql/DocumentTransform.ts). + * + * @defaultValue + * Defaults to `2000`. + * + * @remarks + * This method is called from `transformDocument`, which is called from + * `QueryManager` with a user-provided DocumentNode. + * It is also called with already-transformed DocumentNodes, assuming the + * user provided additional transforms. + * + * The cache size here should be chosen with other DocumentTransforms in mind. + * For example, if there was a DocumentTransform that would take `n` DocumentNodes, + * and returned a differently-transformed DocumentNode depending if the app is + * online or offline, then we assume that the cache returns `2*n` documents. + * + * No user-provided DocumentNode will actually be "the last one", as we run the + * `defaultDocumentTransform` before *and* after the user-provided transforms. + * + * So if we assume that the user-provided transforms receive `n` documents and + * return `n` documents, the cache size should be `2*n`. + * + * If we assume that the user-provided transforms receive `n` documents and + * returns `2*n` documents, the cache size should be `3*n`. + * + * This size should also then be used in every other cache that mentions that + * it operates on a "transformed" DocumentNode. + */ + "documentTransform.cache": number; + /** + * Cache size for the `transformCache` used in the `getDocumentInfo` method of + * [`QueryManager`](../../core/QueryManager.ts). + * + * @defaultValue + * Defaults to `2000`. + * + * @remarks + * `getDocumentInfo` is called throughout the `QueryManager` with transformed + * DocumentNodes. + */ + "queryManager.getDocumentInfo": number; + /** + * Cache size for the `hashesByQuery` cache in the [`PersistedQueryLink`](../../link/persisted-queries/index.ts). + * + * @defaultValue + * Defaults to `2000`. + * + * @remarks + * This cache is used to cache the hashes of persisted queries. It is working with + * transformed DocumentNodes. + */ + "PersistedQueryLink.persistedQueryHashes": number; + /** + * Cache for the `sortingMap` used by [`canonicalStringify`](../../utilities/common/canonicalStringify.ts). + * + * @defaultValue + * Defaults to `1000`. + * + * @remarks + * This cache contains the sorted keys of objects that are stringified by + * `canonicalStringify`. + * It uses the stringified unsorted keys of objects as keys. + * The cache will not grow beyond the size of different object **shapes** + * encountered in an application, no matter how much actual data gets stringified. + */ + canonicalStringify: number; + /** + * Cache size for the `transform` method of [`FragmentRegistry`](../../cache/inmemory/fragmentRegistry.ts). + * + * @defaultValue + * Defaults to `2000`. + * + * @remarks + * This function is called as part of the `defaultDocumentTransform` which will be called with + * user-provided and already-transformed DocumentNodes. + * + */ + "fragmentRegistry.transform": number; + /** + * Cache size for the `lookup` method of [`FragmentRegistry`](../../cache/inmemory/fragmentRegistry.ts). + * + * @defaultValue + * Defaults to `1000`. + * + * @remarks + * This function is called with fragment names in the form of a string. + * + * Note: + * This function is a dependency of `transform`, so having a too small cache size here + * might involuntarily invalidate values in the `transform` cache. + */ + "fragmentRegistry.lookup": number; + /** + * Cache size for the `findFragmentSpreads` method of [`FragmentRegistry`](../../cache/inmemory/fragmentRegistry.ts). + * + * @defaultValue + * Defaults to `4000`. + * + * @remarks + * This function is called with transformed DocumentNodes, as well as recursively + * with every fragment spread referenced within that, or a fragment referenced by a + * fragment spread. + * + * Note: + * This function is a dependency of `transform`, so having a too small cache size here + * might involuntarily invalidate values in the `transform` cache. + */ + "fragmentRegistry.findFragmentSpreads": number; + /** + * Cache size for the `getFragmentDoc` method of [`ApolloCache`](../../cache/core/cache.ts). + * + * @defaultValue + * Defaults to `1000`. + * + * @remarks + * This function is called from `readFragment` with user-provided fragment definitions. + */ + "cache.fragmentQueryDocuments": number; + /** + * Cache size for the `getVariableDefinitions` function in [`removeTypenameFromVariables`](../../link/remove-typename/removeTypenameFromVariables.ts). + * + * @defaultValue + * Defaults to `2000`. + * + * @remarks + * This function is called in a link with transformed DocumentNodes. + */ + "removeTypenameFromVariables.getVariableDefinitions": number; + /** + * Cache size for the `maybeBroadcastWatch` method on [`InMemoryCache`](../../cache/inmemory/inMemoryCache.ts). + * + * `maybeBroadcastWatch` will be set to the `resultCacheMaxSize` option and + * will fall back to this configuration value if the option is not set. + * + * @defaultValue + * Defaults to `5000`. + * + * @remarks + * This method is used for dependency tracking in the `InMemoryCache` and + * prevents from unnecessary re-renders. + * It is recommended to keep this value significantly higher than the number of + * possible subscribers you will have active at the same time in your application + * at any time. + */ + "inMemoryCache.maybeBroadcastWatch": number; + /** + * Cache size for the `executeSelectionSet` method on [`StoreReader`](../../cache/inmemory/readFromStore.ts). + * + * `executeSelectionSet` will be set to the `resultCacheMaxSize` option and + * will fall back to this configuration value if the option is not set. + * + * @defaultValue + * Defaults to `10000`. + * + * @remarks + * Every object that is read from the cache will be cached here, so it is + * recommended to set this to a high value. + */ + "inMemoryCache.executeSelectionSet": number; + /** + * Cache size for the `executeSubSelectedArray` method on [`StoreReader`](../../cache/inmemory/readFromStore.ts). + * + * `executeSubSelectedArray` will be set to the `resultCacheMaxSize` option and + * will fall back to this configuration value if the option is not set. + * + * @defaultValue + * Defaults to `5000`. + * + * @remarks + * Every array that is read from the cache will be cached here, so it is + * recommended to set this to a high value. + */ + "inMemoryCache.executeSubSelectedArray": number; +} + +const cacheSizeSymbol = Symbol.for("apollo.cacheSize"); +/** + * + * The global cache size configuration for Apollo Client. + * + * @remarks + * + * You can directly modify this object, but any modification will + * only have an effect on caches that are created after the modification. + * + * So for global caches, such as `parser`, `canonicalStringify` and `print`, + * you might need to call `.reset` on them, which will essentially re-create them. + * + * Alternatively, you can set `globalThis[Symbol.for("apollo.cacheSize")]` before + * you load the Apollo Client package: + * + * @example + * ```ts + * globalThis[Symbol.for("apollo.cacheSize")] = { + * parser: 100 + * } satisfies Partial // the `satisfies` is optional if using TypeScript + * ``` + */ +export const cacheSizes: Partial = { ...global[cacheSizeSymbol] }; + +export const enum defaultCacheSizes { + parser = 1000, + canonicalStringify = 1000, + print = 2000, + "documentTransform.cache" = 2000, + "queryManager.getDocumentInfo" = 2000, + "PersistedQueryLink.persistedQueryHashes" = 2000, + "fragmentRegistry.transform" = 2000, + "fragmentRegistry.lookup" = 1000, + "fragmentRegistry.findFragmentSpreads" = 4000, + "cache.fragmentQueryDocuments" = 1000, + "removeTypenameFromVariables.getVariableDefinitions" = 2000, + "inMemoryCache.maybeBroadcastWatch" = 5000, + "inMemoryCache.executeSelectionSet" = 10000, + "inMemoryCache.executeSubSelectedArray" = 5000, +} diff --git a/src/utilities/common/canonicalStringify.ts b/src/utilities/common/canonicalStringify.ts index 021f23430e2..7c037b0a680 100644 --- a/src/utilities/common/canonicalStringify.ts +++ b/src/utilities/common/canonicalStringify.ts @@ -1,3 +1,9 @@ +import { + AutoCleanedStrongCache, + cacheSizes, + defaultCacheSizes, +} from "../../utilities/caching/index.js"; + /** * Like JSON.stringify, but with object keys always sorted in the same order. * @@ -24,14 +30,17 @@ export const canonicalStringify = Object.assign( // Clearing the sortingMap will reclaim all cached memory, without // affecting the logical results of canonicalStringify, but potentially // sacrificing performance until the cache is refilled. - sortingMap.clear(); + sortingMap = new AutoCleanedStrongCache( + cacheSizes.canonicalStringify || defaultCacheSizes.canonicalStringify + ); }, } ); // Values are JSON-serialized arrays of object keys (in any order), and values // are sorted arrays of the same keys. -const sortingMap = new Map(); +let sortingMap!: AutoCleanedStrongCache; +canonicalStringify.reset(); // The JSON.stringify function takes an optional second argument called a // replacer function. This function is called for each key-value pair in the diff --git a/src/utilities/graphql/DocumentTransform.ts b/src/utilities/graphql/DocumentTransform.ts index 7a5ce40fe4c..bf016aee0da 100644 --- a/src/utilities/graphql/DocumentTransform.ts +++ b/src/utilities/graphql/DocumentTransform.ts @@ -5,6 +5,7 @@ import { invariant } from "../globals/index.js"; import type { DocumentNode } from "graphql"; import { WeakCache } from "@wry/caches"; import { wrap } from "optimism"; +import { cacheSizes } from "../caching/index.js"; export type DocumentTransformCacheKey = ReadonlyArray; @@ -96,7 +97,7 @@ export class DocumentTransform { return stableCacheKeys.lookupArray(cacheKeys); } }, - max: 1000 /** TODO: decide on a maximum size (will do all max sizes in a combined separate PR) */, + max: cacheSizes["documentTransform.cache"], cache: WeakCache, } ); diff --git a/src/utilities/graphql/print.ts b/src/utilities/graphql/print.ts index 3ba1134c968..e32a3f048df 100644 --- a/src/utilities/graphql/print.ts +++ b/src/utilities/graphql/print.ts @@ -1,8 +1,12 @@ import type { ASTNode } from "graphql"; import { print as origPrint } from "graphql"; -import { WeakCache } from "@wry/caches"; +import { + AutoCleanedWeakCache, + cacheSizes, + defaultCacheSizes, +} from "../caching/index.js"; -let printCache!: WeakCache; +let printCache!: AutoCleanedWeakCache; export const print = Object.assign( (ast: ASTNode) => { let result = printCache.get(ast); @@ -15,10 +19,9 @@ export const print = Object.assign( }, { reset() { - printCache = new WeakCache< - ASTNode, - string - >(/** TODO: decide on a maximum size (will do all max sizes in a combined separate PR) */); + printCache = new AutoCleanedWeakCache( + cacheSizes.print || defaultCacheSizes.print + ); }, } ); diff --git a/src/utilities/index.ts b/src/utilities/index.ts index 35c1ec45cad..637ae100af7 100644 --- a/src/utilities/index.ts +++ b/src/utilities/index.ts @@ -131,3 +131,11 @@ export * from "./types/IsStrictlyAny.js"; export type { DeepOmit } from "./types/DeepOmit.js"; export type { DeepPartial } from "./types/DeepPartial.js"; export type { OnlyRequiredProperties } from "./types/OnlyRequiredProperties.js"; + +export { + AutoCleanedStrongCache, + AutoCleanedWeakCache, + cacheSizes, + defaultCacheSizes, +} from "./caching/index.js"; +export type { CacheSizes } from "./caching/index.js";