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
This commit is contained in:
parent
dd8987563a
commit
35bbc974d8
@ -41,11 +41,32 @@ export abstract class Lossy<Accumulator, Result> {
|
|||||||
// apply a filter to the deltas composing that lossless view,
|
// apply a filter to the deltas composing that lossless view,
|
||||||
// and then apply a supplied resolver function which receives
|
// and then apply a supplied resolver function which receives
|
||||||
// the filtered lossless view as input.
|
// the filtered lossless view as input.
|
||||||
|
// Resolve the current state of the view
|
||||||
resolve(entityIds?: DomainEntityID[]): Result | undefined {
|
resolve(entityIds?: DomainEntityID[]): Result | undefined {
|
||||||
if (!entityIds) {
|
if (!entityIds) {
|
||||||
entityIds = Array.from(this.lossless.domainEntities.keys());
|
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;
|
if (!this.accumulator) return undefined;
|
||||||
|
|
||||||
return this.resolver(this.accumulator);
|
return this.resolver(this.accumulator);
|
||||||
|
@ -64,8 +64,10 @@ export class AggregationResolver extends Lossy<Accumulator, Result> {
|
|||||||
super(lossless);
|
super(lossless);
|
||||||
}
|
}
|
||||||
|
|
||||||
initializer(): Accumulator {
|
initializer(view: LosslessViewOne): Accumulator {
|
||||||
return {};
|
return {
|
||||||
|
[view.id]: { id: view.id, properties: {} }
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
reducer(acc: Accumulator, cur: LosslessViewOne): Accumulator {
|
reducer(acc: Accumulator, cur: LosslessViewOne): Accumulator {
|
||||||
@ -120,31 +122,7 @@ export class AggregationResolver extends Lossy<Accumulator, Result> {
|
|||||||
return res;
|
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
|
// Convenience classes for common aggregation types
|
||||||
|
@ -63,8 +63,10 @@ export class CustomResolver extends Lossy<CustomResolverAccumulator, CustomResol
|
|||||||
super(lossless);
|
super(lossless);
|
||||||
}
|
}
|
||||||
|
|
||||||
initializer(): CustomResolverAccumulator {
|
initializer(view: LosslessViewOne): CustomResolverAccumulator {
|
||||||
return {};
|
return {
|
||||||
|
[view.id]: { id: view.id, properties: {} }
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
reducer(acc: CustomResolverAccumulator, cur: LosslessViewOne): CustomResolverAccumulator {
|
reducer(acc: CustomResolverAccumulator, cur: LosslessViewOne): CustomResolverAccumulator {
|
||||||
@ -118,30 +120,7 @@ export class CustomResolver extends Lossy<CustomResolverAccumulator, CustomResol
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Override resolve to build accumulator on-demand if needed
|
|
||||||
resolve(entityIds?: DomainEntityID[]): CustomResolverResult | 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();
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Built-in plugin implementations
|
// Built-in plugin implementations
|
||||||
|
@ -70,66 +70,44 @@ export function lastValueFromDeltas(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class LastWriteWins extends Lossy<Accumulator, Result> {
|
export class LastWriteWins extends Lossy<Accumulator, Result> {
|
||||||
initializer(): Accumulator {
|
initializer(view: LosslessViewOne): Accumulator {
|
||||||
return {};
|
return {
|
||||||
|
[view.id]: { id: view.id, properties: {} }
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
reducer(acc: Accumulator, cur: LosslessViewOne): Accumulator {
|
reducer(acc: Accumulator, cur: LosslessViewOne): Accumulator {
|
||||||
if (!acc[cur.id]) {
|
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)) {
|
for (const [key, deltas] of Object.entries(cur.propertyDeltas)) {
|
||||||
const {value, timeUpdated} = lastValueFromDeltas(key, deltas) || {};
|
const { value, timeUpdated } = lastValueFromDeltas(key, deltas) || {};
|
||||||
if (!value || !timeUpdated) continue;
|
if (!value || timeUpdated === undefined) continue;
|
||||||
|
|
||||||
if (timeUpdated > (acc[cur.id].properties[key]?.timeUpdated || 0)) {
|
const currentTime = acc[cur.id].properties[key]?.timeUpdated || 0;
|
||||||
acc[cur.id].properties[key] = {
|
if (timeUpdated > currentTime) {
|
||||||
value,
|
acc[cur.id].properties[key] = { value, timeUpdated };
|
||||||
timeUpdated
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return acc;
|
return acc;
|
||||||
};
|
}
|
||||||
|
|
||||||
resolver(cur: Accumulator): Result {
|
resolver(cur: Accumulator): Result {
|
||||||
const res: Result = {};
|
const result: Result = {};
|
||||||
|
|
||||||
for (const [id, ent] of Object.entries(cur)) {
|
for (const [id, entity] of Object.entries(cur)) {
|
||||||
res[id] = {id, properties: {}};
|
result[id] = {
|
||||||
for (const [key, {value}] of Object.entries(ent.properties)) {
|
id,
|
||||||
res[id].properties[key] = value;
|
properties: Object.fromEntries(
|
||||||
}
|
Object.entries(entity.properties)
|
||||||
|
.map(([key, { value }]) => [key, value])
|
||||||
|
)
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return res;
|
return result;
|
||||||
};
|
|
||||||
|
|
||||||
// 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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -72,8 +72,10 @@ export class TimestampResolver extends Lossy<Accumulator, Result> {
|
|||||||
super(lossless);
|
super(lossless);
|
||||||
}
|
}
|
||||||
|
|
||||||
initializer(): Accumulator {
|
initializer(view: LosslessViewOne): Accumulator {
|
||||||
return {};
|
return {
|
||||||
|
[view.id]: { id: view.id, properties: {} }
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
reducer(acc: Accumulator, cur: LosslessViewOne): Accumulator {
|
reducer(acc: Accumulator, cur: LosslessViewOne): Accumulator {
|
||||||
@ -124,31 +126,7 @@ export class TimestampResolver extends Lossy<Accumulator, Result> {
|
|||||||
return res;
|
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
|
// Convenience classes for different tie-breaking strategies
|
||||||
|
Loading…
x
Reference in New Issue
Block a user