Skip to content

Commit

Permalink
Decouple canonicalStringify from ObjectCanon (#11254)
Browse files Browse the repository at this point in the history
Co-authored-by: Lenz Weber-Tronic <lorenz.weber-tronic@apollographql.com>
  • Loading branch information
benjamn and phryneas committed Oct 5, 2023
1 parent 33e0d78 commit d08970d
Show file tree
Hide file tree
Showing 18 changed files with 256 additions and 98 deletions.
12 changes: 4 additions & 8 deletions .api-reports/api-report-cache.md
Expand Up @@ -192,7 +192,7 @@ export const cacheSlot: {

// @public (undocumented)
export const canonicalStringify: ((value: any) => string) & {
reset: typeof resetCanonicalStringify;
reset(): void;
};

// @public (undocumented)
Expand Down Expand Up @@ -858,9 +858,6 @@ export interface Reference {
readonly __ref: string;
}

// @public (undocumented)
function resetCanonicalStringify(): void;

// @public (undocumented)
type SafeReadonly<T> = T extends object ? Readonly<T> : T;

Expand Down Expand Up @@ -947,10 +944,9 @@ interface WriteContext extends ReadMergeModifyContext {

// Warnings were encountered during analysis:
//
// src/cache/inmemory/object-canon.ts:203:32 - (ae-forgotten-export) The symbol "resetCanonicalStringify" needs to be exported by the entry point index.d.ts
// src/cache/inmemory/policies.ts:98:3 - (ae-forgotten-export) The symbol "FragmentMap" needs to be exported by the entry point index.d.ts
// src/cache/inmemory/policies.ts:167:3 - (ae-forgotten-export) The symbol "KeySpecifier" needs to be exported by the entry point index.d.ts
// src/cache/inmemory/policies.ts:167:3 - (ae-forgotten-export) The symbol "KeyArgsFunction" needs to be exported by the entry point index.d.ts
// src/cache/inmemory/policies.ts:92:3 - (ae-forgotten-export) The symbol "FragmentMap" needs to be exported by the entry point index.d.ts
// src/cache/inmemory/policies.ts:161:3 - (ae-forgotten-export) The symbol "KeySpecifier" needs to be exported by the entry point index.d.ts
// src/cache/inmemory/policies.ts:161:3 - (ae-forgotten-export) The symbol "KeyArgsFunction" needs to be exported by the entry point index.d.ts
// src/cache/inmemory/types.ts:126: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
6 changes: 3 additions & 3 deletions .api-reports/api-report-core.md
Expand Up @@ -2179,9 +2179,9 @@ interface WriteContext extends ReadMergeModifyContext {

// Warnings were encountered during analysis:
//
// src/cache/inmemory/policies.ts:98:3 - (ae-forgotten-export) The symbol "FragmentMap" needs to be exported by the entry point index.d.ts
// src/cache/inmemory/policies.ts:167:3 - (ae-forgotten-export) The symbol "KeySpecifier" needs to be exported by the entry point index.d.ts
// src/cache/inmemory/policies.ts:167:3 - (ae-forgotten-export) The symbol "KeyArgsFunction" needs to be exported by the entry point index.d.ts
// src/cache/inmemory/policies.ts:92:3 - (ae-forgotten-export) The symbol "FragmentMap" needs to be exported by the entry point index.d.ts
// src/cache/inmemory/policies.ts:161:3 - (ae-forgotten-export) The symbol "KeySpecifier" needs to be exported by the entry point index.d.ts
// src/cache/inmemory/policies.ts:161:3 - (ae-forgotten-export) The symbol "KeyArgsFunction" needs to be exported by the entry point index.d.ts
// src/cache/inmemory/types.ts:126:3 - (ae-forgotten-export) The symbol "KeyFieldsFunction" needs to be exported by the entry point index.d.ts
// src/core/ObservableQuery.ts:112:5 - (ae-forgotten-export) The symbol "QueryManager" needs to be exported by the entry point index.d.ts
// src/core/ObservableQuery.ts:113:5 - (ae-forgotten-export) The symbol "QueryInfo" needs to be exported by the entry point index.d.ts
Expand Down
25 changes: 15 additions & 10 deletions .api-reports/api-report-utilities.md
Expand Up @@ -462,6 +462,11 @@ const enum CacheWriteBehavior {
OVERWRITE = 1
}

// @public (undocumented)
export const canonicalStringify: ((value: any) => string) & {
reset(): void;
};

// @public (undocumented)
type CanReadFunction = (value: StoreValue) => boolean;

Expand Down Expand Up @@ -1073,7 +1078,7 @@ export function getQueryDefinition(doc: DocumentNode): OperationDefinitionNode;

// @public (undocumented)
export const getStoreKeyName: ((fieldName: string, args?: Record<string, any> | null, directives?: Directives) => string) & {
setStringify(s: typeof stringify): (value: any) => string;
setStringify(s: typeof storeKeyNameStringify): (value: any) => string;
};

// @public (undocumented)
Expand Down Expand Up @@ -2284,6 +2289,9 @@ type StorageType = Record<string, any>;
// @public (undocumented)
export function storeKeyNameFromField(field: FieldNode, variables?: Object): string;

// @public (undocumented)
let storeKeyNameStringify: (value: any) => string;

// @public (undocumented)
export interface StoreObject {
// (undocumented)
Expand All @@ -2298,9 +2306,6 @@ type StoreObjectValueMaybeReference<StoreVal> = StoreVal extends Record<string,
// @public (undocumented)
export type StoreValue = number | string | string[] | Reference | Reference[] | null | undefined | void | Object;

// @public (undocumented)
let stringify: (value: any) => string;

// @public (undocumented)
export function stringifyForDisplay(value: any, space?: number): string;

Expand Down Expand Up @@ -2498,11 +2503,11 @@ interface WriteContext extends ReadMergeModifyContext {
// Warnings were encountered during analysis:
//
// src/cache/core/types/DataProxy.ts:141:5 - (ae-forgotten-export) The symbol "MissingFieldError" needs to be exported by the entry point index.d.ts
// src/cache/inmemory/policies.ts:63:3 - (ae-forgotten-export) The symbol "TypePolicy" needs to be exported by the entry point index.d.ts
// src/cache/inmemory/policies.ts:167:3 - (ae-forgotten-export) The symbol "KeySpecifier" needs to be exported by the entry point index.d.ts
// src/cache/inmemory/policies.ts:167:3 - (ae-forgotten-export) The symbol "KeyArgsFunction" needs to be exported by the entry point index.d.ts
// src/cache/inmemory/policies.ts:168:3 - (ae-forgotten-export) The symbol "FieldReadFunction" needs to be exported by the entry point index.d.ts
// src/cache/inmemory/policies.ts:169:3 - (ae-forgotten-export) The symbol "FieldMergeFunction" needs to be exported by the entry point index.d.ts
// src/cache/inmemory/policies.ts:57:3 - (ae-forgotten-export) The symbol "TypePolicy" needs to be exported by the entry point index.d.ts
// src/cache/inmemory/policies.ts:161:3 - (ae-forgotten-export) The symbol "KeySpecifier" needs to be exported by the entry point index.d.ts
// src/cache/inmemory/policies.ts:161:3 - (ae-forgotten-export) The symbol "KeyArgsFunction" needs to be exported by the entry point index.d.ts
// src/cache/inmemory/policies.ts:162:3 - (ae-forgotten-export) The symbol "FieldReadFunction" needs to be exported by the entry point index.d.ts
// src/cache/inmemory/policies.ts:163:3 - (ae-forgotten-export) The symbol "FieldMergeFunction" needs to be exported by the entry point index.d.ts
// src/cache/inmemory/types.ts:126:3 - (ae-forgotten-export) The symbol "KeyFieldsFunction" needs to be exported by the entry point index.d.ts
// src/cache/inmemory/writeToStore.ts:65:7 - (ae-forgotten-export) The symbol "MergeTree" needs to be exported by the entry point index.d.ts
// src/core/ApolloClient.ts:47:3 - (ae-forgotten-export) The symbol "UriFunction" needs to be exported by the entry point index.d.ts
Expand All @@ -2517,7 +2522,7 @@ interface WriteContext extends ReadMergeModifyContext {
// src/core/types.ts:178:3 - (ae-forgotten-export) The symbol "MutationQueryReducer" needs to be exported by the entry point index.d.ts
// src/core/types.ts:205:5 - (ae-forgotten-export) The symbol "Resolver" needs to be exported by the entry point index.d.ts
// src/core/watchQueryOptions.ts:191:3 - (ae-forgotten-export) The symbol "UpdateQueryFn" needs to be exported by the entry point index.d.ts
// src/utilities/graphql/storeUtils.ts:202:12 - (ae-forgotten-export) The symbol "stringify" needs to be exported by the entry point index.d.ts
// src/utilities/graphql/storeUtils.ts:208:12 - (ae-forgotten-export) The symbol "storeKeyNameStringify" needs to be exported by the entry point index.d.ts
// src/utilities/policies/pagination.ts:76:3 - (ae-forgotten-export) The symbol "TRelayEdge" needs to be exported by the entry point index.d.ts
// src/utilities/policies/pagination.ts:77:3 - (ae-forgotten-export) The symbol "TRelayPageInfo" needs to be exported by the entry point index.d.ts

Expand Down
6 changes: 3 additions & 3 deletions .api-reports/api-report.md
Expand Up @@ -2858,9 +2858,9 @@ interface WriteContext extends ReadMergeModifyContext {

// Warnings were encountered during analysis:
//
// src/cache/inmemory/policies.ts:98:3 - (ae-forgotten-export) The symbol "FragmentMap" needs to be exported by the entry point index.d.ts
// src/cache/inmemory/policies.ts:167:3 - (ae-forgotten-export) The symbol "KeySpecifier" needs to be exported by the entry point index.d.ts
// src/cache/inmemory/policies.ts:167:3 - (ae-forgotten-export) The symbol "KeyArgsFunction" needs to be exported by the entry point index.d.ts
// src/cache/inmemory/policies.ts:92:3 - (ae-forgotten-export) The symbol "FragmentMap" needs to be exported by the entry point index.d.ts
// src/cache/inmemory/policies.ts:161:3 - (ae-forgotten-export) The symbol "KeySpecifier" needs to be exported by the entry point index.d.ts
// src/cache/inmemory/policies.ts:161:3 - (ae-forgotten-export) The symbol "KeyArgsFunction" needs to be exported by the entry point index.d.ts
// src/cache/inmemory/types.ts:126:3 - (ae-forgotten-export) The symbol "KeyFieldsFunction" needs to be exported by the entry point index.d.ts
// src/core/ObservableQuery.ts:112:5 - (ae-forgotten-export) The symbol "QueryManager" needs to be exported by the entry point index.d.ts
// src/core/ObservableQuery.ts:113:5 - (ae-forgotten-export) The symbol "QueryInfo" needs to be exported by the entry point index.d.ts
Expand Down
5 changes: 5 additions & 0 deletions .changeset/beige-geese-wink.md
@@ -0,0 +1,5 @@
---
"@apollo/client": patch
---

Decouple `canonicalStringify` from `ObjectCanon` for better time and memory performance.
4 changes: 2 additions & 2 deletions .size-limit.cjs
@@ -1,7 +1,7 @@
const checks = [
{
path: "dist/apollo-client.min.cjs",
limit: "38000",
limit: "38049",
},
{
path: "dist/main.cjs",
Expand All @@ -10,7 +10,7 @@ const checks = [
{
path: "dist/index.js",
import: "{ ApolloClient, InMemoryCache, HttpLink }",
limit: "32052",
limit: "32082",
},
...[
"ApolloProvider",
Expand Down
1 change: 1 addition & 0 deletions src/__tests__/__snapshots__/exports.ts.snap
Expand Up @@ -395,6 +395,7 @@ Array [
"canUseSymbol",
"canUseWeakMap",
"canUseWeakSet",
"canonicalStringify",
"checkDocument",
"cloneDeep",
"compact",
Expand Down
8 changes: 5 additions & 3 deletions src/cache/index.ts
Expand Up @@ -14,7 +14,11 @@ export type {
export { MissingFieldError } from "./core/types/common.js";

export type { Reference } from "../utilities/index.js";
export { isReference, makeReference } from "../utilities/index.js";
export {
isReference,
makeReference,
canonicalStringify,
} from "../utilities/index.js";

export { EntityStore } from "./inmemory/entityStore.js";
export {
Expand All @@ -38,8 +42,6 @@ export type {
} from "./inmemory/policies.js";
export { Policies } from "./inmemory/policies.js";

export { canonicalStringify } from "./inmemory/object-canon.js";

export type { FragmentRegistryAPI } from "./inmemory/fragmentRegistry.js";
export { createFragmentRegistry } from "./inmemory/fragmentRegistry.js";

Expand Down
2 changes: 1 addition & 1 deletion src/cache/inmemory/__tests__/key-extractor.ts
@@ -1,5 +1,5 @@
import { KeySpecifier } from "../policies";
import { canonicalStringify } from "../object-canon";
import { canonicalStringify } from "../../../utilities";
import {
getSpecifierPaths,
collectSpecifierPaths,
Expand Down
2 changes: 1 addition & 1 deletion src/cache/inmemory/inMemoryCache.ts
Expand Up @@ -16,6 +16,7 @@ import {
addTypenameToDocument,
isReference,
DocumentTransform,
canonicalStringify,
} from "../../utilities/index.js";
import type { InMemoryCacheConfig, NormalizedCacheObject } from "./types.js";
import { StoreReader } from "./readFromStore.js";
Expand All @@ -24,7 +25,6 @@ import { EntityStore, supportsResultCaching } from "./entityStore.js";
import { makeVar, forgetCache, recallCache } from "./reactiveVars.js";
import { Policies } from "./policies.js";
import { hasOwn, normalizeConfig, shouldCanonizeResults } from "./helpers.js";
import { canonicalStringify } from "./object-canon.js";
import type { OperationVariables } from "../../core/index.js";

type BroadcastOptions = Pick<
Expand Down
32 changes: 0 additions & 32 deletions src/cache/inmemory/object-canon.ts
Expand Up @@ -195,35 +195,3 @@ type SortedKeysInfo = {
sorted: string[];
json: string;
};

// Since the keys of canonical objects are always created in lexicographically
// sorted order, we can use the ObjectCanon to implement a fast and stable
// version of JSON.stringify, which automatically sorts object keys.
export const canonicalStringify = Object.assign(
function (value: any): string {
if (isObjectOrArray(value)) {
if (stringifyCanon === void 0) {
resetCanonicalStringify();
}
const canonical = stringifyCanon.admit(value);
let json = stringifyCache.get(canonical);
if (json === void 0) {
stringifyCache.set(canonical, (json = JSON.stringify(canonical)));
}
return json;
}
return JSON.stringify(value);
},
{
reset: resetCanonicalStringify,
}
);

// Can be reset by calling canonicalStringify.reset().
let stringifyCanon: ObjectCanon;
let stringifyCache: WeakMap<object, string>;

function resetCanonicalStringify() {
stringifyCanon = new ObjectCanon();
stringifyCache = new (canUseWeakMap ? WeakMap : Map)();
}
6 changes: 0 additions & 6 deletions src/cache/inmemory/policies.ts
Expand Up @@ -48,17 +48,11 @@ import type {
} from "../core/types/common.js";
import type { WriteContext } from "./writeToStore.js";

// Upgrade to a faster version of the default stable JSON.stringify function
// used by getStoreKeyName. This function is used when computing storeFieldName
// strings (when no keyArgs has been configured for a field).
import { canonicalStringify } from "./object-canon.js";
import {
keyArgsFnFromSpecifier,
keyFieldsFnFromSpecifier,
} from "./key-extractor.js";

getStoreKeyName.setStringify(canonicalStringify);

export type TypePolicies = {
[__typename: string]: TypePolicy;
};
Expand Down
3 changes: 2 additions & 1 deletion src/cache/inmemory/readFromStore.ts
Expand Up @@ -28,6 +28,7 @@ import {
isNonNullObject,
canUseWeakMap,
compact,
canonicalStringify,
} from "../../utilities/index.js";
import type { Cache } from "../core/types/Cache.js";
import type {
Expand All @@ -50,7 +51,7 @@ import type { Policies } from "./policies.js";
import type { InMemoryCache } from "./inMemoryCache.js";
import type { MissingTree } from "../core/types/common.js";
import { MissingFieldError } from "../core/types/common.js";
import { canonicalStringify, ObjectCanon } from "./object-canon.js";
import { ObjectCanon } from "./object-canon.js";

export type VariableMap = { [name: string]: any };

Expand Down
2 changes: 1 addition & 1 deletion src/cache/inmemory/writeToStore.ts
Expand Up @@ -25,6 +25,7 @@ import {
addTypenameToDocument,
isNonEmptyArray,
argumentsObjectFromField,
canonicalStringify,
} from "../../utilities/index.js";

import type {
Expand All @@ -44,7 +45,6 @@ import type { StoreReader } from "./readFromStore.js";
import type { InMemoryCache } from "./inMemoryCache.js";
import type { EntityStore } from "./entityStore.js";
import type { Cache } from "../../core/index.js";
import { canonicalStringify } from "./object-canon.js";
import { normalizeReadFieldOptions } from "./policies.js";
import type { ReadFieldFunction } from "../core/types/common.js";

Expand Down

0 comments on commit d08970d

Please sign in to comment.