From 911232ebaca084b91cc401cd266c2c266a2e7a6e Mon Sep 17 00:00:00 2001 From: Ben Newman Date: Wed, 27 Sep 2023 13:02:20 -0400 Subject: [PATCH] Keep getStoreKeyName.setStringify but use canonicalStringify by default. https://github.com/apollographql/apollo-client/pull/11254#discussion_r1338588020 --- .api-reports/api-report-cache.md | 6 +- .api-reports/api-report-core.md | 6 +- .api-reports/api-report-utilities.md | 22 ++++-- .api-reports/api-report.md | 6 +- .size-limit.cjs | 4 +- src/cache/inmemory/policies.ts | 3 + src/utilities/graphql/storeUtils.ts | 114 +++++++++++++++------------ 7 files changed, 95 insertions(+), 66 deletions(-) diff --git a/.api-reports/api-report-cache.md b/.api-reports/api-report-cache.md index 0af8cb6cff1..199193d4ca1 100644 --- a/.api-reports/api-report-cache.md +++ b/.api-reports/api-report-cache.md @@ -944,9 +944,9 @@ interface WriteContext extends ReadMergeModifyContext { // Warnings were encountered during analysis: // -// 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/policies.ts:95:3 - (ae-forgotten-export) The symbol "FragmentMap" needs to be exported by the entry point index.d.ts +// src/cache/inmemory/policies.ts:164:3 - (ae-forgotten-export) The symbol "KeySpecifier" needs to be exported by the entry point index.d.ts +// src/cache/inmemory/policies.ts:164: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) diff --git a/.api-reports/api-report-core.md b/.api-reports/api-report-core.md index a2acd522195..e51577fd590 100644 --- a/.api-reports/api-report-core.md +++ b/.api-reports/api-report-core.md @@ -2179,9 +2179,9 @@ interface WriteContext extends ReadMergeModifyContext { // Warnings were encountered during analysis: // -// 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/policies.ts:95:3 - (ae-forgotten-export) The symbol "FragmentMap" needs to be exported by the entry point index.d.ts +// src/cache/inmemory/policies.ts:164:3 - (ae-forgotten-export) The symbol "KeySpecifier" needs to be exported by the entry point index.d.ts +// src/cache/inmemory/policies.ts:164: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 diff --git a/.api-reports/api-report-utilities.md b/.api-reports/api-report-utilities.md index 2114304b3d6..9071633b420 100644 --- a/.api-reports/api-report-utilities.md +++ b/.api-reports/api-report-utilities.md @@ -1077,7 +1077,11 @@ export function getOperationName(doc: DocumentNode): string | null; export function getQueryDefinition(doc: DocumentNode): OperationDefinitionNode; // @public (undocumented) -export function getStoreKeyName(fieldName: string, args?: Record | null, directives?: Directives): string; +export const getStoreKeyName: ((fieldName: string, args?: Record | null, directives?: Directives) => string) & { + setStringify(s: typeof storeKeyNameStringify): ((value: any) => string) & { + reset(): void; + }; +}; // @public (undocumented) export function getTypenameFromResult(result: Record, selectionSet: SelectionSetNode, fragmentMap?: FragmentMap): string | undefined; @@ -2287,6 +2291,11 @@ type StorageType = Record; // @public (undocumented) export function storeKeyNameFromField(field: FieldNode, variables?: Object): string; +// @public (undocumented) +let storeKeyNameStringify: ((value: any) => string) & { + reset(): void; +}; + // @public (undocumented) export interface StoreObject { // (undocumented) @@ -2498,11 +2507,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: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/policies.ts:60:3 - (ae-forgotten-export) The symbol "TypePolicy" needs to be exported by the entry point index.d.ts +// src/cache/inmemory/policies.ts:164:3 - (ae-forgotten-export) The symbol "KeySpecifier" needs to be exported by the entry point index.d.ts +// src/cache/inmemory/policies.ts:164:3 - (ae-forgotten-export) The symbol "KeyArgsFunction" needs to be exported by the entry point index.d.ts +// src/cache/inmemory/policies.ts:165:3 - (ae-forgotten-export) The symbol "FieldReadFunction" needs to be exported by the entry point index.d.ts +// src/cache/inmemory/policies.ts:166: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 @@ -2517,6 +2526,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: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 diff --git a/.api-reports/api-report.md b/.api-reports/api-report.md index bbf1f3f2898..324100b59a1 100644 --- a/.api-reports/api-report.md +++ b/.api-reports/api-report.md @@ -2858,9 +2858,9 @@ interface WriteContext extends ReadMergeModifyContext { // Warnings were encountered during analysis: // -// 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/policies.ts:95:3 - (ae-forgotten-export) The symbol "FragmentMap" needs to be exported by the entry point index.d.ts +// src/cache/inmemory/policies.ts:164:3 - (ae-forgotten-export) The symbol "KeySpecifier" needs to be exported by the entry point index.d.ts +// src/cache/inmemory/policies.ts:164: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 diff --git a/.size-limit.cjs b/.size-limit.cjs index 286c4f7ba0d..db746e9af39 100644 --- a/.size-limit.cjs +++ b/.size-limit.cjs @@ -1,7 +1,7 @@ const checks = [ { path: "dist/apollo-client.min.cjs", - limit: "38060", + limit: "38090", }, { path: "dist/main.cjs", @@ -10,7 +10,7 @@ const checks = [ { path: "dist/index.js", import: "{ ApolloClient, InMemoryCache, HttpLink }", - limit: "32095", + limit: "32126", }, ...[ "ApolloProvider", diff --git a/src/cache/inmemory/policies.ts b/src/cache/inmemory/policies.ts index 6918c7dd6aa..d44c896cc83 100644 --- a/src/cache/inmemory/policies.ts +++ b/src/cache/inmemory/policies.ts @@ -20,6 +20,7 @@ import { getStoreKeyName, isNonNullObject, stringifyForDisplay, + canonicalStringify, } from "../../utilities/index.js"; import type { IdGetter, @@ -53,6 +54,8 @@ import { keyFieldsFnFromSpecifier, } from "./key-extractor.js"; +getStoreKeyName.setStringify(canonicalStringify); + export type TypePolicies = { [__typename: string]: TypePolicy; }; diff --git a/src/utilities/graphql/storeUtils.ts b/src/utilities/graphql/storeUtils.ts index 5624432ebe1..c5b166065a6 100644 --- a/src/utilities/graphql/storeUtils.ts +++ b/src/utilities/graphql/storeUtils.ts @@ -195,62 +195,78 @@ const KNOWN_DIRECTIVES: string[] = [ "nonreactive", ]; -export function getStoreKeyName( - fieldName: string, - args?: Record | null, - directives?: Directives -): string { - if ( - args && - directives && - directives["connection"] && - directives["connection"]["key"] - ) { +// Default stable JSON.stringify implementation used by getStoreKeyName. Can be +// updated/replaced with something better by calling +// getStoreKeyName.setStringify(newStringifyFunction). +let storeKeyNameStringify = canonicalStringify; + +export const getStoreKeyName = Object.assign( + function ( + fieldName: string, + args?: Record | null, + directives?: Directives + ): string { if ( - directives["connection"]["filter"] && - (directives["connection"]["filter"] as string[]).length > 0 + args && + directives && + directives["connection"] && + directives["connection"]["key"] ) { - const filterKeys = directives["connection"]["filter"] - ? (directives["connection"]["filter"] as string[]) - : []; - filterKeys.sort(); - - const filteredArgs = {} as { [key: string]: any }; - filterKeys.forEach((key) => { - filteredArgs[key] = args[key]; - }); - - return `${directives["connection"]["key"]}(${canonicalStringify( - filteredArgs - )})`; - } else { - return directives["connection"]["key"]; + if ( + directives["connection"]["filter"] && + (directives["connection"]["filter"] as string[]).length > 0 + ) { + const filterKeys = directives["connection"]["filter"] + ? (directives["connection"]["filter"] as string[]) + : []; + filterKeys.sort(); + + const filteredArgs = {} as { [key: string]: any }; + filterKeys.forEach((key) => { + filteredArgs[key] = args[key]; + }); + + return `${directives["connection"]["key"]}(${storeKeyNameStringify( + filteredArgs + )})`; + } else { + return directives["connection"]["key"]; + } } - } - let completeFieldName: string = fieldName; + let completeFieldName: string = fieldName; - if (args) { - // We can't use `JSON.stringify` here since it's non-deterministic, - // and can lead to different store key names being created even though - // the `args` object used during creation has the same properties/values. - const stringifiedArgs: string = canonicalStringify(args); - completeFieldName += `(${stringifiedArgs})`; - } + if (args) { + // We can't use `JSON.stringify` here since it's non-deterministic, + // and can lead to different store key names being created even though + // the `args` object used during creation has the same properties/values. + const stringifiedArgs: string = storeKeyNameStringify(args); + completeFieldName += `(${stringifiedArgs})`; + } - if (directives) { - Object.keys(directives).forEach((key) => { - if (KNOWN_DIRECTIVES.indexOf(key) !== -1) return; - if (directives[key] && Object.keys(directives[key]).length) { - completeFieldName += `@${key}(${canonicalStringify(directives[key])})`; - } else { - completeFieldName += `@${key}`; - } - }); - } + if (directives) { + Object.keys(directives).forEach((key) => { + if (KNOWN_DIRECTIVES.indexOf(key) !== -1) return; + if (directives[key] && Object.keys(directives[key]).length) { + completeFieldName += `@${key}(${storeKeyNameStringify( + directives[key] + )})`; + } else { + completeFieldName += `@${key}`; + } + }); + } - return completeFieldName; -} + return completeFieldName; + }, + { + setStringify(s: typeof storeKeyNameStringify) { + const previous = storeKeyNameStringify; + storeKeyNameStringify = s; + return previous; + }, + } +); export function argumentsObjectFromField( field: FieldNode | DirectiveNode,