From 5832c9d891e288f47e3b181f4a2f5529847dee2b Mon Sep 17 00:00:00 2001 From: Paul Hachmang Date: Thu, 3 Oct 2024 10:19:48 +0200 Subject: [PATCH 1/8] Solved issue where Recently Viewed Products would execute a query even if there were no products to display. --- .changeset/new-squids-clean.md | 5 +++++ .../plugins/demo/DemoRecentlyViewedProducts.tsx | 5 ++++- .../components/RecentlyViewedProducts.tsx | 5 ++++- 3 files changed, 13 insertions(+), 2 deletions(-) create mode 100644 .changeset/new-squids-clean.md diff --git a/.changeset/new-squids-clean.md b/.changeset/new-squids-clean.md new file mode 100644 index 0000000000..dd6c2bf3d3 --- /dev/null +++ b/.changeset/new-squids-clean.md @@ -0,0 +1,5 @@ +--- +'@graphcommerce/magento-recently-viewed-products': patch +--- + +Solved issue where Recently Viewed Products would execute a query even if there were no products to display. diff --git a/packages/demo-magento-graphcommerce/plugins/demo/DemoRecentlyViewedProducts.tsx b/packages/demo-magento-graphcommerce/plugins/demo/DemoRecentlyViewedProducts.tsx index 88619e074a..3511e926b6 100644 --- a/packages/demo-magento-graphcommerce/plugins/demo/DemoRecentlyViewedProducts.tsx +++ b/packages/demo-magento-graphcommerce/plugins/demo/DemoRecentlyViewedProducts.tsx @@ -27,7 +27,10 @@ export function RecentlyViewedProducts(props: PluginProps(null) const isInView = useInView(ref, { margin: '300px' }) const { skus } = useRecentlyViewedSkus({ exclude }) - const productList = useRecentlyViewedProducts({ exclude, skip: !isInView && loading === 'lazy' }) + const productList = useRecentlyViewedProducts({ + exclude, + skip: skus.length === 0 || (!isInView && loading === 'lazy'), + }) if ( !import.meta.graphCommerce.recentlyViewedProducts?.enabled || diff --git a/packages/magento-recently-viewed-products/components/RecentlyViewedProducts.tsx b/packages/magento-recently-viewed-products/components/RecentlyViewedProducts.tsx index 8185f431eb..0a0c31f78c 100644 --- a/packages/magento-recently-viewed-products/components/RecentlyViewedProducts.tsx +++ b/packages/magento-recently-viewed-products/components/RecentlyViewedProducts.tsx @@ -16,7 +16,10 @@ export function RecentlyViewedProducts(props: RecentlyViewedProductsProps) { const ref = useRef(null) const isInView = useInView(ref, { margin: '300px', once: true }) const { skus } = useRecentlyViewedSkus({ exclude }) - const productList = useRecentlyViewedProducts({ exclude, skip: !isInView && loading === 'lazy' }) + const productList = useRecentlyViewedProducts({ + exclude, + skip: skus.length === 0 || (!isInView && loading === 'lazy'), + }) if ( !import.meta.graphCommerce.recentlyViewedProducts?.enabled || From 9828754192348b966fb9736d7b0e1c78c4a909fa Mon Sep 17 00:00:00 2001 From: Paul Hachmang Date: Thu, 3 Oct 2024 10:21:43 +0200 Subject: [PATCH 2/8] All automatically generated interceptor files are now read-only in vscode to prevent accedental changes. --- .changeset/grumpy-spies-sniff.md | 5 +++++ .vscode/settings.json | 4 ++++ examples/magento-graphcms/.vscode/settings.json | 4 ++++ 3 files changed, 13 insertions(+) create mode 100644 .changeset/grumpy-spies-sniff.md diff --git a/.changeset/grumpy-spies-sniff.md b/.changeset/grumpy-spies-sniff.md new file mode 100644 index 0000000000..25c29e6683 --- /dev/null +++ b/.changeset/grumpy-spies-sniff.md @@ -0,0 +1,5 @@ +--- +'@graphcommerce/next-config': patch +--- + +All automatically generated interceptor files are now read-only in vscode to prevent accedental changes. diff --git a/.vscode/settings.json b/.vscode/settings.json index 61c20d343e..049acd914d 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -18,6 +18,10 @@ "yarn.lock": true, "**/*.tsbuildinfo": true }, + "files.readonlyInclude": { + "**/*.interceptor.tsx": true, + "**/*.interceptor.ts": true + }, "[typescriptreact]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, diff --git a/examples/magento-graphcms/.vscode/settings.json b/examples/magento-graphcms/.vscode/settings.json index 43dfd1645e..89e6facabb 100644 --- a/examples/magento-graphcms/.vscode/settings.json +++ b/examples/magento-graphcms/.vscode/settings.json @@ -22,5 +22,9 @@ ".yarn": true, "yarn.lock": true }, + "files.readonlyInclude": { + "**/*.interceptor.tsx": true, + "**/*.interceptor.ts": true + }, "typescript.enablePromptUseWorkspaceTsdk": true } From c4ff06da58e01de47c48aa55f4f822223202570c Mon Sep 17 00:00:00 2001 From: Paul Hachmang Date: Thu, 3 Oct 2024 10:24:58 +0200 Subject: [PATCH 3/8] When a useInContextQuery is called, only execute when there is no InContextMaskContext defined above --- .changeset/thin-teachers-try.md | 5 +++++ .../graphql/components/InContextMask/InContextMask.tsx | 4 ++-- packages/graphql/hooks/useInContextQuery.ts | 7 +++++-- 3 files changed, 12 insertions(+), 4 deletions(-) create mode 100644 .changeset/thin-teachers-try.md diff --git a/.changeset/thin-teachers-try.md b/.changeset/thin-teachers-try.md new file mode 100644 index 0000000000..61e40d92cd --- /dev/null +++ b/.changeset/thin-teachers-try.md @@ -0,0 +1,5 @@ +--- +'@graphcommerce/graphql': patch +--- + +When a useInContextQuery is called, only execute when there is no InContextMaskContext defined above diff --git a/packages/graphql/components/InContextMask/InContextMask.tsx b/packages/graphql/components/InContextMask/InContextMask.tsx index 3d85c90744..35258acc66 100644 --- a/packages/graphql/components/InContextMask/InContextMask.tsx +++ b/packages/graphql/components/InContextMask/InContextMask.tsx @@ -23,9 +23,9 @@ export type InContextMaskProps< type InContextMaskContextType = { mask: boolean } -const InContextMaskContext = createContext(null) +export const InContextMaskContext = createContext(undefined) -export function useInContextInputMask() { +export function useInContextInputMask(): InContextMaskContextType { const context = useContext(InContextMaskContext) // eslint-disable-next-line react-hooks/rules-of-hooks diff --git a/packages/graphql/hooks/useInContextQuery.ts b/packages/graphql/hooks/useInContextQuery.ts index def9b1be59..e208fe1d01 100644 --- a/packages/graphql/hooks/useInContextQuery.ts +++ b/packages/graphql/hooks/useInContextQuery.ts @@ -3,9 +3,10 @@ import type { InputMaybe, InContextInput } from '@graphcommerce/graphql-mesh' import { useIsSSR } from '@graphcommerce/next-ui/hooks/useIsSsr' // eslint-disable-next-line import/no-extraneous-dependencies import { getCssFlag, removeCssFlag, setCssFlag } from '@graphcommerce/next-ui/utils/cssFlags' -import { useEffect } from 'react' +import { useContext, useEffect } from 'react' import { QueryHookOptions, QueryResult, TypedDocumentNode, useQuery } from '../apollo' import { useInContextInput } from './useInContextInput' +import { InContextMaskContext } from '../components/InContextMask/InContextMask' /** * Creates a query that allows fetching data for logged in customers, but have @@ -32,6 +33,8 @@ export function useInContextQuery< const context = useInContextInput() const isSsr = useIsSSR() + const alreadyInContext = Boolean(useContext(InContextMaskContext)) + useEffect(() => { if (isSsr) return if (context && !getCssFlag('in-context')) setCssFlag('in-context', true) @@ -41,7 +44,7 @@ export function useInContextQuery< const clientQuery = useQuery(document, { ...options, variables: { ...options.variables, context } as V, - skip: skip && !context, + skip: alreadyInContext || (skip && !context), }) let { data } = clientQuery From 959e0dd81a7943648250be6a2bf8f1bc27be8a3b Mon Sep 17 00:00:00 2001 From: Paul Hachmang Date: Thu, 3 Oct 2024 10:25:38 +0200 Subject: [PATCH 4/8] Render multiple items in the RowSpecs table as a list --- .changeset/young-sloths-draw.md | 5 +++++ .../ProductSpecsCustomAttributes.tsx | 17 ++++++++--------- 2 files changed, 13 insertions(+), 9 deletions(-) create mode 100644 .changeset/young-sloths-draw.md diff --git a/.changeset/young-sloths-draw.md b/.changeset/young-sloths-draw.md new file mode 100644 index 0000000000..b19431560a --- /dev/null +++ b/.changeset/young-sloths-draw.md @@ -0,0 +1,5 @@ +--- +'@graphcommerce/magento-product': patch +--- + +Render multiple items in the RowSpecs table as a list diff --git a/packages/magento-product/components/ProductSpecs/ProductSpecsCustomAttributes.tsx b/packages/magento-product/components/ProductSpecs/ProductSpecsCustomAttributes.tsx index 21002f0a09..afe06b8ba4 100644 --- a/packages/magento-product/components/ProductSpecs/ProductSpecsCustomAttributes.tsx +++ b/packages/magento-product/components/ProductSpecs/ProductSpecsCustomAttributes.tsx @@ -1,5 +1,5 @@ import { useQuery } from '@graphcommerce/graphql' -import { extendableComponent } from '@graphcommerce/next-ui' +import { extendableComponent, ListFormat } from '@graphcommerce/next-ui' import { Box } from '@mui/material' import { ProductSpecsFragment } from './ProductSpecs.gql' import { ProductSpecsTypesDocument } from './ProductSpecsTypes.gql' @@ -19,24 +19,23 @@ export function ProductSpecsCustomAttributes(props: ProductSpecsCustomAttributes if (items?.length === 0) return null + console.log(specs) return ( <> {specs?.map((item) => (
  • - { - productSpecsTypes?.data?.attributesList?.items?.find( - (type) => type?.code === item?.code, - )?.label - } + {productSpecsTypes?.data?.attributesList?.items?.find( + (type) => type?.code === item?.code, + )?.label ?? item?.code}
    - + {item?.__typename === 'AttributeSelectedOptions' && ( - <> + {item?.selected_options?.map((option) => ( {option?.label === '1' ? 'Yes' : option?.label} ))} - + )} {item?.__typename === 'AttributeValue' && {item.value}} From 6202fc7d2f36d5d1f2fedb74b2f2d93b308d5885 Mon Sep 17 00:00:00 2001 From: Paul Hachmang Date: Thu, 3 Oct 2024 10:26:15 +0200 Subject: [PATCH 5/8] In some cases the xMagentoCacheId wasn't defined in the returned query, make sure the application doesn't crash --- .changeset/chilly-nails-enjoy.md | 5 +++++ packages/magento-customer/link/xMagentoCacheIdHeader.ts | 6 ++++-- 2 files changed, 9 insertions(+), 2 deletions(-) create mode 100644 .changeset/chilly-nails-enjoy.md diff --git a/.changeset/chilly-nails-enjoy.md b/.changeset/chilly-nails-enjoy.md new file mode 100644 index 0000000000..bf92519f8c --- /dev/null +++ b/.changeset/chilly-nails-enjoy.md @@ -0,0 +1,5 @@ +--- +'@graphcommerce/magento-customer': patch +--- + +In some cases the xMagentoCacheId wasn't defined in the returned query, make sure the application doesn't crash diff --git a/packages/magento-customer/link/xMagentoCacheIdHeader.ts b/packages/magento-customer/link/xMagentoCacheIdHeader.ts index fbed2e04e9..a052b4a780 100644 --- a/packages/magento-customer/link/xMagentoCacheIdHeader.ts +++ b/packages/magento-customer/link/xMagentoCacheIdHeader.ts @@ -14,8 +14,10 @@ export const xMagentoCacheIdHeader = new ApolloLink((operation, forward) => { const { cache } = operation.getContext() if (!cache) return data - const xMagentoCacheId = (data.extensions as { forwardedHeaders: Record }) - .forwardedHeaders['x-magento-cache-id'] + const xMagentoCacheId = ( + data.extensions as { forwardedHeaders: Record } | undefined + )?.forwardedHeaders?.['x-magento-cache-id'] + if (!xMagentoCacheId) return data const tokenResult = cache.readQuery({ query: CustomerTokenDocument }) From a5219df7765829e126004c8ef15be9f476deff0e Mon Sep 17 00:00:00 2001 From: Paul Hachmang Date: Thu, 3 Oct 2024 10:27:11 +0200 Subject: [PATCH 6/8] Removed console.log --- .../components/ProductSpecs/ProductSpecsCustomAttributes.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/magento-product/components/ProductSpecs/ProductSpecsCustomAttributes.tsx b/packages/magento-product/components/ProductSpecs/ProductSpecsCustomAttributes.tsx index afe06b8ba4..c550499d87 100644 --- a/packages/magento-product/components/ProductSpecs/ProductSpecsCustomAttributes.tsx +++ b/packages/magento-product/components/ProductSpecs/ProductSpecsCustomAttributes.tsx @@ -19,7 +19,6 @@ export function ProductSpecsCustomAttributes(props: ProductSpecsCustomAttributes if (items?.length === 0) return null - console.log(specs) return ( <> {specs?.map((item) => ( From 0ad22bf82cf08e22b44ed43e257346ae3f5e4f84 Mon Sep 17 00:00:00 2001 From: Paul Hachmang Date: Thu, 3 Oct 2024 10:37:09 +0200 Subject: [PATCH 7/8] Make sure the mask value of the parent get passed through if defined. --- packages/graphql/hooks/useInContextQuery.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/packages/graphql/hooks/useInContextQuery.ts b/packages/graphql/hooks/useInContextQuery.ts index e208fe1d01..8584d868d3 100644 --- a/packages/graphql/hooks/useInContextQuery.ts +++ b/packages/graphql/hooks/useInContextQuery.ts @@ -5,8 +5,8 @@ import { useIsSSR } from '@graphcommerce/next-ui/hooks/useIsSsr' import { getCssFlag, removeCssFlag, setCssFlag } from '@graphcommerce/next-ui/utils/cssFlags' import { useContext, useEffect } from 'react' import { QueryHookOptions, QueryResult, TypedDocumentNode, useQuery } from '../apollo' -import { useInContextInput } from './useInContextInput' import { InContextMaskContext } from '../components/InContextMask/InContextMask' +import { useInContextInput } from './useInContextInput' /** * Creates a query that allows fetching data for logged in customers, but have @@ -33,7 +33,7 @@ export function useInContextQuery< const context = useInContextInput() const isSsr = useIsSSR() - const alreadyInContext = Boolean(useContext(InContextMaskContext)) + const inContext = useContext(InContextMaskContext) useEffect(() => { if (isSsr) return @@ -44,7 +44,7 @@ export function useInContextQuery< const clientQuery = useQuery(document, { ...options, variables: { ...options.variables, context } as V, - skip: alreadyInContext || (skip && !context), + skip: !!inContext || (skip && !context), }) let { data } = clientQuery @@ -56,5 +56,10 @@ export function useInContextQuery< mask = !skip ? !clientQuery.data && !clientQuery.previousData : !clientQuery.data } + // If this method is called within an InContextMask, we skip this complete functionality so we show the parent mask. + if (inContext) { + mask = inContext.mask + } + return { ...clientQuery, data: data ?? unscopedResult, mask } } From ce30678ad353ac4c7c38d79e96a2bb3de55f6fcb Mon Sep 17 00:00:00 2001 From: Paul Hachmang Date: Thu, 3 Oct 2024 10:40:27 +0200 Subject: [PATCH 8/8] Automatically fall back to existing upsells/related products if they are defined and Algolia returns an error --- .changeset/tidy-timers-live.md | 5 + .../mesh/algoliaHitToMagentoProduct.ts | 2 +- .../mesh/getRecommendations.ts | 98 +++++++++++-------- packages/algolia-recommend/mesh/resolvers.ts | 24 +++-- 4 files changed, 76 insertions(+), 53 deletions(-) create mode 100644 .changeset/tidy-timers-live.md diff --git a/.changeset/tidy-timers-live.md b/.changeset/tidy-timers-live.md new file mode 100644 index 0000000000..624c97bb79 --- /dev/null +++ b/.changeset/tidy-timers-live.md @@ -0,0 +1,5 @@ +--- +'@graphcommerce/algolia-recommend': patch +--- + +Automatically fall back to existing upsells/related products if they are defined and Algolia returns an error diff --git a/packages/algolia-products/mesh/algoliaHitToMagentoProduct.ts b/packages/algolia-products/mesh/algoliaHitToMagentoProduct.ts index b84cb728da..5fb284bacb 100644 --- a/packages/algolia-products/mesh/algoliaHitToMagentoProduct.ts +++ b/packages/algolia-products/mesh/algoliaHitToMagentoProduct.ts @@ -175,7 +175,7 @@ export function algoliaHitToMagentoProduct( // options_container: null, // price_tiers: [], // product_links: [], - // related_products: getRecommendations(), + // related_products: null, // short_description: null, // small_image: null, // special_price: null, diff --git a/packages/algolia-recommend/mesh/getRecommendations.ts b/packages/algolia-recommend/mesh/getRecommendations.ts index 48b328b800..d2a2430545 100644 --- a/packages/algolia-recommend/mesh/getRecommendations.ts +++ b/packages/algolia-recommend/mesh/getRecommendations.ts @@ -5,9 +5,13 @@ import type { AlgoliarecommendationsHit, } from '@graphcommerce/graphql-mesh' import { nonNullable } from '@graphcommerce/next-ui' -import type { GraphQLResolveInfo } from 'graphql' +import type { GraphQLError, GraphQLResolveInfo } from 'graphql' import type { Simplify } from 'type-fest' +function isGraphQLError(err: unknown): err is GraphQLError { + return !!(err as GraphQLError)?.message +} + const inputToModel = { Trending_items_Input: 'trending_items' as const, Trending_facet_values_Input: 'trending_facets' as const, @@ -15,29 +19,11 @@ const inputToModel = { Looking_similar_Input: 'looking_similar' as const, Related_products_Input: 'related_products' as const, } + function isAlgoliaResponse(root: T): root is T & { uid: string } { return 'uid' in root } -function argsFromKeysInput(keys, args, context) { - const body = keys - .map( - (key) => - ({ - [key.keyInput]: { - model: inputToModel[key.keyInput as string], - indexName: getIndexName(context), - - ...args, - objectID: key.objectId, - }, - }) as unknown as AlgoliarecommendationsRequest_Input, - ) - .filter(nonNullable) - - const returnObject = { input: { requests: body } } - return returnObject -} export async function getRecommendations< K extends keyof AlgoliarecommendationsRequest_Input, Input extends AlgoliarecommendationsRequest_Input[K], @@ -53,29 +39,57 @@ export async function getRecommendations< if (!isAlgoliaResponse(root)) { return [] } - return ( - (await context.algoliaRecommend.Query.algolia_getRecommendations({ - key: { keyInput, objectId: atob(root.uid) }, - argsFromKeys: (keys) => argsFromKeysInput(keys, args, context), - valuesFromResults: (res, keys) => - keys - .map((_key, index) => res?.results[index]) - .map((r) => r?.hits.map((hit) => hit && mapper(hit)).filter(nonNullable)) ?? null, - selectionSet: /* GraphQL */ ` - { - results { - nbHits - hits { - ... on AlgoliarecommendHit { - objectID - additionalProperties + try { + return ( + (await context.algoliaRecommend.Query.algolia_getRecommendations({ + key: { keyInput, objectId: atob(root.uid) }, + argsFromKeys: (keys) => ({ + input: { + requests: keys + .map( + (key) => + ({ + [key.keyInput]: { + model: inputToModel[key.keyInput as string], + indexName: getIndexName(context), + + ...args, + objectID: key.objectId, + }, + }) as unknown as AlgoliarecommendationsRequest_Input, + ) + .filter(nonNullable), + }, + }), + valuesFromResults: (res, keys) => + keys + .map((_key, index) => res?.results[index]) + .map((r) => r?.hits.map((hit) => hit && mapper(hit)).filter(nonNullable)) ?? null, + selectionSet: /* GraphQL */ ` + { + results { + nbHits + hits { + ... on AlgoliarecommendHit { + objectID + additionalProperties + } } } } - } - `, - context, - info, - })) ?? null - ) + `, + context, + info, + })) ?? null + ) + } catch (e) { + if (isGraphQLError(e)) { + console.log( + 'There was an error retrieving Algolia Recommendations, make sure the recommendation models are created', + e, + ) + } + + return null + } } diff --git a/packages/algolia-recommend/mesh/resolvers.ts b/packages/algolia-recommend/mesh/resolvers.ts index 339dbf2274..c96da7d036 100644 --- a/packages/algolia-recommend/mesh/resolvers.ts +++ b/packages/algolia-recommend/mesh/resolvers.ts @@ -87,11 +87,12 @@ type ProductResolver = ResolverFn< > if (isEnabled(import.meta.graphCommerce.algolia.relatedProducts)) { + const fieldName = enumToLocation(import.meta.graphCommerce.algolia.relatedProducts) const resolve: ProductResolver = async (root, args, context, info) => { const { objectID, threshold, fallbackParameters, maxRecommendations, queryParameters } = await getRecommendationsArgs(root, args, context) - return getRecommendations( + const recommendations = await getRecommendations( root, 'Related_products_Input', { objectID, threshold, fallbackParameters, maxRecommendations, queryParameters }, @@ -99,11 +100,12 @@ if (isEnabled(import.meta.graphCommerce.algolia.relatedProducts)) { info, await createProductMapper(context), ) + return recommendations ?? root[fieldName] ?? null } productInterfaceTypes.forEach((productType) => { if (!resolvers[productType]) resolvers[productType] = {} - resolvers[productType][enumToLocation(import.meta.graphCommerce.algolia.relatedProducts)] = { + resolvers[productType][fieldName] = { selectionSet: `{ uid }`, resolve, } @@ -111,10 +113,11 @@ if (isEnabled(import.meta.graphCommerce.algolia.relatedProducts)) { } if (isEnabled(import.meta.graphCommerce.algolia.lookingSimilar)) { + const fieldName = enumToLocation(import.meta.graphCommerce.algolia.lookingSimilar) const resolve: ProductResolver = async (root, args, context, info) => { const { objectID, threshold, fallbackParameters, maxRecommendations, queryParameters } = await getRecommendationsArgs(root, args, context) - return getRecommendations( + const recommendations = await getRecommendations( root, 'Looking_similar_Input', { objectID, threshold, fallbackParameters, maxRecommendations, queryParameters }, @@ -122,11 +125,12 @@ if (isEnabled(import.meta.graphCommerce.algolia.lookingSimilar)) { info, await createProductMapper(context), ) + return recommendations ?? root[fieldName] ?? null } productInterfaceTypes.forEach((productType) => { if (!resolvers[productType]) resolvers[productType] = {} - resolvers[productType][enumToLocation(import.meta.graphCommerce.algolia.lookingSimilar)] = { + resolvers[productType][fieldName] = { selectionSet: `{ uid }`, resolve, } @@ -134,11 +138,12 @@ if (isEnabled(import.meta.graphCommerce.algolia.lookingSimilar)) { } if (isEnabled(import.meta.graphCommerce.algolia.frequentlyBoughtTogether)) { - const resolver: ProductResolver = async (root, args, context, info) => { + const fieldName = enumToLocation(import.meta.graphCommerce.algolia.frequentlyBoughtTogether) + + const resolve: ProductResolver = async (root, args, context, info) => { const { objectID, threshold, maxRecommendations, queryParameters } = await getRecommendationsArgs(root, args, context) - - return getRecommendations( + const recommendations = await getRecommendations( root, 'Frequently_bought_together_Input', { objectID, threshold, maxRecommendations, queryParameters }, @@ -146,13 +151,12 @@ if (isEnabled(import.meta.graphCommerce.algolia.frequentlyBoughtTogether)) { info, await createProductMapper(context), ) + return recommendations ?? root[fieldName] ?? null } productInterfaceTypes.forEach((productType) => { if (!resolvers[productType]) resolvers[productType] = {} - resolvers[productType][ - enumToLocation(import.meta.graphCommerce.algolia.frequentlyBoughtTogether) - ] = resolver + resolvers[productType][fieldName] = { selectionSet: `{ uid }`, resolve } }) }