diff --git a/packages/visual-sdk/src/store/EditorEntityStore.ts b/packages/visual-sdk/src/store/EditorEntityStore.ts index ac492e6b..290d5d07 100644 --- a/packages/visual-sdk/src/store/EditorEntityStore.ts +++ b/packages/visual-sdk/src/store/EditorEntityStore.ts @@ -67,30 +67,20 @@ export class EditorEntityStore extends EntityStore { return id.length === 1 ? id[0] : id.join(this.cacheIdSeperator); } - private findMissingEntites(ids: string[]) { - const missing = []; - - for (const id of ids) { - const entry = this.entitiesMap.get(id); - if (!entry) { - missing.push(id); - } - } - - return missing; - } - - private async fetchEntity(ids: string[]): Promise>; - private async fetchEntity(ids: string[], isAsset: true): Promise>; - private async fetchEntity(ids: string[], isAsset?: boolean): Promise> { - const missingIds = this.findMissingEntites(ids); - - if (missingIds.length === 0) { + private async fetchEntity(type: 'Asset', ids: string[]): Promise>; + private async fetchEntity(type: 'Entry', ids: string[]): Promise>; + private async fetchEntity( + type: 'Asset' | 'Entry', + ids: string[] + ): Promise | Array> { + const { missing, resolved } = this.getEntitiesFromMap(type, ids); + + if (missing.length === 0) { // everything is already in cache - return ids.map((id) => this.entitiesMap.get(id)) as Array; + return resolved as Array | Array; } - const cacheId = this.getCacheId(missingIds); + const cacheId = this.getCacheId(missing); const openRequest = this.requestCache.get(cacheId); if (openRequest) { @@ -101,7 +91,7 @@ export class EditorEntityStore extends EntityStore { const unsubscribe = this.subscribe( PostMessageMethods.REQUESTED_ENTITIES, (message: RequestedEntitiesMessage) => { - if (missingIds.every((id) => message.entities.find((entity) => entity.sys.id === id))) { + if (missing.every((id) => message.entities.find((entity) => entity.sys.id === id))) { clearTimeout(timeout); resolve(message.entities); @@ -119,8 +109,8 @@ export class EditorEntityStore extends EntityStore { }, this.timeoutDuration); this.sendMessage(PostMessageMethods.REQUEST_ENTITIES, { - entityIds: missingIds, - entityType: isAsset ? 'Asset' : 'Entry', + entityIds: missing, + entityType: type, locale: this.locale, }); }); @@ -130,13 +120,13 @@ export class EditorEntityStore extends EntityStore { this.requestCache.set(cid, newPromise); }); - const result = (await newPromise) as Array; + const result = (await newPromise) as Array | Array; result.forEach((value) => { - this.entitiesMap.set(value.sys.id, value); + this.addEntity(value); }); - return ids.map((id) => this.entitiesMap.get(id)) as Array; + return this.getEntitiesFromMap(type, ids).resolved as Array | Array; } public async fetchAsset(id: string): Promise { @@ -150,7 +140,7 @@ export class EditorEntityStore extends EntityStore { } public fetchAssets(ids: string[]): Promise { - return this.fetchEntity(ids, true); + return this.fetchEntity('Asset', ids); } public async fetchEntry(id: string): Promise { @@ -164,6 +154,6 @@ export class EditorEntityStore extends EntityStore { } public fetchEntries(ids: string[]): Promise { - return this.fetchEntity(ids); + return this.fetchEntity('Entry', ids); } } diff --git a/packages/visual-sdk/src/store/EditorStore.spec.ts b/packages/visual-sdk/src/store/EditorStore.spec.ts index 6ecde8c8..27789b1d 100644 --- a/packages/visual-sdk/src/store/EditorStore.spec.ts +++ b/packages/visual-sdk/src/store/EditorStore.spec.ts @@ -22,6 +22,21 @@ describe('EntityStore', () => { expect(createStore().entities).toEqual(entities); }); + it('prevents id collision for assets and entries', async () => { + const patchedAsset = { ...asset, sys: { ...asset.sys, id: '1' } }; + const patchedEntry = { ...entry, sys: { ...entry.sys, id: '1' } }; + + const store = new EntityStore({ entities: [patchedAsset, patchedEntry], locale }); + + const [resolvedAsset, resolvedEntry] = await Promise.all([ + store.fetchAsset('1'), + store.fetchEntry('1'), + ]); + + expect(resolvedAsset).toEqual(patchedAsset); + expect(resolvedEntry).toEqual(patchedEntry); + }); + describe('getValue', () => { it('should return the value based on entityId and path', () => { const store = createStore(); diff --git a/packages/visual-sdk/src/store/EntityStore.ts b/packages/visual-sdk/src/store/EntityStore.ts index fc01e89b..a4aa33bb 100644 --- a/packages/visual-sdk/src/store/EntityStore.ts +++ b/packages/visual-sdk/src/store/EntityStore.ts @@ -8,28 +8,33 @@ import { get } from './utils'; */ export class EntityStore { protected locale: string; - protected entitiesMap: Map; + protected entryMap = new Map(); + protected assetMap = new Map(); constructor({ entities, locale }: { entities: Array; locale: string }) { - this.entitiesMap = new Map(entities.map((entity) => [entity.sys.id, entity])); this.locale = locale; + + for (const entity of entities) { + this.addEntity(entity); + } } public get entities() { - return [...this.entitiesMap.values()]; + return [...this.entryMap.values(), ...this.assetMap.values()]; } public updateEntity(entity: Entry | Asset) { - this.entitiesMap.set(entity.sys.id, entity); + this.addEntity(entity); } public getValue( entityLink: UnresolvedLink<'Entry' | 'Asset'>, path: string[] ): string | undefined { - const entity = this.entitiesMap.get(entityLink.sys.id); + entityLink.sys.type; + const entity = this.getEntity(entityLink.sys.linkType, entityLink.sys.id); - if (!entity || entity.sys.type !== entityLink.sys.linkType) { + if (!entity) { // TODO: move to `debug` utils once it is extracted console.warn(`Unresolved entity reference: ${entityLink}`); return; @@ -38,50 +43,79 @@ export class EntityStore { return get(entity, path); } - private getEntitiesFromMap(ids: string[]) { - const entity = []; - const missingEntityIds = []; + protected getEntitiesFromMap(type: 'Entry' | 'Asset', ids: string[]) { + const resolved = []; + const missing = []; for (const id of ids) { - const entry = this.entitiesMap.get(id); - if (entry) { - entity.push(entry); + const entity = this.getEntity(type, id); + if (entity) { + resolved.push(entity); } else { - missingEntityIds.push(id); + missing.push(id); } } - if (missingEntityIds.length) { - throw new Error(`Missing entity in the store (${missingEntityIds.join(',')})`); - } + return { + resolved, + missing, + }; + } - return entity as Array; + protected addEntity(entity: Entry | Asset): void { + if (this.isAsset(entity)) { + this.assetMap.set(entity.sys.id, entity); + } else { + this.entryMap.set(entity.sys.id, entity); + } } public async fetchAsset(id: string): Promise { - try { - return this.getEntitiesFromMap([id])[0] as Asset; - } catch (err) { + const { resolved, missing } = this.getEntitiesFromMap('Asset', [id]); + if (missing.length) { // TODO: move to `debug` utils once it is extracted console.warn(`Asset "${id}" is not in the store`); return undefined; } + + return resolved[0] as Asset; } public async fetchAssets(ids: string[]): Promise { - return this.getEntitiesFromMap(ids) as Asset[]; + const { resolved, missing } = this.getEntitiesFromMap('Asset', ids); + if (missing.length) { + throw new Error(`Missing assets in the store (${missing.join(',')})`); + } + return resolved as Asset[]; } public async fetchEntry(id: string): Promise { - try { - return this.getEntitiesFromMap([id])[0] as Entry; - } catch (err) { + const { resolved, missing } = this.getEntitiesFromMap('Entry', [id]); + if (missing.length) { // TODO: move to `debug` utils once it is extracted console.warn(`Entry "${id}" is not in the store`); return undefined; } + + return resolved[0] as Entry; } public async fetchEntries(ids: string[]): Promise { - return this.getEntitiesFromMap(ids) as Entry[]; + const { resolved, missing } = this.getEntitiesFromMap('Entry', ids); + if (missing.length) { + throw new Error(`Missing assets in the store (${missing.join(',')})`); + } + return resolved as Entry[]; + } + + private isAsset(entity: Entry | Asset): entity is Asset { + return entity.sys.type === 'Asset'; + } + + private getEntity(type: 'Asset' | 'Entry', id: string) { + if (type === 'Asset') { + return this.assetMap.get(id); + } + + return this.entryMap.get(id); } }