diff --git a/plugins/payload-client-map.ts b/plugins/payload-client-map.ts index c5a223f..432cf86 100644 --- a/plugins/payload-client-map.ts +++ b/plugins/payload-client-map.ts @@ -1,4 +1,10 @@ import { Info } from "~/shared/utils/luxon"; +import { ClientEntityNew } from "~/utils/client-user"; + +const typeMap: Record> = { + "user": ClientUser, +}; +const classMap = new Map(Object.entries(typeMap).map(([k, v]) => [v, k])); export default definePayloadPlugin(() => { definePayloadReducer( @@ -7,8 +13,10 @@ export default definePayloadPlugin(() => { if (!(data instanceof ClientMap)) { return; } + const type = classMap.get(data.EntityClass)!; const accountStore = useAccountStore(); return { + type, timezone: accountStore.activeTimezone, locale: accountStore.activeLocale, api: data.toApi(false), @@ -17,9 +25,10 @@ export default definePayloadPlugin(() => { ); definePayloadReviver( "ClientMap", - ({ timezone, locale, api }) => { + ({ type, timezone, locale, api }) => { + const EntityClass = typeMap[type]; const zone = Info.normalizeZone(timezone); - return ClientMap.fromApi(api, { zone, locale }) + return ClientMap.fromApi(EntityClass, api, { zone, locale }) }, ); }); diff --git a/stores/users.ts b/stores/users.ts index 8d50df4..11c17c3 100644 --- a/stores/users.ts +++ b/stores/users.ts @@ -33,10 +33,7 @@ export const useUsersStore = defineStore("users", () => { const promise = (async () => { try { const apiUsers = await requestFetch("/api/users", { signal: controller.signal }); - state.users.value.apiUpdate({ - type: "user", - entities: apiUsers, - }, { zone, locale }); + state.users.value.apiUpdate(apiUsers, { zone, locale }); state.pendingSync.value = undefined; state.fetched.value = true; return state.users; @@ -70,10 +67,7 @@ export const useUsersStore = defineStore("users", () => { console.log("appyling", event.data) const zone = Info.normalizeZone(accountStore.activeTimezone); const locale = accountStore.activeLocale; - state.users.value.apiUpdate({ - type: "user", - entities: [event.data.data], - }, { zone, locale }); + state.users.value.apiUpdate([event.data.data], { zone, locale }); }); return { diff --git a/utils/client-map.nuxt.test.ts b/utils/client-map.nuxt.test.ts index 5ec141e..426b895 100644 --- a/utils/client-map.nuxt.test.ts +++ b/utils/client-map.nuxt.test.ts @@ -27,34 +27,31 @@ function fixtureClientMap() { ); } -function fixtureApiMap(): ApiMap { - return { - type: "user", - entities: [ - { - id: 1, - updatedAt: nowIso, - name: "A", - type: "regular", - }, - { - id: 2, - updatedAt: nowIso, - name: "B", - type: "regular", - }, - { - id: 5, - updatedAt: nowIso, - deleted: true, - }, - ], - }; +function fixtureApiMap(): ApiUser[] { + return [ + { + id: 1, + updatedAt: nowIso, + name: "A", + type: "regular", + }, + { + id: 2, + updatedAt: nowIso, + name: "B", + type: "regular", + }, + { + id: 5, + updatedAt: nowIso, + deleted: true, + }, + ]; } describe("class ClientMap", () => { test("load from api", () => { - const map = ClientMap.fromApi(fixtureApiMap(), { zone, locale }) + const map = ClientMap.fromApi(ClientUser, fixtureApiMap(), { zone, locale }) expect(map).toStrictEqual(fixtureClientMap()); }); @@ -202,7 +199,7 @@ describe("class ClientMap", () => { } else { throw new Error(`Unknown action pattern ${action}`) } - map.apiUpdate({ type: "user", entities: [update] }, { zone, locale }); + map.apiUpdate([update], { zone, locale }); // Check const expectedUser = userFromPattern(expectedPattern, useSameTimestamp); const expectedTomb = tombFromPattern(expectedPattern, useSameTimestamp); @@ -243,7 +240,7 @@ describe("class ClientMap", () => { } else { throw new Error(`Unknown action pattern ${action}`) } - map.apiUpdate({ type: "user", entities: [update] }, { zone, locale }); + map.apiUpdate([update], { zone, locale }); // Check const expectedUser = userFromPattern(startPattern, useSameTimestamp); const expectedTomb = tombFromPattern(startPattern, useSameTimestamp); diff --git a/utils/client-map.ts b/utils/client-map.ts index 32f1054..72f0a58 100644 --- a/utils/client-map.ts +++ b/utils/client-map.ts @@ -2,23 +2,11 @@ import { type Entity, type EntityLiving, type Id, type Living } from "~/shared/t import { DateTime, Zone } from "~/shared/utils/luxon"; import { ClientEntityNew } from "~/utils/client-user"; -export interface ApiMap { - type: string, - entities: T[] -} - -interface EntityClass { - name: string, - type: string, +export interface EntityClass { fromApi(api: EntityLiving, opts: { zone: Zone, locale: string }): T, } - export class ClientMap { - static typeMap: Record> = { - user: ClientUser, - }; - constructor( public EntityClass: EntityClass, public map: Map, @@ -60,27 +48,24 @@ export class ClientMap { } } - static fromApi( - api: ApiMap, + static fromApi( + EntityClass: EntityClass, + entities: T[], opts: { zone: Zone, locale: string }, ) { - const EntityClass = this.typeMap[api.type]; - const entities = api.entities.filter(entity => !entity.deleted) as Living[]; - const tombstones = api.entities.filter(entity => entity.deleted === true); + const living = entities.filter(entity => !entity.deleted) as Living[]; + const tombstones = entities.filter(entity => entity.deleted === true); return new this( EntityClass, - idMap(entities.map(apiEntity => EntityClass.fromApi(apiEntity, opts))), + idMap(living.map(apiEntity => EntityClass.fromApi(apiEntity, opts))), new Map(tombstones.map(tombstone => [tombstone.id, Date.parse(tombstone.updatedAt)])), ); } - apiUpdate(api: ApiMap, opts: { zone: Zone, locale: string }) { - if (api.type !== this.EntityClass.type) { - throw new Error(`ClientMap: Map of ${this.EntityClass.name} received update for ${api.type}.`); - } - const entities = api.entities.filter(entity => !entity.deleted); - const tombstones = api.entities.filter(entity => entity.deleted === true); - for (const entity of entities) { + apiUpdate(entities: Entity[], opts: { zone: Zone, locale: string }) { + const living = entities.filter(entity => !entity.deleted); + const tombstones = entities.filter(entity => entity.deleted === true); + for (const entity of living) { const tombstoneMs = this.tombstones.get(entity.id); const updatedMs = Date.parse(entity.updatedAt); if (tombstoneMs !== undefined) { @@ -120,37 +105,31 @@ export class ClientMap { } } - toApi(diff: boolean): ApiMap { + toApi(diff: boolean): Entity[] { if (!diff) { - return { - type: this.EntityClass.type, - entities: [ - ...[...this.map.values()].map(entity => entity.toApi()), - ...[...this.tombstones].map(([id, updatedMs]) => ({ - id, - updatedAt: new Date(updatedMs).toISOString(), - deleted: true as const, - })) - ], - }; + return [ + ...[...this.map.values()].map(entity => entity.toApi()), + ...[...this.tombstones].map(([id, updatedMs]) => ({ + id, + updatedAt: new Date(updatedMs).toISOString(), + deleted: true as const, + })), + ]; } - return { - type: this.EntityClass.type, - entities: [ - ...[...this.map.values()] - .filter(entity => entity.isModified() && !entity.deleted) - .map(entity => entity.toApi()) - , - ...[...this.map.values()] - .filter(entity => entity.deleted) - .map(entity => ({ - id: entity.id, - updatedAt: toIso(entity.updatedAt), - deleted: true as const, - })) - , - ], - }; + return [ + ...[...this.map.values()] + .filter(entity => entity.isModified() && !entity.deleted) + .map(entity => entity.toApi()) + , + ...[...this.map.values()] + .filter(entity => entity.deleted) + .map(entity => ({ + id: entity.id, + updatedAt: toIso(entity.updatedAt), + deleted: true as const, + })) + , + ]; } }