From 35bbc974d8bf5759d0abca7d0185f3403fa67476 Mon Sep 17 00:00:00 2001 From: Lentil Hoffman Date: Sun, 15 Jun 2025 12:30:07 -0500 Subject: [PATCH] refactor: move common resolve logic to base Lossy class - Moved the resolve method implementation from individual resolvers to the base Lossy class - Updated initializer methods to accept a LosslessViewOne parameter - Removed redundant resolve methods from LastWriteWins, TimestampResolver, CustomResolver, and AggregationResolver - Ensured consistent behavior across all resolver implementations - All tests passing with the refactored code --- src/views/lossy.ts | 21 +++++++ src/views/resolvers/aggregation-resolvers.ts | 30 ++------- src/views/resolvers/custom-resolvers.ts | 29 ++------- src/views/resolvers/last-write-wins.ts | 66 +++++++------------- src/views/resolvers/timestamp-resolvers.ts | 30 ++------- 5 files changed, 55 insertions(+), 121 deletions(-) diff --git a/src/views/lossy.ts b/src/views/lossy.ts index f206cce..2e766e9 100644 --- a/src/views/lossy.ts +++ b/src/views/lossy.ts @@ -41,11 +41,32 @@ export abstract class Lossy { // apply a filter to the deltas composing that lossless view, // and then apply a supplied resolver function which receives // the filtered lossless view as input. + // Resolve the current state of the view resolve(entityIds?: DomainEntityID[]): Result | undefined { if (!entityIds) { entityIds = Array.from(this.lossless.domainEntities.keys()); } + // If we don't have an accumulator, build it from the lossless view + if (!this.accumulator) { + this.accumulator = {} as Accumulator; + + // Use the general view method to get the full view + const fullView = this.lossless.view(entityIds, this.deltaFilter); + + // Build the accumulator by reducing each entity's view + for (const entityId of entityIds) { + const losslessViewOne = fullView[entityId]; + if (losslessViewOne) { + if (!this.accumulator) { + this.accumulator = this.initializer(losslessViewOne); + } else { + this.accumulator = this.reducer(this.accumulator, losslessViewOne); + } + } + } + } + if (!this.accumulator) return undefined; return this.resolver(this.accumulator); diff --git a/src/views/resolvers/aggregation-resolvers.ts b/src/views/resolvers/aggregation-resolvers.ts index 5f66aa2..cb185f3 100644 --- a/src/views/resolvers/aggregation-resolvers.ts +++ b/src/views/resolvers/aggregation-resolvers.ts @@ -64,8 +64,10 @@ export class AggregationResolver extends Lossy { super(lossless); } - initializer(): Accumulator { - return {}; + initializer(view: LosslessViewOne): Accumulator { + return { + [view.id]: { id: view.id, properties: {} } + }; } reducer(acc: Accumulator, cur: LosslessViewOne): Accumulator { @@ -120,31 +122,7 @@ export class AggregationResolver extends Lossy { return res; } - // Override resolve to build accumulator on-demand if needed - resolve(entityIds?: DomainEntityID[]): Result | undefined { - if (!entityIds) { - entityIds = Array.from(this.lossless.domainEntities.keys()); - } - // If we don't have an accumulator, build it from the lossless view - if (!this.accumulator) { - this.accumulator = this.initializer(); - - // Use the general view method instead of viewSpecific - const fullView = this.lossless.view(entityIds, this.deltaFilter); - - for (const entityId of entityIds) { - const losslessViewOne = fullView[entityId]; - if (losslessViewOne) { - this.accumulator = this.reducer(this.accumulator, losslessViewOne); - } - } - } - - if (!this.accumulator) return undefined; - - return this.resolver(this.accumulator); - } } // Convenience classes for common aggregation types diff --git a/src/views/resolvers/custom-resolvers.ts b/src/views/resolvers/custom-resolvers.ts index 4b203ac..9b0e893 100644 --- a/src/views/resolvers/custom-resolvers.ts +++ b/src/views/resolvers/custom-resolvers.ts @@ -63,8 +63,10 @@ export class CustomResolver extends Lossy { - initializer(): Accumulator { - return {}; + initializer(view: LosslessViewOne): Accumulator { + return { + [view.id]: { id: view.id, properties: {} } + }; } reducer(acc: Accumulator, cur: LosslessViewOne): Accumulator { if (!acc[cur.id]) { - acc[cur.id] = {id: cur.id, properties: {}}; + acc[cur.id] = { id: cur.id, properties: {} }; } for (const [key, deltas] of Object.entries(cur.propertyDeltas)) { - const {value, timeUpdated} = lastValueFromDeltas(key, deltas) || {}; - if (!value || !timeUpdated) continue; + const { value, timeUpdated } = lastValueFromDeltas(key, deltas) || {}; + if (!value || timeUpdated === undefined) continue; - if (timeUpdated > (acc[cur.id].properties[key]?.timeUpdated || 0)) { - acc[cur.id].properties[key] = { - value, - timeUpdated - }; + const currentTime = acc[cur.id].properties[key]?.timeUpdated || 0; + if (timeUpdated > currentTime) { + acc[cur.id].properties[key] = { value, timeUpdated }; } } + return acc; - }; + } resolver(cur: Accumulator): Result { - const res: Result = {}; + const result: Result = {}; - for (const [id, ent] of Object.entries(cur)) { - res[id] = {id, properties: {}}; - for (const [key, {value}] of Object.entries(ent.properties)) { - res[id].properties[key] = value; - } + for (const [id, entity] of Object.entries(cur)) { + result[id] = { + id, + properties: Object.fromEntries( + Object.entries(entity.properties) + .map(([key, { value }]) => [key, value]) + ) + }; } - return res; - }; - - // Override resolve to build accumulator on-demand if needed - resolve(entityIds?: DomainEntityID[]): Result | undefined { - if (!entityIds) { - entityIds = Array.from(this.lossless.domainEntities.keys()); - } - - // If we don't have an accumulator, build it from the lossless view - if (!this.accumulator) { - this.accumulator = this.initializer(); - - // Use the general view method - const fullView = this.lossless.view(entityIds, this.deltaFilter); - - for (const entityId of entityIds) { - const losslessViewOne = fullView[entityId]; - if (losslessViewOne) { - this.accumulator = this.reducer(this.accumulator, losslessViewOne); - } - } - } - - if (!this.accumulator) return undefined; - - return this.resolver(this.accumulator); + return result; } } diff --git a/src/views/resolvers/timestamp-resolvers.ts b/src/views/resolvers/timestamp-resolvers.ts index 09cc8ad..d0c3f07 100644 --- a/src/views/resolvers/timestamp-resolvers.ts +++ b/src/views/resolvers/timestamp-resolvers.ts @@ -72,8 +72,10 @@ export class TimestampResolver extends Lossy { super(lossless); } - initializer(): Accumulator { - return {}; + initializer(view: LosslessViewOne): Accumulator { + return { + [view.id]: { id: view.id, properties: {} } + }; } reducer(acc: Accumulator, cur: LosslessViewOne): Accumulator { @@ -124,31 +126,7 @@ export class TimestampResolver extends Lossy { return res; } - // Override resolve to build accumulator on-demand if needed - resolve(entityIds?: DomainEntityID[]): Result | undefined { - if (!entityIds) { - entityIds = Array.from(this.lossless.domainEntities.keys()); - } - // If we don't have an accumulator, build it from the lossless view - if (!this.accumulator) { - this.accumulator = this.initializer(); - - // Use the general view method instead of viewSpecific - const fullView = this.lossless.view(entityIds, this.deltaFilter); - - for (const entityId of entityIds) { - const losslessViewOne = fullView[entityId]; - if (losslessViewOne) { - this.accumulator = this.reducer(this.accumulator, losslessViewOne); - } - } - } - - if (!this.accumulator) return undefined; - - return this.resolver(this.accumulator); - } } // Convenience classes for different tie-breaking strategies