From 5486b979960f069883a857ab4851c81451884551 Mon Sep 17 00:00:00 2001 From: Jerel Miller Date: Fri, 15 Nov 2024 10:16:16 -0700 Subject: [PATCH] Get fix for unmask with nested data --- src/core/masking.ts | 332 ++++++++++++++++++++------------------------ 1 file changed, 148 insertions(+), 184 deletions(-) diff --git a/src/core/masking.ts b/src/core/masking.ts index 920a227d478..fdede0508ac 100644 --- a/src/core/masking.ts +++ b/src/core/masking.ts @@ -1,5 +1,9 @@ import { Kind } from "graphql"; -import type { FragmentDefinitionNode, SelectionSetNode } from "graphql"; +import type { + FragmentDefinitionNode, + SelectionNode, + SelectionSetNode, +} from "graphql"; import { createFragmentMap, resultKeyNameFromField, @@ -7,7 +11,6 @@ import { getFragmentMaskMode, getOperationDefinition, maybeDeepFreeze, - isNonNullObject, } from "../utilities/index.js"; import type { FragmentMap } from "../utilities/index.js"; import type { ApolloCache, DocumentNode, TypedDocumentNode } from "./index.js"; @@ -20,7 +23,6 @@ interface MaskingContext { operationName: string | undefined; fragmentMap: FragmentMap; cache: ApolloCache; - disableWarnings?: boolean; } // Contextual slot that allows us to disable accessor warnings on fields when in @@ -170,15 +172,16 @@ function maskSelectionSet( return [changed ? masked : data, changed]; } - const result = selectionSet.selections.reduce<[any, boolean]>( - ([memo, changed], selection) => { - switch (selection.kind) { - case Kind.FIELD: { - disableWarningsSlot.withValue(true, () => { + const result = selectionSet.selections + .concat() + .sort(sortFragmentsLast) + .reduce<[any, boolean]>( + ([memo, changed], selection) => { + switch (selection.kind) { + case Kind.FIELD: { const keyName = resultKeyNameFromField(selection); const childSelectionSet = selection.selectionSet; - const maybeValueWithWarning = memo[keyName]; memo[keyName] = data[keyName]; if (memo[keyName] === void 0) { @@ -204,87 +207,82 @@ function maskSelectionSet( // additional fields from the same child selection. Object.keys(masked).length !== Object.keys(data[keyName]).length ) { - delete memo[keyName]; - memo[keyName] = - maybeValueWithWarning ? - backfillFieldsWithWarnings(masked, maybeValueWithWarning) - : masked; + memo[keyName] = masked; changed = true; } } - }); - return [memo, changed]; - } - case Kind.INLINE_FRAGMENT: { - if ( - selection.typeCondition && - !context.cache.fragmentMatches!(selection, data.__typename) - ) { return [memo, changed]; } + case Kind.INLINE_FRAGMENT: { + if ( + selection.typeCondition && + !context.cache.fragmentMatches!(selection, data.__typename) + ) { + return [memo, changed]; + } - const [fragmentData, childChanged] = maskSelectionSet( - data, - selection.selectionSet, - context, - path - ); - - return [ - { - ...memo, - ...fragmentData, - }, - changed || childChanged, - ]; - } - case Kind.FRAGMENT_SPREAD: { - const fragmentName = selection.name.value; - let fragment: FragmentDefinitionNode | null = - context.fragmentMap[fragmentName] || - (context.fragmentMap[fragmentName] = - context.cache.lookupFragment(fragmentName)!); - invariant( - fragment, - "Could not find fragment with name '%s'.", - fragmentName - ); - - const mode = getFragmentMaskMode(selection); - - if (mode === "mask") { - return [memo, true]; + const [fragmentData, childChanged] = maskSelectionSet( + data, + selection.selectionSet, + context, + path + ); + + return [ + { + ...memo, + ...fragmentData, + }, + changed || childChanged, + ]; } + case Kind.FRAGMENT_SPREAD: { + const fragmentName = selection.name.value; + let fragment: FragmentDefinitionNode | null = + context.fragmentMap[fragmentName] || + (context.fragmentMap[fragmentName] = + context.cache.lookupFragment(fragmentName)!); + invariant( + fragment, + "Could not find fragment with name '%s'.", + fragmentName + ); + + const mode = getFragmentMaskMode(selection); + + if (mode === "mask") { + return [memo, true]; + } - if (__DEV__) { - if (mode === "migrate") { - return [ - addFieldAccessorWarnings( - memo, - data, - fragment.selectionSet, - path || "", - context - ), - true, - ]; + if (__DEV__) { + if (mode === "migrate") { + return [ + addFieldAccessorWarnings( + memo, + data, + fragment.selectionSet, + path || "", + context + ), + true, + ]; + } } - } - const [fragmentData, changed] = maskSelectionSet( - data, - fragment.selectionSet, - context, - path - ); + const [fragmentData, changed] = maskSelectionSet( + data, + fragment.selectionSet, + context, + path + ); - return [{ ...memo, ...fragmentData }, changed]; + return [{ ...memo, ...fragmentData }, changed]; + } } - } - }, - [Object.create(null), false] - ); + }, + [Object.create(null), false] + ); if (data && "__typename" in data && !("__typename" in result[0])) { result[0].__typename = data.__typename; @@ -312,84 +310,91 @@ function addFieldAccessorWarnings( }); } - return selectionSetNode.selections.reduce((memo, selection) => { - switch (selection.kind) { - case Kind.FIELD: { - const keyName = resultKeyNameFromField(selection); - const childSelectionSet = selection.selectionSet; + return selectionSetNode.selections + .concat() + .sort(sortFragmentsLast) + .reduce((memo, selection) => { + switch (selection.kind) { + case Kind.FIELD: { + const keyName = resultKeyNameFromField(selection); + const childSelectionSet = selection.selectionSet; - if (keyName in memo && !childSelectionSet) { - return memo; - } + if (keyName in memo && !childSelectionSet) { + return memo; + } - let value = data[keyName]; + let value = data[keyName]; + + if (childSelectionSet) { + value = addFieldAccessorWarnings( + memo[keyName] || + (Array.isArray(data[keyName]) ? [] : Object.create(null)), + data[keyName] as Record, + childSelectionSet, + `${path}.${keyName}`, + context + ); + } - if (childSelectionSet) { - value = addFieldAccessorWarnings( - memo[keyName] || - (Array.isArray(data[keyName]) ? [] : Object.create(null)), - data[keyName] as Record, - childSelectionSet, - `${path}.${keyName}`, - context - ); - } + if (__DEV__) { + if (keyName in memo) { + memo[keyName] = value; + } else { + addAccessorWarning(memo, value, keyName, path, context); + } + } - if (__DEV__) { - addAccessorWarning(memo, value, keyName, path, context); - } + if (!__DEV__) { + memo[keyName] = data[keyName]; + } - if (!__DEV__) { - memo[keyName] = data[keyName]; + return memo; } + case Kind.INLINE_FRAGMENT: { + if ( + selection.typeCondition && + !context.cache.fragmentMatches!(selection, (data as any).__typename) + ) { + return memo; + } - return memo; - } - case Kind.INLINE_FRAGMENT: { - if ( - selection.typeCondition && - !context.cache.fragmentMatches!(selection, (data as any).__typename) - ) { - return memo; + return addFieldAccessorWarnings( + memo, + data, + selection.selectionSet, + path, + context + ); } + case Kind.FRAGMENT_SPREAD: { + const fragment = context.fragmentMap[selection.name.value]; + const mode = getFragmentMaskMode(selection); - return addFieldAccessorWarnings( - memo, - data, - selection.selectionSet, - path, - context - ); - } - case Kind.FRAGMENT_SPREAD: { - const fragment = context.fragmentMap[selection.name.value]; - const mode = getFragmentMaskMode(selection); + if (mode === "mask") { + return memo; + } - if (mode === "mask") { - return memo; - } + if (mode === "unmask") { + const [fragmentData] = maskSelectionSet( + data, + fragment.selectionSet, + context, + path + ); - if (mode === "unmask") { - const [fragmentData] = maskSelectionSet( + return Object.assign(memo, fragmentData); + } + + return addFieldAccessorWarnings( + memo, data, fragment.selectionSet, - context, - path + path, + context ); - - return Object.assign(memo, fragmentData); } - - return addFieldAccessorWarnings( - memo, - data, - fragment.selectionSet, - path, - context - ); } - } - }, memo); + }, memo); } function addAccessorWarning( @@ -448,51 +453,10 @@ function warnOnImproperCacheImplementation() { } } -function backfillFieldsWithWarnings( - source: Record, - unmasked: Record -): any { - if (Array.isArray(source)) { - if (Array.isArray(unmasked)) { - return source.map((s, idx) => - backfillFieldsWithWarnings(s, unmasked[idx]) - ); - } - - return source; - } - - const keys = new Set([...Object.keys(source), ...Object.keys(unmasked)]); - const returnValue: Record = {}; - const { hasOwnProperty } = Object.prototype; - - if (isNonNullObject(source) && isNonNullObject(unmasked)) { - Array.from(keys).forEach((key) => { - if (hasOwnProperty.call(source, key)) { - if (isNonNullObject(source[key]) && isNonNullObject(unmasked[key])) { - returnValue[key] = backfillFieldsWithWarnings( - source[key], - unmasked[key] - ); - } else { - returnValue[key] = source[key]; - } - - return; - } - - const descriptor = Object.getOwnPropertyDescriptor(unmasked, key); - - Object.defineProperty(returnValue, key, { - get: descriptor?.get, - set: descriptor?.set, - enumerable: true, - configurable: true, - }); - }); - } else { - return source; +function sortFragmentsLast(a: SelectionNode, b: SelectionNode) { + if (a.kind === b.kind) { + return 0; } - return returnValue; + return a.kind === Kind.FRAGMENT_SPREAD ? 1 : -1; }