diff --git a/.changeset/small-tomatoes-explode.md b/.changeset/small-tomatoes-explode.md new file mode 100644 index 00000000000..2f5c39228c3 --- /dev/null +++ b/.changeset/small-tomatoes-explode.md @@ -0,0 +1,5 @@ +--- +'@apollo/client': minor +--- + +Allow `ApolloCache` implementations to specify default value for `assumeImmutableResults` client option, improving performance for applications currently using `InMemoryCache` without configuring `new ApolloClient({ assumeImmutableResults: true })` diff --git a/src/cache/core/cache.ts b/src/cache/core/cache.ts index b87f9da95eb..1941c204de0 100644 --- a/src/cache/core/cache.ts +++ b/src/cache/core/cache.ts @@ -12,6 +12,8 @@ import { Cache } from './types/Cache'; export type Transaction = (c: ApolloCache) => void; export abstract class ApolloCache implements DataProxy { + public readonly assumeImmutableResults: boolean = false; + // required to implement // core API public abstract read( diff --git a/src/cache/inmemory/inMemoryCache.ts b/src/cache/inmemory/inMemoryCache.ts index 9dd57fab005..3ff418ffbb9 100644 --- a/src/cache/inmemory/inMemoryCache.ts +++ b/src/cache/inmemory/inMemoryCache.ts @@ -49,6 +49,10 @@ export class InMemoryCache extends ApolloCache { any, [Cache.WatchOptions]>; + // Override the default value, since InMemoryCache result objects are frozen + // in development and expected to remain logically immutable in production. + public readonly assumeImmutableResults = true; + // Dynamically imported code can augment existing typePolicies or // possibleTypes by calling cache.policies.addTypePolicies or // cache.policies.addPossibletypes. diff --git a/src/core/ApolloClient.ts b/src/core/ApolloClient.ts index 5dea6d1a86f..961d53d8cc0 100644 --- a/src/core/ApolloClient.ts +++ b/src/core/ApolloClient.ts @@ -127,6 +127,14 @@ export class ApolloClient implements DataProxy { * you are using. */ constructor(options: ApolloClientOptions) { + if (!options.cache) { + throw new InvariantError( + "To initialize Apollo Client, you must specify a 'cache' property " + + "in the options object. \n" + + "For more information, please visit: https://go.apollo.dev/c/docs" + ); + } + const { uri, credentials, @@ -143,7 +151,7 @@ export class ApolloClient implements DataProxy { __DEV__, queryDeduplication = true, defaultOptions, - assumeImmutableResults = false, + assumeImmutableResults = cache.assumeImmutableResults, resolvers, typeDefs, fragmentMatcher, @@ -159,14 +167,6 @@ export class ApolloClient implements DataProxy { : ApolloLink.empty(); } - if (!cache) { - throw new InvariantError( - "To initialize Apollo Client, you must specify a 'cache' property " + - "in the options object. \n" + - "For more information, please visit: https://go.apollo.dev/c/docs" - ); - } - this.link = link; this.cache = cache; this.disableNetworkFetches = ssrMode || ssrForceFetchDelay > 0; diff --git a/src/core/QueryInfo.ts b/src/core/QueryInfo.ts index 0288f2d598c..1e95fc3849a 100644 --- a/src/core/QueryInfo.ts +++ b/src/core/QueryInfo.ts @@ -6,7 +6,7 @@ import { DeepMerger } from "../utilities" import { mergeIncrementalData } from '../utilities'; import { WatchQueryOptions, ErrorPolicy } from './watchQueryOptions'; import { ObservableQuery, reobserveCacheFirst } from './ObservableQuery'; -import { QueryListener } from './types'; +import { QueryListener, MethodKeys } from './types'; import { FetchResult } from '../link/core'; import { ObservableSubscription, @@ -40,7 +40,7 @@ const destructiveMethodCounts = new ( function wrapDestructiveCacheMethod( cache: ApolloCache, - methodName: keyof ApolloCache, + methodName: MethodKeys>, ) { const original = cache[methodName]; if (typeof original === "function") { diff --git a/src/core/QueryManager.ts b/src/core/QueryManager.ts index b7d80957268..def2c0a15c0 100644 --- a/src/core/QueryManager.ts +++ b/src/core/QueryManager.ts @@ -119,7 +119,7 @@ export class QueryManager { ssrMode = false, clientAwareness = {}, localState, - assumeImmutableResults, + assumeImmutableResults = !!cache.assumeImmutableResults, }: { cache: ApolloCache; link: ApolloLink; @@ -138,7 +138,7 @@ export class QueryManager { this.clientAwareness = clientAwareness; this.localState = localState || new LocalState({ cache }); this.ssrMode = ssrMode; - this.assumeImmutableResults = !!assumeImmutableResults; + this.assumeImmutableResults = assumeImmutableResults; if ((this.onBroadcast = onBroadcast)) { this.mutationStore = Object.create(null); } diff --git a/src/core/types.ts b/src/core/types.ts index 4bdf3df8aaf..7888c5df553 100644 --- a/src/core/types.ts +++ b/src/core/types.ts @@ -13,6 +13,10 @@ import { IsStrictlyAny } from '../utilities'; export { TypedDocumentNode } from '@graphql-typed-document-node/core'; +export type MethodKeys = { + [P in keyof T]: T[P] extends Function ? P : never +}[keyof T]; + export interface DefaultContext extends Record {}; export type QueryListener = (queryInfo: QueryInfo) => void;