From 81fade264257a6c032ed866d615116c960987613 Mon Sep 17 00:00:00 2001 From: Chris Helgert Date: Thu, 26 Oct 2023 16:35:19 +0200 Subject: [PATCH 1/3] fix(gql): inifite loop with circular references [] --- .../live-preview-sdk/src/graphql/entries.ts | 154 +++++++++++------- .../src/helpers/resolveReference.ts | 17 +- packages/live-preview-sdk/src/liveUpdates.ts | 20 ++- .../live-preview-sdk/src/rest/entities.ts | 99 +++-------- packages/live-preview-sdk/src/types.ts | 11 +- 5 files changed, 144 insertions(+), 157 deletions(-) diff --git a/packages/live-preview-sdk/src/graphql/entries.ts b/packages/live-preview-sdk/src/graphql/entries.ts index 35d6188a..015e6ee6 100644 --- a/packages/live-preview-sdk/src/graphql/entries.ts +++ b/packages/live-preview-sdk/src/graphql/entries.ts @@ -14,17 +14,18 @@ import { SUPPORTED_RICHTEXT_EMBEDS, isAsset, isRichText } from '../helpers/entit import { CollectionItem, SysProps, - EntityReferenceMap, Entity, ASSET_TYPENAME, UpdateFieldProps, UpdateReferenceFieldProps, UpdateEntryProps, GraphQLParams, + ReferenceMap, } from '../types'; import { updateAsset } from './assets'; import { isRelevantField, updateAliasedInformation } from './queryUtils'; import { buildCollectionName, generateTypeName } from './utils'; +import { MAX_DEPTH } from '../constants'; /** * Updates GraphQL response data based on CMA entry object @@ -40,9 +41,10 @@ export async function updateEntry({ dataFromPreviewApp, updateFromEntryEditor, locale, - entityReferenceMap, gqlParams, sendMessage, + depth, + referenceMap, }: UpdateEntryProps): Promise { if (dataFromPreviewApp.sys.id !== updateFromEntryEditor.sys.id) { return dataFromPreviewApp; @@ -63,11 +65,12 @@ export async function updateEntry({ await updateRichTextField({ dataFromPreviewApp: copyOfDataFromPreviewApp, updateFromEntryEditor, - entityReferenceMap, name, locale, gqlParams, sendMessage, + depth, + referenceMap, }); } else if (field.type === 'Link') { await updateSingleRefField({ @@ -75,9 +78,10 @@ export async function updateEntry({ updateFromEntryEditor, name, locale, - entityReferenceMap, gqlParams, sendMessage, + depth, + referenceMap, }); } else if (field.type === 'Array' && field.items?.type === 'Link') { await updateMultiRefField({ @@ -85,9 +89,10 @@ export async function updateEntry({ updateFromEntryEditor, name, locale, - entityReferenceMap, gqlParams, sendMessage, + depth, + referenceMap, }); } } @@ -106,15 +111,21 @@ function isEntityLinkEmpty(obj: RichTextLink) { return Object.values(obj).every((arr) => arr.length === 0); } -async function processNode( - node: any, - entries: RichTextLink, - assets: RichTextLink, - entityReferenceMap: EntityReferenceMap, - locale: string, - sendMessage: SendMessage, - gqlParams?: GraphQLParams -) { +async function processNode({ + node, + entries, + assets, + ...props +}: { + node: any; + entries: RichTextLink; + assets: RichTextLink; + locale: string; + sendMessage: SendMessage; + gqlParams?: GraphQLParams; + depth: number; + referenceMap: ReferenceMap; +}) { // Check if the node is an embedded entity if (SUPPORTED_RICHTEXT_EMBEDS.includes(node.nodeType)) { if (node.data && node.data.target && node.data.target.sys) { @@ -126,21 +137,15 @@ async function processNode( // Use the updateReferenceEntryField or updateReferenceAssetField function to resolve the entity reference if (node.data.target.sys.linkType === 'Entry') { ref = await updateReferenceEntryField({ + ...props, referenceFromPreviewApp: null, updatedReference, - entityReferenceMap, - locale, - gqlParams, - sendMessage, }); } else if (node.data.target.sys.linkType === 'Asset') { ref = await updateReferenceAssetField({ + ...props, referenceFromPreviewApp: null, updatedReference, - entityReferenceMap, - locale, - gqlParams, - sendMessage, }); } @@ -174,32 +179,38 @@ async function processNode( // since embedded entries can be part of other rich text content (e.g. embedded inline entries) // we need to recursively check for these entries to display them for (const contentNode of node.content) { - await processNode( - contentNode, + await processNode({ + ...props, + node: contentNode, entries, assets, - entityReferenceMap, - locale, - sendMessage, - gqlParams - ); + }); } } } -async function processRichTextField( - richTextNode: any | null, - entityReferenceMap: EntityReferenceMap, - locale: string, - sendMessage: SendMessage, - gqlParams?: GraphQLParams -): Promise<{ entries: RichTextLink; assets: RichTextLink }> { +async function processRichTextField({ + richTextNode, + ...props +}: { + richTextNode: any | null; + locale: string; + sendMessage: SendMessage; + gqlParams?: GraphQLParams; + depth: number; + referenceMap: ReferenceMap; +}): Promise<{ entries: RichTextLink; assets: RichTextLink }> { const entries: RichTextLink = { block: [], inline: [], hyperlink: [] }; const assets: RichTextLink = { block: [], inline: [], hyperlink: [] }; if (richTextNode) { for (const node of richTextNode.content) { - await processNode(node, entries, assets, entityReferenceMap, locale, sendMessage, gqlParams); + await processNode({ + ...props, + node, + entries, + assets, + }); } } @@ -214,9 +225,10 @@ async function updateRichTextField({ updateFromEntryEditor, name, locale, - entityReferenceMap, gqlParams, sendMessage, + depth, + referenceMap, }: UpdateFieldProps) { if (!dataFromPreviewApp[name]) { dataFromPreviewApp[name] = {}; @@ -227,29 +239,30 @@ async function updateRichTextField({ updateFromEntryEditor?.fields?.[name] || null; // Update the rich text embedded entries - dataFromPreviewApp[name].links = await processRichTextField( - dataFromPreviewApp[name].json, - entityReferenceMap, + dataFromPreviewApp[name].links = await processRichTextField({ + richTextNode: dataFromPreviewApp[name].json, locale, sendMessage, - gqlParams - ); + gqlParams, + depth, + referenceMap, + }); } async function updateReferenceAssetField({ referenceFromPreviewApp, updatedReference, - entityReferenceMap, locale, gqlParams, sendMessage, + referenceMap, }: SetOptional, 'gqlParams'>) { const { reference } = await resolveReference({ - entityReferenceMap, referenceId: updatedReference.sys.id, isAsset: true, locale, sendMessage, + referenceMap, }); return updateAsset( @@ -263,19 +276,24 @@ async function updateReferenceAssetField({ ); } +function isInDepthLimit(depth: number, gqlParams?: GraphQLParams): boolean { + return !!gqlParams || depth < MAX_DEPTH; +} + async function updateReferenceEntryField({ referenceFromPreviewApp, updatedReference, - entityReferenceMap, locale, gqlParams, sendMessage, + depth, + referenceMap, }: SetOptional, 'gqlParams'>) { const { reference, typeName } = await resolveReference({ - entityReferenceMap, referenceId: updatedReference.sys.id, locale, sendMessage, + referenceMap, }); // If we have the typename of the updated reference, we can work with it @@ -296,29 +314,31 @@ async function updateReferenceEntryField({ if (isRichText(value)) { // richtext merged[key] = { json: value }; - merged[key].links = await processRichTextField( - value, - entityReferenceMap, + merged[key].links = await processRichTextField({ + richTextNode: value, locale, sendMessage, - gqlParams - ); + gqlParams, + depth, + referenceMap, + }); } - if ('sys' in value) { + if ('sys' in value && isInDepthLimit(depth, gqlParams)) { // single reference merged[key] = value; await updateSingleRefField({ dataFromPreviewApp: merged, updateFromEntryEditor: reference, locale, - entityReferenceMap, name: key, gqlParams, sendMessage, + depth: depth + 1, + referenceMap, }); } - } else if (Array.isArray(value) && value[0]?.sys) { + } else if (Array.isArray(value) && value[0]?.sys && isInDepthLimit(depth, gqlParams)) { // multi references const name = buildCollectionName(key); merged[name] = { items: value }; @@ -326,10 +346,11 @@ async function updateReferenceEntryField({ dataFromPreviewApp: merged, updateFromEntryEditor: reference, locale, - entityReferenceMap, name: key, gqlParams, sendMessage, + depth: depth + 1, + referenceMap, }); } else { // primitive fields @@ -343,10 +364,11 @@ async function updateReferenceEntryField({ async function updateReferenceField({ referenceFromPreviewApp, updatedReference, - entityReferenceMap, locale, gqlParams, sendMessage, + depth, + referenceMap, }: UpdateReferenceFieldProps) { if (!updatedReference) { return null; @@ -365,20 +387,22 @@ async function updateReferenceField({ return updateReferenceAssetField({ referenceFromPreviewApp, updatedReference, - entityReferenceMap, locale, gqlParams, sendMessage, + depth, + referenceMap, }); } return updateReferenceEntryField({ referenceFromPreviewApp, updatedReference, - entityReferenceMap, locale, gqlParams, sendMessage, + depth, + referenceMap, }); } @@ -387,9 +411,10 @@ async function updateSingleRefField({ updateFromEntryEditor, name, locale, - entityReferenceMap, gqlParams, sendMessage, + depth, + referenceMap, }: UpdateFieldProps) { const updatedReference = updateFromEntryEditor?.fields?.[name] as Asset | Entry | undefined; dataFromPreviewApp[name] = await updateReferenceField({ @@ -397,10 +422,11 @@ async function updateSingleRefField({ __typename?: string; }, updatedReference, - entityReferenceMap: entityReferenceMap as EntityReferenceMap, locale, gqlParams, sendMessage, + depth, + referenceMap, }); } @@ -409,9 +435,10 @@ async function updateMultiRefField({ updateFromEntryEditor, name, locale, - entityReferenceMap, gqlParams, sendMessage, + depth, + referenceMap, }: UpdateFieldProps) { const fieldName = buildCollectionName(name); @@ -427,10 +454,11 @@ async function updateMultiRefField({ __typename?: string; }, updatedReference: updatedItem, - entityReferenceMap: entityReferenceMap as EntityReferenceMap, locale, gqlParams, sendMessage, + depth, + referenceMap, }); return result; diff --git a/packages/live-preview-sdk/src/helpers/resolveReference.ts b/packages/live-preview-sdk/src/helpers/resolveReference.ts index ebb034bb..a38fae8b 100644 --- a/packages/live-preview-sdk/src/helpers/resolveReference.ts +++ b/packages/live-preview-sdk/src/helpers/resolveReference.ts @@ -7,7 +7,7 @@ import { import type { Asset, Entry } from 'contentful'; import { generateTypeName } from '../graphql/utils'; -import { ASSET_TYPENAME, EntityReferenceMap } from '../types'; +import { ASSET_TYPENAME, ReferenceMap } from '../types'; const store: Record = {}; @@ -42,39 +42,39 @@ function getStore(locale: string, sendMessage: SendMessage): EditorEntityStore { } export async function resolveReference(info: { - entityReferenceMap: EntityReferenceMap; referenceId: string; locale: string; sendMessage: SendMessage; + referenceMap: ReferenceMap; }): Promise<{ reference: Entry; typeName: string }>; export async function resolveReference(info: { - entityReferenceMap: EntityReferenceMap; referenceId: string; isAsset: true; locale: string; sendMessage: SendMessage; + referenceMap: ReferenceMap; }): Promise<{ reference: Asset; typeName: string }>; /** * Returns the requested reference from - * 1) the entityReferenceMap if it was already resolved once + * 1) the referenceMap if it was already resolved once * 2) loads it from the editor directly */ export async function resolveReference({ - entityReferenceMap, referenceId, isAsset, locale, sendMessage, + referenceMap, }: { - entityReferenceMap: EntityReferenceMap; referenceId: string; isAsset?: boolean; locale: string; sendMessage: SendMessage; + referenceMap: ReferenceMap; }): Promise<{ reference: Entry | Asset; typeName: string }> { - const reference = entityReferenceMap.get(referenceId); - + const reference = referenceMap.get(referenceId); if (reference) { + referenceMap.set(referenceId, reference); return { reference, typeName: @@ -101,6 +101,7 @@ export async function resolveReference({ throw new Error(`Unknown reference ${referenceId}`); } + referenceMap.set(referenceId, result); return { reference: result, typeName: generateTypeName(result.sys.contentType.sys.id), diff --git a/packages/live-preview-sdk/src/liveUpdates.ts b/packages/live-preview-sdk/src/liveUpdates.ts index 2a66c21a..6c014654 100644 --- a/packages/live-preview-sdk/src/liveUpdates.ts +++ b/packages/live-preview-sdk/src/liveUpdates.ts @@ -20,10 +20,10 @@ import { ContentType, Entity, EntityWithSys, - EntityReferenceMap, hasSysInformation, Subscription, GraphQLParams, + ReferenceMap, } from './types'; interface MergeEntityProps { @@ -31,7 +31,6 @@ interface MergeEntityProps { locale: string; updateFromEntryEditor: Entry | Asset; contentType: ContentType; - entityReferenceMap: EntityReferenceMap; gqlParams?: GraphQLParams; } @@ -48,6 +47,7 @@ export class LiveUpdates { private storage: StorageMap; private defaultLocale: string; private sendMessage: (method: PostMessageMethods, data: EditorMessage) => void; + private referenceMap: ReferenceMap = new Map(); constructor({ locale, targetOrigin }: { locale: string; targetOrigin: string[] }) { this.defaultLocale = locale; @@ -59,7 +59,6 @@ export class LiveUpdates { private async mergeEntity({ contentType, dataFromPreviewApp, - entityReferenceMap, locale, updateFromEntryEditor, gqlParams, @@ -69,6 +68,8 @@ export class LiveUpdates { data: Entity; updated: boolean; }> { + const depth = 0; + if ('__typename' in dataFromPreviewApp) { // GraphQL const data = await (dataFromPreviewApp.__typename === 'Asset' @@ -78,9 +79,10 @@ export class LiveUpdates { dataFromPreviewApp, updateFromEntryEditor: updateFromEntryEditor as Entry, locale, - entityReferenceMap, gqlParams, sendMessage: this.sendMessage, + depth, + referenceMap: this.referenceMap, })); return { @@ -91,17 +93,14 @@ export class LiveUpdates { if (this.isCfEntity(dataFromPreviewApp)) { // REST - const depth = 0; - const visitedReferenceMap = new Map(); return { data: await rest.updateEntity( contentType, dataFromPreviewApp as Entry, updateFromEntryEditor as Entry, locale, - entityReferenceMap, depth, - visitedReferenceMap, + this.referenceMap, this.sendMessage ), updated: true, @@ -201,6 +200,10 @@ export class LiveUpdates { ) { const { entity, contentType, entityReferenceMap } = message as EntryUpdatedMessage; + for (const [key, value] of entityReferenceMap.entries()) { + this.referenceMap.set(key, value); + } + await Promise.all( [...this.subscriptions].map(async ([, s]) => { try { @@ -212,7 +215,6 @@ export class LiveUpdates { locale: s.locale || this.defaultLocale, updateFromEntryEditor: entity, contentType: contentType, - entityReferenceMap: entityReferenceMap, gqlParams: s.gqlParams, }); diff --git a/packages/live-preview-sdk/src/rest/entities.ts b/packages/live-preview-sdk/src/rest/entities.ts index e6adc5de..d1144880 100644 --- a/packages/live-preview-sdk/src/rest/entities.ts +++ b/packages/live-preview-sdk/src/rest/entities.ts @@ -17,7 +17,7 @@ import { isResourceLink, isRichText, } from '../helpers/entities'; -import { ContentType, EntityReferenceMap } from '../types'; +import { ContentType, ReferenceMap } from '../types'; export type Reference = Asset | Entry | WithResourceName; @@ -34,39 +34,24 @@ function getFieldName(contentType: ContentType, field: ContentType['fields'][num } /** - * Update the reference from the entry editor with the information from the entityReferenceMap. + * Update the reference from the entry editor with the information from the referenceMap. * If the information is not yet available, it send a message to the editor to retrieve it. */ async function updateRef( dataFromPreviewApp: Reference | undefined, updateFromEntryEditor: Reference, locale: string, - entityReferenceMap: EntityReferenceMap, depth: number, - visitedReferenceMap: Map, + referenceMap: Map, sendMessage: SendMessage ): Promise { - let reference; - const id = - 'urn' in updateFromEntryEditor.sys - ? updateFromEntryEditor.sys.urn - : updateFromEntryEditor.sys.id; - - // If the ID of the updateFromEntryEditor is in visitedReferences, then stop the recursion - if (visitedReferenceMap.has(id)) { - debug.warn('Detected a circular reference, stopping recursion'); - reference = visitedReferenceMap.get(id); - } else { - const { reference: resolvedReference } = await resolveReference({ - entityReferenceMap, - referenceId: updateFromEntryEditor.sys.id, - ...(isAsset(updateFromEntryEditor) ? { isAsset: true } : undefined), - locale, - sendMessage, - }); - reference = resolvedReference; - visitedReferenceMap.set(id, resolvedReference); - } + const { reference } = await resolveReference({ + referenceId: updateFromEntryEditor.sys.id, + ...(isAsset(updateFromEntryEditor) ? { isAsset: true } : undefined), + locale, + sendMessage, + referenceMap, + }); if (!reference) { return dataFromPreviewApp; @@ -86,9 +71,8 @@ async function updateRef( reference, locale, key as keyof Reference['fields'], - entityReferenceMap, depth + 1, - visitedReferenceMap, + referenceMap, sendMessage ); // multi ref fields @@ -98,9 +82,8 @@ async function updateRef( reference, locale, key as keyof Reference['fields'], - entityReferenceMap, depth + 1, - visitedReferenceMap, + referenceMap, sendMessage ); // rich text fields @@ -110,9 +93,8 @@ async function updateRef( reference as Entry, key, locale, - entityReferenceMap, depth + 1, - visitedReferenceMap, + referenceMap, sendMessage ); // single and multi resource link fields @@ -139,9 +121,8 @@ async function updateMultiRefField( updateFromEntryEditor: Reference, locale: string, name: keyof Reference['fields'], - entityReferenceMap: EntityReferenceMap, depth: number, - visitedReferenceMap: Map, + referenceMap: Map, sendMessage: SendMessage ) { if (!updateFromEntryEditor.fields?.[name]) { @@ -156,9 +137,8 @@ async function updateMultiRefField( (dataFromPreviewApp.fields[name] as Reference[])?.[index], updateFromEntryReference, locale, - entityReferenceMap, depth + 1, - visitedReferenceMap, + referenceMap, sendMessage ) ) @@ -173,9 +153,8 @@ async function updateSingleRefField( updateFromEntryEditor: Reference, locale: string, name: keyof Reference['fields'], - entityReferenceMap: EntityReferenceMap, depth: number, - visitedReferenceMap: Map, + referenceMap: Map, sendMessage: SendMessage ) { const matchUpdateFromEntryEditor = updateFromEntryEditor?.fields?.[name] as Reference | undefined; @@ -191,9 +170,8 @@ async function updateSingleRefField( dataFromPreviewApp.fields[name] as Reference | undefined, matchUpdateFromEntryEditor, locale, - entityReferenceMap, depth + 1, - visitedReferenceMap, + referenceMap, sendMessage ); @@ -202,10 +180,9 @@ async function updateSingleRefField( async function resolveRichTextLinks( node: any, - entityReferenceMap: EntityReferenceMap, locale: string, depth: number, - visitedReferenceMap: Map, + referenceMap: Map, sendMessage: SendMessage ) { if (SUPPORTED_RICHTEXT_EMBEDS.includes(node.nodeType)) { @@ -220,9 +197,8 @@ async function resolveRichTextLinks( undefined, updatedReference, locale, - entityReferenceMap, depth + 1, - visitedReferenceMap, + referenceMap, sendMessage ); } @@ -231,14 +207,7 @@ async function resolveRichTextLinks( if (node.content) { for (const childNode of node.content) { - await resolveRichTextLinks( - childNode, - entityReferenceMap, - locale, - depth + 1, -visitedReferenceMap, - sendMessage - ); + await resolveRichTextLinks(childNode, locale, depth + 1, referenceMap, sendMessage); } } } @@ -248,9 +217,8 @@ async function updateRichTextField( updateFromEntryEditor: Entry, name: string, locale: string, - entityReferenceMap: EntityReferenceMap, depth: number, - visitedReferenceMap: Map, + referenceMap: Map, sendMessage: SendMessage ) { const richText = updateFromEntryEditor.fields?.[name]; @@ -260,14 +228,7 @@ async function updateRichTextField( dataFromPreviewApp.fields[name] = richText; // Resolve the linked entries or assets within the rich text field for (const node of richText.content) { - await resolveRichTextLinks( - node, - entityReferenceMap, - locale, - depth, - visitedReferenceMap, - sendMessage - ); + await resolveRichTextLinks(node, locale, depth, referenceMap, sendMessage); } } } @@ -275,10 +236,6 @@ async function updateRichTextField( /** * Updates REST response data based on CMA entry object * - * @param contentType - * @param dataFromPreviewApp The REST response to be updated - * @param updateFromEntryEditor CMA entry object containing the update - * @param locale Locale code * @returns Updated REST response data */ export async function updateEntity( @@ -286,9 +243,8 @@ export async function updateEntity( dataFromPreviewApp: Entry, updateFromEntryEditor: Entry | Asset, locale: string, - entityReferenceMap: EntityReferenceMap, depth: number, - visitedReferenceMap: Map, + referenceMap: ReferenceMap, sendMessage: SendMessage ): Promise { if (dataFromPreviewApp.sys.id !== updateFromEntryEditor.sys.id) { @@ -310,9 +266,8 @@ export async function updateEntity( updateFromEntryEditor, locale, name as keyof Reference['fields'], - entityReferenceMap, depth + 1, - visitedReferenceMap, + referenceMap, sendMessage ); } else if (field.type === 'Array' && field.items?.type === 'Link' && depth < MAX_DEPTH) { @@ -321,9 +276,8 @@ export async function updateEntity( updateFromEntryEditor, locale, name as keyof Reference['fields'], - entityReferenceMap, depth + 1, - visitedReferenceMap, + referenceMap, sendMessage ); } else if (field.type === 'RichText') { @@ -332,9 +286,8 @@ export async function updateEntity( updateFromEntryEditor as Entry, name, locale, - entityReferenceMap, depth, - visitedReferenceMap, + referenceMap, sendMessage ); } else if (field.type === 'ResourceLink') { diff --git a/packages/live-preview-sdk/src/types.ts b/packages/live-preview-sdk/src/types.ts index 8da34e43..a3589848 100644 --- a/packages/live-preview-sdk/src/types.ts +++ b/packages/live-preview-sdk/src/types.ts @@ -56,16 +56,17 @@ export interface CollectionItem { __typename?: string; } -export class EntityReferenceMap extends Map {} +export type ReferenceMap = Map; export type UpdateEntryProps = { contentType: ContentType; dataFromPreviewApp: Entity & { sys: SysProps }; updateFromEntryEditor: Entry; locale: string; - entityReferenceMap: EntityReferenceMap; gqlParams?: GraphQLParams; sendMessage: SendMessage; + depth: number; + referenceMap: ReferenceMap; }; export type UpdateFieldProps = { @@ -73,18 +74,20 @@ export type UpdateFieldProps = { updateFromEntryEditor: Entry; name: string; locale: string; - entityReferenceMap: EntityReferenceMap; gqlParams?: GraphQLParams; sendMessage: SendMessage; + depth: number; + referenceMap: ReferenceMap; }; export type UpdateReferenceFieldProps = { referenceFromPreviewApp: (Entity & { __typename?: string }) | null | undefined; updatedReference?: (Pick | Pick) & { __typename?: string }; - entityReferenceMap: EntityReferenceMap; locale: string; gqlParams?: GraphQLParams; sendMessage: SendMessage; + depth: number; + referenceMap: ReferenceMap; }; /** From 8e3dabae03340b3419759a9eeae4eeb2dd71f715 Mon Sep 17 00:00:00 2001 From: Chris Helgert Date: Thu, 26 Oct 2023 17:01:52 +0200 Subject: [PATCH 2/3] refactor(references): lift up store and drop referenceMap [] --- .../src/__tests__/resolveReference.spec.ts | 121 ++++-------------- .../src/graphql/__tests__/entries.test.ts | 10 +- .../live-preview-sdk/src/graphql/entries.ts | 93 ++++---------- .../src/helpers/resolveReference.ts | 71 ++-------- packages/live-preview-sdk/src/liveUpdates.ts | 45 +++++-- .../src/rest/__tests__/entities.test.ts | 49 +++---- .../live-preview-sdk/src/rest/entities.ts | 71 +++------- packages/live-preview-sdk/src/types.ts | 15 +-- 8 files changed, 152 insertions(+), 323 deletions(-) diff --git a/packages/live-preview-sdk/src/__tests__/resolveReference.spec.ts b/packages/live-preview-sdk/src/__tests__/resolveReference.spec.ts index 8229137a..1b99c236 100644 --- a/packages/live-preview-sdk/src/__tests__/resolveReference.spec.ts +++ b/packages/live-preview-sdk/src/__tests__/resolveReference.spec.ts @@ -1,17 +1,18 @@ /* @vitest-environment jsdom */ import { EditorEntityStore } from '@contentful/visual-sdk'; -import type { Asset, Entry } from 'contentful'; import { describe, vi, it, beforeEach, afterEach, expect, Mock } from 'vitest'; import { resolveReference } from '../helpers/resolveReference'; import cdaAsset from './fixtures/cdaAsset.json'; import cdaEntry from './fixtures/cdaEntry.json'; +import type { GetStore } from '../types'; vi.mock('@contentful/visual-sdk'); describe('resolveReference', () => { const locale = 'en-US'; - const sendMessage = vi.fn(); + const getStore = vi.fn, ReturnType>(); + let store: EditorEntityStore; beforeEach(() => { (EditorEntityStore as Mock).mockImplementation(() => ({ @@ -19,108 +20,42 @@ describe('resolveReference', () => { fetchEntry: async () => cdaEntry, fetchAsset: async () => cdaAsset, })); + + store = new EditorEntityStore({ + entities: [], + locale, + sendMessage: vi.fn(), + subscribe: vi.fn(), + timeoutDuration: 10, + }); + + getStore.mockImplementation(() => store); }); afterEach(() => { vi.clearAllMocks(); }); - describe('asset', () => { - const asset = { - fields: { - file: { fileName: 'abc.jpg' }, - }, - sys: { - id: '1', - }, - } as unknown as Asset; - - const expected = { - reference: { - fields: { - file: { fileName: 'abc.jpg' }, - }, - sys: { - id: '1', - }, - }, - typeName: 'Asset', - }; - - it('resolves it directly from the map', async () => { - const result = await resolveReference({ - entityReferenceMap: new Map([['1', asset]]), - referenceId: '1', - locale, - sendMessage, - }); - - expect(result).toEqual(expected); + it('resolves an assets correctly from the store', async () => { + const result = await resolveReference({ + referenceId: cdaAsset.sys.id, + isAsset: true, + locale, + getStore, }); - it('resolves it from the editor', async () => { - const result = await resolveReference({ - entityReferenceMap: new Map(), - referenceId: cdaAsset.sys.id, - isAsset: true, - locale, - sendMessage, - }); - - expect(result.typeName).toBe('Asset'); - expect(result.reference.fields).toEqual(cdaAsset.fields); - }); + expect(result.typeName).toBe('Asset'); + expect(result.reference.fields).toEqual(cdaAsset.fields); }); - describe('entries', () => { - const entry = { - fields: { - title: 'Hello World', - }, - sys: { - id: '1', - contentType: { - sys: { id: 'helloWorld' }, - }, - }, - } as unknown as Entry; - - const expected = { - reference: { - fields: { - title: 'Hello World', - }, - sys: { - id: '1', - contentType: { - sys: { id: 'helloWorld' }, - }, - }, - }, - typeName: 'HelloWorld', - }; - - it('resolves it directly from the map', async () => { - const result = await resolveReference({ - entityReferenceMap: new Map([['1', entry]]), - referenceId: '1', - locale, - sendMessage, - }); - - expect(result).toEqual(expected); + it('resolves an entry correctly from the store', async () => { + const result = await resolveReference({ + referenceId: cdaEntry.sys.id, + locale, + getStore, }); - it('resolves it from the editor', async () => { - const result = await resolveReference({ - entityReferenceMap: new Map(), - referenceId: cdaEntry.sys.id, - locale, - sendMessage, - }); - - expect(result.typeName).toBe('TopicProductFeature'); - expect(result.reference.fields).toEqual(cdaEntry.fields); - }); + expect(result.typeName).toBe('TopicProductFeature'); + expect(result.reference.fields).toEqual(cdaEntry.fields); }); }); diff --git a/packages/live-preview-sdk/src/graphql/__tests__/entries.test.ts b/packages/live-preview-sdk/src/graphql/__tests__/entries.test.ts index 3d62d36d..f3a89db4 100644 --- a/packages/live-preview-sdk/src/graphql/__tests__/entries.test.ts +++ b/packages/live-preview-sdk/src/graphql/__tests__/entries.test.ts @@ -4,7 +4,7 @@ import { describe, it, expect, vi, afterEach, beforeEach, Mock } from 'vitest'; import * as Utils from '../../helpers'; import { resolveReference } from '../../helpers/resolveReference'; -import { SysProps, EntityReferenceMap, Entity, ContentType } from '../../types'; +import type { SysProps, Entity, ContentType, GetStore } from '../../types'; import { updateEntry } from '../entries'; import defaultContentTypeJSON from './fixtures/contentType.json'; import entry from './fixtures/entry.json'; @@ -19,7 +19,7 @@ const defaultContentType = defaultContentTypeJSON as ContentTypeProps; // Note: we can get rid of expect.objectContaining, if we iterate over the provided data instead of the ContentType.fields describe('Update GraphQL Entry', () => { const testReferenceId = '18kDTlnJNnDIJf6PsXE5Mr'; - const sendMessage = vi.fn(); + const getStore = vi.fn, ReturnType>(); beforeEach(() => { (resolveReference as Mock).mockResolvedValue({ @@ -45,13 +45,11 @@ describe('Update GraphQL Entry', () => { data, update = entry as unknown as Entry, locale = EN, - entityReferenceMap = new EntityReferenceMap(), contentType = defaultContentType, }: { data: Entity & { sys: SysProps }; update?: Entry; locale?: string; - entityReferenceMap?: EntityReferenceMap; contentType?: ContentType; }) => { return updateEntry({ @@ -59,8 +57,8 @@ describe('Update GraphQL Entry', () => { dataFromPreviewApp: data, updateFromEntryEditor: update, locale, - entityReferenceMap, - sendMessage, + getStore, + depth: 0, }); }; diff --git a/packages/live-preview-sdk/src/graphql/entries.ts b/packages/live-preview-sdk/src/graphql/entries.ts index 015e6ee6..39f30767 100644 --- a/packages/live-preview-sdk/src/graphql/entries.ts +++ b/packages/live-preview-sdk/src/graphql/entries.ts @@ -2,14 +2,7 @@ import { BLOCKS, INLINES } from '@contentful/rich-text-types'; import { Asset, Entry } from 'contentful'; import type { SetOptional } from 'type-fest'; -import { - isPrimitiveField, - updatePrimitiveField, - resolveReference, - clone, - debug, - SendMessage, -} from '../helpers'; +import { isPrimitiveField, updatePrimitiveField, resolveReference, clone, debug } from '../helpers'; import { SUPPORTED_RICHTEXT_EMBEDS, isAsset, isRichText } from '../helpers/entities'; import { CollectionItem, @@ -20,7 +13,7 @@ import { UpdateReferenceFieldProps, UpdateEntryProps, GraphQLParams, - ReferenceMap, + GetStore, } from '../types'; import { updateAsset } from './assets'; import { isRelevantField, updateAliasedInformation } from './queryUtils'; @@ -40,11 +33,7 @@ export async function updateEntry({ contentType, dataFromPreviewApp, updateFromEntryEditor, - locale, - gqlParams, - sendMessage, - depth, - referenceMap, + ...props }: UpdateEntryProps): Promise { if (dataFromPreviewApp.sys.id !== updateFromEntryEditor.sys.id) { return dataFromPreviewApp; @@ -54,7 +43,7 @@ export async function updateEntry({ for (const field of contentType.fields) { const name = field.apiName ?? field.name; - if (isRelevantField(name, typename, gqlParams)) { + if (isRelevantField(name, typename, props.gqlParams)) { if (isPrimitiveField(field)) { updatePrimitiveField({ dataFromPreviewApp: copyOfDataFromPreviewApp, @@ -63,42 +52,30 @@ export async function updateEntry({ }); } else if (field.type === 'RichText') { await updateRichTextField({ + ...props, dataFromPreviewApp: copyOfDataFromPreviewApp, updateFromEntryEditor, name, - locale, - gqlParams, - sendMessage, - depth, - referenceMap, }); } else if (field.type === 'Link') { await updateSingleRefField({ + ...props, dataFromPreviewApp: copyOfDataFromPreviewApp, updateFromEntryEditor, name, - locale, - gqlParams, - sendMessage, - depth, - referenceMap, }); } else if (field.type === 'Array' && field.items?.type === 'Link') { await updateMultiRefField({ + ...props, dataFromPreviewApp: copyOfDataFromPreviewApp, updateFromEntryEditor, name, - locale, - gqlParams, - sendMessage, - depth, - referenceMap, }); } } } - return updateAliasedInformation(copyOfDataFromPreviewApp, typename, gqlParams); + return updateAliasedInformation(copyOfDataFromPreviewApp, typename, props.gqlParams); } interface RichTextLink { @@ -121,10 +98,9 @@ async function processNode({ entries: RichTextLink; assets: RichTextLink; locale: string; - sendMessage: SendMessage; + getStore: GetStore; gqlParams?: GraphQLParams; depth: number; - referenceMap: ReferenceMap; }) { // Check if the node is an embedded entity if (SUPPORTED_RICHTEXT_EMBEDS.includes(node.nodeType)) { @@ -195,10 +171,9 @@ async function processRichTextField({ }: { richTextNode: any | null; locale: string; - sendMessage: SendMessage; + getStore: GetStore; gqlParams?: GraphQLParams; depth: number; - referenceMap: ReferenceMap; }): Promise<{ entries: RichTextLink; assets: RichTextLink }> { const entries: RichTextLink = { block: [], inline: [], hyperlink: [] }; const assets: RichTextLink = { block: [], inline: [], hyperlink: [] }; @@ -226,9 +201,8 @@ async function updateRichTextField({ name, locale, gqlParams, - sendMessage, + getStore, depth, - referenceMap, }: UpdateFieldProps) { if (!dataFromPreviewApp[name]) { dataFromPreviewApp[name] = {}; @@ -242,10 +216,9 @@ async function updateRichTextField({ dataFromPreviewApp[name].links = await processRichTextField({ richTextNode: dataFromPreviewApp[name].json, locale, - sendMessage, + getStore, gqlParams, depth, - referenceMap, }); } @@ -254,15 +227,13 @@ async function updateReferenceAssetField({ updatedReference, locale, gqlParams, - sendMessage, - referenceMap, + getStore, }: SetOptional, 'gqlParams'>) { const { reference } = await resolveReference({ referenceId: updatedReference.sys.id, isAsset: true, locale, - sendMessage, - referenceMap, + getStore, }); return updateAsset( @@ -285,15 +256,13 @@ async function updateReferenceEntryField({ updatedReference, locale, gqlParams, - sendMessage, + getStore, depth, - referenceMap, }: SetOptional, 'gqlParams'>) { const { reference, typeName } = await resolveReference({ referenceId: updatedReference.sys.id, locale, - sendMessage, - referenceMap, + getStore, }); // If we have the typename of the updated reference, we can work with it @@ -317,10 +286,9 @@ async function updateReferenceEntryField({ merged[key].links = await processRichTextField({ richTextNode: value, locale, - sendMessage, gqlParams, depth, - referenceMap, + getStore, }); } @@ -333,9 +301,8 @@ async function updateReferenceEntryField({ locale, name: key, gqlParams, - sendMessage, + getStore, depth: depth + 1, - referenceMap, }); } } else if (Array.isArray(value) && value[0]?.sys && isInDepthLimit(depth, gqlParams)) { @@ -348,9 +315,8 @@ async function updateReferenceEntryField({ locale, name: key, gqlParams, - sendMessage, + getStore, depth: depth + 1, - referenceMap, }); } else { // primitive fields @@ -366,9 +332,8 @@ async function updateReferenceField({ updatedReference, locale, gqlParams, - sendMessage, + getStore, depth, - referenceMap, }: UpdateReferenceFieldProps) { if (!updatedReference) { return null; @@ -389,9 +354,8 @@ async function updateReferenceField({ updatedReference, locale, gqlParams, - sendMessage, + getStore, depth, - referenceMap, }); } @@ -400,9 +364,8 @@ async function updateReferenceField({ updatedReference, locale, gqlParams, - sendMessage, + getStore, depth, - referenceMap, }); } @@ -412,9 +375,8 @@ async function updateSingleRefField({ name, locale, gqlParams, - sendMessage, + getStore, depth, - referenceMap, }: UpdateFieldProps) { const updatedReference = updateFromEntryEditor?.fields?.[name] as Asset | Entry | undefined; dataFromPreviewApp[name] = await updateReferenceField({ @@ -424,9 +386,8 @@ async function updateSingleRefField({ updatedReference, locale, gqlParams, - sendMessage, + getStore, depth, - referenceMap, }); } @@ -436,9 +397,8 @@ async function updateMultiRefField({ name, locale, gqlParams, - sendMessage, + getStore, depth, - referenceMap, }: UpdateFieldProps) { const fieldName = buildCollectionName(name); @@ -456,9 +416,8 @@ async function updateMultiRefField({ updatedReference: updatedItem, locale, gqlParams, - sendMessage, + getStore, depth, - referenceMap, }); return result; diff --git a/packages/live-preview-sdk/src/helpers/resolveReference.ts b/packages/live-preview-sdk/src/helpers/resolveReference.ts index a38fae8b..647949b5 100644 --- a/packages/live-preview-sdk/src/helpers/resolveReference.ts +++ b/packages/live-preview-sdk/src/helpers/resolveReference.ts @@ -1,91 +1,37 @@ -import { - EditorEntityStore, - PostMessageMethods, - RequestEntitiesMessage, - RequestedEntitiesMessage, -} from '@contentful/visual-sdk'; import type { Asset, Entry } from 'contentful'; import { generateTypeName } from '../graphql/utils'; -import { ASSET_TYPENAME, ReferenceMap } from '../types'; - -const store: Record = {}; - -export type SendMessage = (method: PostMessageMethods, params: RequestEntitiesMessage) => void; - -function getStore(locale: string, sendMessage: SendMessage): EditorEntityStore { - if (!store[locale]) { - store[locale] = new EditorEntityStore({ - entities: [], - sendMessage, - subscribe: (method, cb) => { - // TODO: move this to a generic subscribe function on ContentfulLivePreview - const listeners = ( - event: MessageEvent - ) => { - if (typeof event.data !== 'object' || !event.data) return; - if (event.data.from !== 'live-preview') return; - if (event.data.method === method) { - cb(event.data); - } - }; - - window.addEventListener('message', listeners); - - return () => window.removeEventListener('message', listeners); - }, - locale, - }); - } - - return store[locale]; -} +import { ASSET_TYPENAME, GetStore } from '../types'; export async function resolveReference(info: { referenceId: string; locale: string; - sendMessage: SendMessage; - referenceMap: ReferenceMap; + getStore: GetStore; }): Promise<{ reference: Entry; typeName: string }>; export async function resolveReference(info: { referenceId: string; isAsset: true; locale: string; - sendMessage: SendMessage; - referenceMap: ReferenceMap; + getStore: GetStore; }): Promise<{ reference: Asset; typeName: string }>; /** * Returns the requested reference from - * 1) the referenceMap if it was already resolved once + * 1) the store if it was already resolved once * 2) loads it from the editor directly */ export async function resolveReference({ referenceId, isAsset, locale, - sendMessage, - referenceMap, + getStore, }: { referenceId: string; isAsset?: boolean; locale: string; - sendMessage: SendMessage; - referenceMap: ReferenceMap; + getStore: GetStore; }): Promise<{ reference: Entry | Asset; typeName: string }> { - const reference = referenceMap.get(referenceId); - if (reference) { - referenceMap.set(referenceId, reference); - return { - reference, - typeName: - 'contentType' in reference.sys && reference.sys?.contentType?.sys?.id - ? generateTypeName(reference.sys.contentType.sys.id) - : ASSET_TYPENAME, - }; - } - if (isAsset) { - const result = await getStore(locale, sendMessage).fetchAsset(referenceId); + const result = await getStore(locale).fetchAsset(referenceId); if (!result) { throw new Error(`Unknown reference ${referenceId}`); } @@ -96,12 +42,11 @@ export async function resolveReference({ }; } - const result = await getStore(locale, sendMessage).fetchEntry(referenceId); + const result = await getStore(locale).fetchEntry(referenceId); if (!result) { throw new Error(`Unknown reference ${referenceId}`); } - referenceMap.set(referenceId, result); return { reference: result, typeName: generateTypeName(result.sys.contentType.sys.id), diff --git a/packages/live-preview-sdk/src/liveUpdates.ts b/packages/live-preview-sdk/src/liveUpdates.ts index 6c014654..f933b508 100644 --- a/packages/live-preview-sdk/src/liveUpdates.ts +++ b/packages/live-preview-sdk/src/liveUpdates.ts @@ -7,6 +7,7 @@ import type { ErrorMessage, MessageFromEditor, PostMessageMethods, + RequestedEntitiesMessage, SubscribedMessage, } from '.'; import * as gql from './graphql'; @@ -23,8 +24,8 @@ import { hasSysInformation, Subscription, GraphQLParams, - ReferenceMap, } from './types'; +import { EditorEntityStore } from '@contentful/visual-sdk'; interface MergeEntityProps { dataFromPreviewApp: Entity; @@ -47,7 +48,7 @@ export class LiveUpdates { private storage: StorageMap; private defaultLocale: string; private sendMessage: (method: PostMessageMethods, data: EditorMessage) => void; - private referenceMap: ReferenceMap = new Map(); + private store: Record = {}; constructor({ locale, targetOrigin }: { locale: string; targetOrigin: string[] }) { this.defaultLocale = locale; @@ -56,6 +57,34 @@ export class LiveUpdates { window.addEventListener('beforeunload', () => this.clearStorage()); } + private getStore(locale: string) { + if (!this.store[locale]) { + this.store[locale] = new EditorEntityStore({ + entities: [], + sendMessage: this.sendMessage, + subscribe: (method, cb) => { + // TODO: move this to a generic subscribe function on ContentfulLivePreview + const listeners = ( + event: MessageEvent + ) => { + if (typeof event.data !== 'object' || !event.data) return; + if (event.data.from !== 'live-preview') return; + if (event.data.method === method) { + cb(event.data); + } + }; + + window.addEventListener('message', listeners); + + return () => window.removeEventListener('message', listeners); + }, + locale, + }); + } + + return this.store[locale]; + } + private async mergeEntity({ contentType, dataFromPreviewApp, @@ -80,9 +109,8 @@ export class LiveUpdates { updateFromEntryEditor: updateFromEntryEditor as Entry, locale, gqlParams, - sendMessage: this.sendMessage, depth, - referenceMap: this.referenceMap, + getStore: this.getStore, })); return { @@ -100,8 +128,7 @@ export class LiveUpdates { updateFromEntryEditor as Entry, locale, depth, - this.referenceMap, - this.sendMessage + this.getStore ), updated: true, }; @@ -200,8 +227,10 @@ export class LiveUpdates { ) { const { entity, contentType, entityReferenceMap } = message as EntryUpdatedMessage; - for (const [key, value] of entityReferenceMap.entries()) { - this.referenceMap.set(key, value); + for (const value of entityReferenceMap.values()) { + if (value.sys.locale) { + this.getStore(value.sys.locale).updateEntity(value); + } } await Promise.all( diff --git a/packages/live-preview-sdk/src/rest/__tests__/entities.test.ts b/packages/live-preview-sdk/src/rest/__tests__/entities.test.ts index f3e940bd..04718510 100644 --- a/packages/live-preview-sdk/src/rest/__tests__/entities.test.ts +++ b/packages/live-preview-sdk/src/rest/__tests__/entities.test.ts @@ -5,7 +5,6 @@ import { describe, it, expect, vi, afterEach, beforeEach, Mock } from 'vitest'; import contentTypeAsset from '../../__tests__/fixtures/contentTypeAsset.json'; import { MAX_DEPTH } from '../../constants'; import { clone, resolveReference } from '../../helpers'; -import { EntityReferenceMap } from '../../types'; import { Reference, updateEntity } from '../entities'; import { EN, referenceWithRichTextId } from './constants'; import contentTypeEntryJSON from './fixtures/contentType.json'; @@ -22,6 +21,7 @@ import dataFromPreviewAppJSON from './fixtures/dataFromPreviewApp.json'; import assetJSON from './fixtures/updateAssetFromEntryEditor.json'; import entryJSON from './fixtures/updateFromEntryEditor.json'; import { patchField } from './utils'; +import { GetStore } from '../../types'; vi.mock('../../helpers/resolveReference'); @@ -31,24 +31,21 @@ const asset = assetJSON as unknown as Asset; const dataFromPreviewApp = dataFromPreviewAppJSON as unknown as Entry; describe('Update REST entry', () => { - const sendMessage = vi.fn(); - const defaultEntityReferenceMap = new Map([ - [newEntryReference.sys.id, newEntryReference], - [newAssetReference.sys.id, newAssetReference], - [ - referenceWithRichTextId, - { - sys: { id: referenceWithRichTextId }, - fields: { - richTextFieldName: unresolvedRichTextLinks, - }, - } as unknown as Entry, - ], - ]); + const getStore = vi.fn, ReturnType>(); + const defaultEntities: Reference[] = [ + newEntryReference, + newAssetReference, + { + sys: { id: referenceWithRichTextId }, + fields: { + richTextFieldName: unresolvedRichTextLinks, + }, + } as unknown as Entry, + ]; beforeEach(() => { (resolveReference as Mock).mockImplementation(async ({ referenceId }) => { - return { reference: defaultEntityReferenceMap.get(referenceId) }; + return { reference: defaultEntities.find((e) => e.sys.id === referenceId) }; }); }); @@ -61,27 +58,20 @@ describe('Update REST entry', () => { dataFromPreviewApp, updateFromEntryEditor = entry, locale = EN, - entityReferenceMap = defaultEntityReferenceMap, }: { contentType?: ContentTypeProps; dataFromPreviewApp: Entry; updateFromEntryEditor?: Entry | Asset; locale?: string; - entityReferenceMap?: EntityReferenceMap; - }) => { - const visitedReferences = new Map(); - - return updateEntity( + }) => + updateEntity( contentType, clone(dataFromPreviewApp), clone(updateFromEntryEditor), locale, - entityReferenceMap, 0, - visitedReferences, - sendMessage + getStore ); - }; it('updates primitive fields', async () => { const result = await updateFn({ dataFromPreviewApp }); @@ -218,8 +208,11 @@ describe('Update REST entry', () => { }, } as unknown as Entry; - // Add circular reference to map - defaultEntityReferenceMap.set(circularReferenceId, circularReference); + (resolveReference as Mock).mockImplementation(async ({ referenceId }) => { + return { + reference: [...defaultEntities, circularReference].find((e) => e.sys.id === referenceId), + }; + }); // Update entry to contain the circular reference const result = await updateFn({ diff --git a/packages/live-preview-sdk/src/rest/entities.ts b/packages/live-preview-sdk/src/rest/entities.ts index d1144880..5ab054f4 100644 --- a/packages/live-preview-sdk/src/rest/entities.ts +++ b/packages/live-preview-sdk/src/rest/entities.ts @@ -2,14 +2,7 @@ import { Asset, Entry } from 'contentful'; import type { WithResourceName } from 'contentful-management'; import { MAX_DEPTH } from '../constants'; -import { - debug, - clone, - isPrimitiveField, - resolveReference, - updatePrimitiveField, - SendMessage, -} from '../helpers'; +import { debug, clone, isPrimitiveField, resolveReference, updatePrimitiveField } from '../helpers'; import { SUPPORTED_RICHTEXT_EMBEDS, isAsset, @@ -17,7 +10,7 @@ import { isResourceLink, isRichText, } from '../helpers/entities'; -import { ContentType, ReferenceMap } from '../types'; +import { ContentType, GetStore } from '../types'; export type Reference = Asset | Entry | WithResourceName; @@ -34,7 +27,7 @@ function getFieldName(contentType: ContentType, field: ContentType['fields'][num } /** - * Update the reference from the entry editor with the information from the referenceMap. + * Update the reference from the entry editor with the information from the store. * If the information is not yet available, it send a message to the editor to retrieve it. */ async function updateRef( @@ -42,22 +35,19 @@ async function updateRef( updateFromEntryEditor: Reference, locale: string, depth: number, - referenceMap: Map, - sendMessage: SendMessage + getStore: GetStore ): Promise { const { reference } = await resolveReference({ referenceId: updateFromEntryEditor.sys.id, ...(isAsset(updateFromEntryEditor) ? { isAsset: true } : undefined), locale, - sendMessage, - referenceMap, + getStore, }); if (!reference) { return dataFromPreviewApp; } - // Entity is already in the reference map, so let's apply it on the data const result = clone(reference); // TODO: Refactor so we check based on field types instead of field value https://contentful.atlassian.net/browse/TOL-1285 @@ -72,8 +62,7 @@ async function updateRef( locale, key as keyof Reference['fields'], depth + 1, - referenceMap, - sendMessage + getStore ); // multi ref fields } else if (Array.isArray(value) && isEntityLink(value[0]) && depth < MAX_DEPTH) { @@ -83,8 +72,7 @@ async function updateRef( locale, key as keyof Reference['fields'], depth + 1, - referenceMap, - sendMessage + getStore ); // rich text fields } else if (isRichText(value)) { @@ -94,8 +82,7 @@ async function updateRef( key, locale, depth + 1, - referenceMap, - sendMessage + getStore ); // single and multi resource link fields } else if (isResourceLink(value) || (Array.isArray(value) && isResourceLink(value[0]))) { @@ -122,8 +109,7 @@ async function updateMultiRefField( locale: string, name: keyof Reference['fields'], depth: number, - referenceMap: Map, - sendMessage: SendMessage + getStore: GetStore ) { if (!updateFromEntryEditor.fields?.[name]) { delete dataFromPreviewApp.fields[name]; @@ -138,8 +124,7 @@ async function updateMultiRefField( updateFromEntryReference, locale, depth + 1, - referenceMap, - sendMessage + getStore ) ) ).then((list) => list.filter(Boolean))) as Reference[]; @@ -154,8 +139,7 @@ async function updateSingleRefField( locale: string, name: keyof Reference['fields'], depth: number, - referenceMap: Map, - sendMessage: SendMessage + getStore: GetStore ) { const matchUpdateFromEntryEditor = updateFromEntryEditor?.fields?.[name] as Reference | undefined; @@ -171,20 +155,13 @@ async function updateSingleRefField( matchUpdateFromEntryEditor, locale, depth + 1, - referenceMap, - sendMessage + getStore ); return dataFromPreviewApp; } -async function resolveRichTextLinks( - node: any, - locale: string, - depth: number, - referenceMap: Map, - sendMessage: SendMessage -) { +async function resolveRichTextLinks(node: any, locale: string, depth: number, getStore: GetStore) { if (SUPPORTED_RICHTEXT_EMBEDS.includes(node.nodeType)) { if (node.data && node.data.target && node.data.target.sys) { if (node.data.target.sys.linkType === 'Entry' || node.data.target.sys.linkType === 'Asset') { @@ -198,8 +175,7 @@ async function resolveRichTextLinks( updatedReference, locale, depth + 1, - referenceMap, - sendMessage + getStore ); } } @@ -207,7 +183,7 @@ async function resolveRichTextLinks( if (node.content) { for (const childNode of node.content) { - await resolveRichTextLinks(childNode, locale, depth + 1, referenceMap, sendMessage); + await resolveRichTextLinks(childNode, locale, depth + 1, getStore); } } } @@ -218,8 +194,7 @@ async function updateRichTextField( name: string, locale: string, depth: number, - referenceMap: Map, - sendMessage: SendMessage + getStore: GetStore ) { const richText = updateFromEntryEditor.fields?.[name]; @@ -228,7 +203,7 @@ async function updateRichTextField( dataFromPreviewApp.fields[name] = richText; // Resolve the linked entries or assets within the rich text field for (const node of richText.content) { - await resolveRichTextLinks(node, locale, depth, referenceMap, sendMessage); + await resolveRichTextLinks(node, locale, depth, getStore); } } } @@ -244,8 +219,7 @@ export async function updateEntity( updateFromEntryEditor: Entry | Asset, locale: string, depth: number, - referenceMap: ReferenceMap, - sendMessage: SendMessage + getStore: GetStore ): Promise { if (dataFromPreviewApp.sys.id !== updateFromEntryEditor.sys.id) { return dataFromPreviewApp; @@ -267,8 +241,7 @@ export async function updateEntity( locale, name as keyof Reference['fields'], depth + 1, - referenceMap, - sendMessage + getStore ); } else if (field.type === 'Array' && field.items?.type === 'Link' && depth < MAX_DEPTH) { await updateMultiRefField( @@ -277,8 +250,7 @@ export async function updateEntity( locale, name as keyof Reference['fields'], depth + 1, - referenceMap, - sendMessage + getStore ); } else if (field.type === 'RichText') { await updateRichTextField( @@ -287,8 +259,7 @@ export async function updateEntity( name, locale, depth, - referenceMap, - sendMessage + getStore ); } else if (field.type === 'ResourceLink') { //@TODO -- add live updates for resource links diff --git a/packages/live-preview-sdk/src/types.ts b/packages/live-preview-sdk/src/types.ts index a3589848..b0bc5052 100644 --- a/packages/live-preview-sdk/src/types.ts +++ b/packages/live-preview-sdk/src/types.ts @@ -1,7 +1,7 @@ import type { Asset, Entry } from 'contentful'; import type { ContentTypeProps } from 'contentful-management'; -import { SendMessage } from './helpers'; +import { EditorEntityStore } from '@contentful/visual-sdk'; export type ContentType = ContentTypeProps; export const ASSET_TYPENAME = 'Asset'; @@ -56,7 +56,9 @@ export interface CollectionItem { __typename?: string; } -export type ReferenceMap = Map; +export type EntityReferenceMap = Map; + +export type GetStore = (locale: string) => EditorEntityStore; export type UpdateEntryProps = { contentType: ContentType; @@ -64,9 +66,8 @@ export type UpdateEntryProps = { updateFromEntryEditor: Entry; locale: string; gqlParams?: GraphQLParams; - sendMessage: SendMessage; + getStore: GetStore; depth: number; - referenceMap: ReferenceMap; }; export type UpdateFieldProps = { @@ -75,9 +76,8 @@ export type UpdateFieldProps = { name: string; locale: string; gqlParams?: GraphQLParams; - sendMessage: SendMessage; + getStore: GetStore; depth: number; - referenceMap: ReferenceMap; }; export type UpdateReferenceFieldProps = { @@ -85,9 +85,8 @@ export type UpdateReferenceFieldProps = { updatedReference?: (Pick | Pick) & { __typename?: string }; locale: string; gqlParams?: GraphQLParams; - sendMessage: SendMessage; + getStore: GetStore; depth: number; - referenceMap: ReferenceMap; }; /** From a73451de451e9745b9790537bfe6d43717cce603 Mon Sep 17 00:00:00 2001 From: Chris Helgert Date: Fri, 27 Oct 2023 11:01:30 +0200 Subject: [PATCH 3/3] refactor(nested-updates): move depth handling around and decrease depth for richtext --- packages/live-preview-sdk/src/constants.ts | 1 + .../src/graphql/__tests__/entries.test.ts | 3 +- .../live-preview-sdk/src/graphql/entries.ts | 89 ++++++++++--------- .../src/helpers/resolveReference.ts | 6 +- packages/live-preview-sdk/src/liveUpdates.ts | 8 +- .../live-preview-sdk/src/rest/entities.ts | 57 +++++++----- packages/live-preview-sdk/src/types.ts | 6 +- 7 files changed, 97 insertions(+), 73 deletions(-) diff --git a/packages/live-preview-sdk/src/constants.ts b/packages/live-preview-sdk/src/constants.ts index 70657b26..d4df6514 100644 --- a/packages/live-preview-sdk/src/constants.ts +++ b/packages/live-preview-sdk/src/constants.ts @@ -9,6 +9,7 @@ export const TOOLTIP_HEIGHT = 32; export const TOOLTIP_PADDING_LEFT = 5; export const MAX_DEPTH = 10; +export const MAX_RTE_DEPTH = 2; export const LIVE_PREVIEW_EDITOR_SOURCE = 'live-preview-editor' as const; export const LIVE_PREVIEW_SDK_SOURCE = 'live-preview-sdk' as const; diff --git a/packages/live-preview-sdk/src/graphql/__tests__/entries.test.ts b/packages/live-preview-sdk/src/graphql/__tests__/entries.test.ts index f3a89db4..ad6e3902 100644 --- a/packages/live-preview-sdk/src/graphql/__tests__/entries.test.ts +++ b/packages/live-preview-sdk/src/graphql/__tests__/entries.test.ts @@ -8,6 +8,7 @@ import type { SysProps, Entity, ContentType, GetStore } from '../../types'; import { updateEntry } from '../entries'; import defaultContentTypeJSON from './fixtures/contentType.json'; import entry from './fixtures/entry.json'; +import { MAX_DEPTH } from '../../constants'; const EN = 'en-US'; @@ -58,7 +59,7 @@ describe('Update GraphQL Entry', () => { updateFromEntryEditor: update, locale, getStore, - depth: 0, + maxDepth: MAX_DEPTH, }); }; diff --git a/packages/live-preview-sdk/src/graphql/entries.ts b/packages/live-preview-sdk/src/graphql/entries.ts index 39f30767..c242f0ab 100644 --- a/packages/live-preview-sdk/src/graphql/entries.ts +++ b/packages/live-preview-sdk/src/graphql/entries.ts @@ -18,7 +18,7 @@ import { import { updateAsset } from './assets'; import { isRelevantField, updateAliasedInformation } from './queryUtils'; import { buildCollectionName, generateTypeName } from './utils'; -import { MAX_DEPTH } from '../constants'; +import { MAX_RTE_DEPTH } from '../constants'; /** * Updates GraphQL response data based on CMA entry object @@ -33,6 +33,7 @@ export async function updateEntry({ contentType, dataFromPreviewApp, updateFromEntryEditor, + maxDepth, ...props }: UpdateEntryProps): Promise { if (dataFromPreviewApp.sys.id !== updateFromEntryEditor.sys.id) { @@ -56,6 +57,7 @@ export async function updateEntry({ dataFromPreviewApp: copyOfDataFromPreviewApp, updateFromEntryEditor, name, + maxDepth: MAX_RTE_DEPTH, }); } else if (field.type === 'Link') { await updateSingleRefField({ @@ -63,6 +65,7 @@ export async function updateEntry({ dataFromPreviewApp: copyOfDataFromPreviewApp, updateFromEntryEditor, name, + maxDepth: maxDepth - 1, }); } else if (field.type === 'Array' && field.items?.type === 'Link') { await updateMultiRefField({ @@ -70,6 +73,7 @@ export async function updateEntry({ dataFromPreviewApp: copyOfDataFromPreviewApp, updateFromEntryEditor, name, + maxDepth: maxDepth - 1, }); } } @@ -100,7 +104,7 @@ async function processNode({ locale: string; getStore: GetStore; gqlParams?: GraphQLParams; - depth: number; + maxDepth: number; }) { // Check if the node is an embedded entity if (SUPPORTED_RICHTEXT_EMBEDS.includes(node.nodeType)) { @@ -173,7 +177,7 @@ async function processRichTextField({ locale: string; getStore: GetStore; gqlParams?: GraphQLParams; - depth: number; + maxDepth: number; }): Promise<{ entries: RichTextLink; assets: RichTextLink }> { const entries: RichTextLink = { block: [], inline: [], hyperlink: [] }; const assets: RichTextLink = { block: [], inline: [], hyperlink: [] }; @@ -202,7 +206,7 @@ async function updateRichTextField({ locale, gqlParams, getStore, - depth, + maxDepth, }: UpdateFieldProps) { if (!dataFromPreviewApp[name]) { dataFromPreviewApp[name] = {}; @@ -218,7 +222,7 @@ async function updateRichTextField({ locale, getStore, gqlParams, - depth, + maxDepth, }); } @@ -247,8 +251,24 @@ async function updateReferenceAssetField({ ); } -function isInDepthLimit(depth: number, gqlParams?: GraphQLParams): boolean { - return !!gqlParams || depth < MAX_DEPTH; +function isInDepthLimit(entityId: string, maxDepth: number, gqlParams?: GraphQLParams): boolean { + if (gqlParams) { + return true; + } + + if (maxDepth > 0) { + return true; + } + + debug.log( + 'Max update depth is reached, please provide the GraphQL query if you need deeper nested information.', + { entityId } + ); + return false; +} + +function getRichTextDepth(maxDepth: number) { + return Math.min(maxDepth - 1, MAX_RTE_DEPTH); } async function updateReferenceEntryField({ @@ -257,7 +277,7 @@ async function updateReferenceEntryField({ locale, gqlParams, getStore, - depth, + maxDepth, }: SetOptional, 'gqlParams'>) { const { reference, typeName } = await resolveReference({ referenceId: updatedReference.sys.id, @@ -287,12 +307,12 @@ async function updateReferenceEntryField({ richTextNode: value, locale, gqlParams, - depth, + maxDepth: getRichTextDepth(maxDepth), getStore, }); } - if ('sys' in value && isInDepthLimit(depth, gqlParams)) { + if ('sys' in value && isInDepthLimit(updatedReference.sys.id, maxDepth, gqlParams)) { // single reference merged[key] = value; await updateSingleRefField({ @@ -302,10 +322,14 @@ async function updateReferenceEntryField({ name: key, gqlParams, getStore, - depth: depth + 1, + maxDepth: maxDepth - 1, }); } - } else if (Array.isArray(value) && value[0]?.sys && isInDepthLimit(depth, gqlParams)) { + } else if ( + Array.isArray(value) && + value[0]?.sys && + isInDepthLimit(updatedReference.sys.id, maxDepth, gqlParams) + ) { // multi references const name = buildCollectionName(key); merged[name] = { items: value }; @@ -316,7 +340,7 @@ async function updateReferenceEntryField({ name: key, gqlParams, getStore, - depth: depth + 1, + maxDepth: maxDepth - 1, }); } else { // primitive fields @@ -330,10 +354,7 @@ async function updateReferenceEntryField({ async function updateReferenceField({ referenceFromPreviewApp, updatedReference, - locale, - gqlParams, - getStore, - depth, + ...props }: UpdateReferenceFieldProps) { if (!updatedReference) { return null; @@ -350,22 +371,16 @@ async function updateReferenceField({ if (isAsset(updatedReference)) { return updateReferenceAssetField({ + ...props, referenceFromPreviewApp, updatedReference, - locale, - gqlParams, - getStore, - depth, }); } return updateReferenceEntryField({ + ...props, referenceFromPreviewApp, updatedReference, - locale, - gqlParams, - getStore, - depth, }); } @@ -373,21 +388,17 @@ async function updateSingleRefField({ dataFromPreviewApp, updateFromEntryEditor, name, - locale, - gqlParams, - getStore, - depth, + maxDepth, + ...props }: UpdateFieldProps) { const updatedReference = updateFromEntryEditor?.fields?.[name] as Asset | Entry | undefined; dataFromPreviewApp[name] = await updateReferenceField({ + ...props, referenceFromPreviewApp: dataFromPreviewApp[name] as Entry & { __typename?: string; }, updatedReference, - locale, - gqlParams, - getStore, - depth, + maxDepth: maxDepth - 1, }); } @@ -395,10 +406,8 @@ async function updateMultiRefField({ dataFromPreviewApp, updateFromEntryEditor, name, - locale, - gqlParams, - getStore, - depth, + maxDepth, + ...props }: UpdateFieldProps) { const fieldName = buildCollectionName(name); @@ -410,14 +419,12 @@ async function updateMultiRefField({ )?.items?.find((item) => item.sys.id === updatedItem.sys.id); const result = await updateReferenceField({ + ...props, referenceFromPreviewApp: itemFromPreviewApp as unknown as Entry & { __typename?: string; }, updatedReference: updatedItem, - locale, - gqlParams, - getStore, - depth, + maxDepth: maxDepth - 1, }); return result; diff --git a/packages/live-preview-sdk/src/helpers/resolveReference.ts b/packages/live-preview-sdk/src/helpers/resolveReference.ts index 647949b5..d91c6a4d 100644 --- a/packages/live-preview-sdk/src/helpers/resolveReference.ts +++ b/packages/live-preview-sdk/src/helpers/resolveReference.ts @@ -30,8 +30,10 @@ export async function resolveReference({ locale: string; getStore: GetStore; }): Promise<{ reference: Entry | Asset; typeName: string }> { + const store = getStore(locale); + if (isAsset) { - const result = await getStore(locale).fetchAsset(referenceId); + const result = await store.fetchAsset(referenceId); if (!result) { throw new Error(`Unknown reference ${referenceId}`); } @@ -42,7 +44,7 @@ export async function resolveReference({ }; } - const result = await getStore(locale).fetchEntry(referenceId); + const result = await store.fetchEntry(referenceId); if (!result) { throw new Error(`Unknown reference ${referenceId}`); } diff --git a/packages/live-preview-sdk/src/liveUpdates.ts b/packages/live-preview-sdk/src/liveUpdates.ts index f933b508..a18fd1fb 100644 --- a/packages/live-preview-sdk/src/liveUpdates.ts +++ b/packages/live-preview-sdk/src/liveUpdates.ts @@ -26,6 +26,7 @@ import { GraphQLParams, } from './types'; import { EditorEntityStore } from '@contentful/visual-sdk'; +import { MAX_DEPTH } from './constants'; interface MergeEntityProps { dataFromPreviewApp: Entity; @@ -54,6 +55,7 @@ export class LiveUpdates { this.defaultLocale = locale; this.sendMessage = (method, data) => sendMessageToEditor(method, data, targetOrigin); this.storage = new StorageMap('live-updates', new Map()); + this.getStore = this.getStore.bind(this); window.addEventListener('beforeunload', () => this.clearStorage()); } @@ -97,8 +99,6 @@ export class LiveUpdates { data: Entity; updated: boolean; }> { - const depth = 0; - if ('__typename' in dataFromPreviewApp) { // GraphQL const data = await (dataFromPreviewApp.__typename === 'Asset' @@ -109,7 +109,7 @@ export class LiveUpdates { updateFromEntryEditor: updateFromEntryEditor as Entry, locale, gqlParams, - depth, + maxDepth: MAX_DEPTH, getStore: this.getStore, })); @@ -127,7 +127,7 @@ export class LiveUpdates { dataFromPreviewApp as Entry, updateFromEntryEditor as Entry, locale, - depth, + MAX_DEPTH, this.getStore ), updated: true, diff --git a/packages/live-preview-sdk/src/rest/entities.ts b/packages/live-preview-sdk/src/rest/entities.ts index 5ab054f4..939fbdfe 100644 --- a/packages/live-preview-sdk/src/rest/entities.ts +++ b/packages/live-preview-sdk/src/rest/entities.ts @@ -1,7 +1,7 @@ import { Asset, Entry } from 'contentful'; import type { WithResourceName } from 'contentful-management'; -import { MAX_DEPTH } from '../constants'; +import { MAX_RTE_DEPTH } from '../constants'; import { debug, clone, isPrimitiveField, resolveReference, updatePrimitiveField } from '../helpers'; import { SUPPORTED_RICHTEXT_EMBEDS, @@ -26,6 +26,14 @@ function getFieldName(contentType: ContentType, field: ContentType['fields'][num return field.apiName || field.name; } +function isInDepthLimit(maxDepth: number): boolean { + return maxDepth >= 0; +} + +function getRichTextDepth(maxDepth: number) { + return Math.min(maxDepth - 1, MAX_RTE_DEPTH); +} + /** * Update the reference from the entry editor with the information from the store. * If the information is not yet available, it send a message to the editor to retrieve it. @@ -34,7 +42,7 @@ async function updateRef( dataFromPreviewApp: Reference | undefined, updateFromEntryEditor: Reference, locale: string, - depth: number, + maxDepth: number, getStore: GetStore ): Promise { const { reference } = await resolveReference({ @@ -55,23 +63,23 @@ async function updateRef( const value = reference.fields[key as keyof typeof reference.fields]; // single ref fields - if (isEntityLink(value) && depth < MAX_DEPTH) { + if (isEntityLink(value) && isInDepthLimit(maxDepth)) { await updateSingleRefField( result, reference, locale, key as keyof Reference['fields'], - depth + 1, + maxDepth - 1, getStore ); // multi ref fields - } else if (Array.isArray(value) && isEntityLink(value[0]) && depth < MAX_DEPTH) { + } else if (Array.isArray(value) && isEntityLink(value[0]) && isInDepthLimit(maxDepth)) { await updateMultiRefField( result, reference, locale, key as keyof Reference['fields'], - depth + 1, + maxDepth - 1, getStore ); // rich text fields @@ -81,7 +89,7 @@ async function updateRef( reference as Entry, key, locale, - depth + 1, + getRichTextDepth(maxDepth), getStore ); // single and multi resource link fields @@ -108,7 +116,7 @@ async function updateMultiRefField( updateFromEntryEditor: Reference, locale: string, name: keyof Reference['fields'], - depth: number, + maxDepth: number, getStore: GetStore ) { if (!updateFromEntryEditor.fields?.[name]) { @@ -123,7 +131,7 @@ async function updateMultiRefField( (dataFromPreviewApp.fields[name] as Reference[])?.[index], updateFromEntryReference, locale, - depth + 1, + maxDepth - 1, getStore ) ) @@ -138,7 +146,7 @@ async function updateSingleRefField( updateFromEntryEditor: Reference, locale: string, name: keyof Reference['fields'], - depth: number, + maxDepth: number, getStore: GetStore ) { const matchUpdateFromEntryEditor = updateFromEntryEditor?.fields?.[name] as Reference | undefined; @@ -154,14 +162,19 @@ async function updateSingleRefField( dataFromPreviewApp.fields[name] as Reference | undefined, matchUpdateFromEntryEditor, locale, - depth + 1, + maxDepth - 1, getStore ); return dataFromPreviewApp; } -async function resolveRichTextLinks(node: any, locale: string, depth: number, getStore: GetStore) { +async function resolveRichTextLinks( + node: any, + locale: string, + maxDepth: number, + getStore: GetStore +) { if (SUPPORTED_RICHTEXT_EMBEDS.includes(node.nodeType)) { if (node.data && node.data.target && node.data.target.sys) { if (node.data.target.sys.linkType === 'Entry' || node.data.target.sys.linkType === 'Asset') { @@ -174,7 +187,7 @@ async function resolveRichTextLinks(node: any, locale: string, depth: number, ge undefined, updatedReference, locale, - depth + 1, + maxDepth - 1, getStore ); } @@ -183,7 +196,7 @@ async function resolveRichTextLinks(node: any, locale: string, depth: number, ge if (node.content) { for (const childNode of node.content) { - await resolveRichTextLinks(childNode, locale, depth + 1, getStore); + await resolveRichTextLinks(childNode, locale, maxDepth - 1, getStore); } } } @@ -193,7 +206,7 @@ async function updateRichTextField( updateFromEntryEditor: Entry, name: string, locale: string, - depth: number, + maxDepth: number, getStore: GetStore ) { const richText = updateFromEntryEditor.fields?.[name]; @@ -203,7 +216,7 @@ async function updateRichTextField( dataFromPreviewApp.fields[name] = richText; // Resolve the linked entries or assets within the rich text field for (const node of richText.content) { - await resolveRichTextLinks(node, locale, depth, getStore); + await resolveRichTextLinks(node, locale, maxDepth, getStore); } } } @@ -218,7 +231,7 @@ export async function updateEntity( dataFromPreviewApp: Entry, updateFromEntryEditor: Entry | Asset, locale: string, - depth: number, + maxDepth: number, getStore: GetStore ): Promise { if (dataFromPreviewApp.sys.id !== updateFromEntryEditor.sys.id) { @@ -234,22 +247,22 @@ export async function updateEntity( updateFromEntryEditor, name, }); - } else if (field.type === 'Link' && depth < MAX_DEPTH) { + } else if (field.type === 'Link' && isInDepthLimit(maxDepth)) { await updateSingleRefField( dataFromPreviewApp, updateFromEntryEditor, locale, name as keyof Reference['fields'], - depth + 1, + maxDepth - 1, getStore ); - } else if (field.type === 'Array' && field.items?.type === 'Link' && depth < MAX_DEPTH) { + } else if (field.type === 'Array' && field.items?.type === 'Link' && isInDepthLimit(maxDepth)) { await updateMultiRefField( dataFromPreviewApp, updateFromEntryEditor, locale, name as keyof Reference['fields'], - depth + 1, + maxDepth - 1, getStore ); } else if (field.type === 'RichText') { @@ -258,7 +271,7 @@ export async function updateEntity( updateFromEntryEditor as Entry, name, locale, - depth, + MAX_RTE_DEPTH, getStore ); } else if (field.type === 'ResourceLink') { diff --git a/packages/live-preview-sdk/src/types.ts b/packages/live-preview-sdk/src/types.ts index b0bc5052..4b4471e2 100644 --- a/packages/live-preview-sdk/src/types.ts +++ b/packages/live-preview-sdk/src/types.ts @@ -67,7 +67,7 @@ export type UpdateEntryProps = { locale: string; gqlParams?: GraphQLParams; getStore: GetStore; - depth: number; + maxDepth: number; }; export type UpdateFieldProps = { @@ -77,7 +77,7 @@ export type UpdateFieldProps = { locale: string; gqlParams?: GraphQLParams; getStore: GetStore; - depth: number; + maxDepth: number; }; export type UpdateReferenceFieldProps = { @@ -86,7 +86,7 @@ export type UpdateReferenceFieldProps = { locale: string; gqlParams?: GraphQLParams; getStore: GetStore; - depth: number; + maxDepth: number; }; /**