Compare commits
No commits in common. "43b8b9db90617ea9ddec5372d5e5f92ec4dce2e4" and "50a822aa0c0c4d79a60d0a30257eb5175e51200e" have entirely different histories.
43b8b9db90
...
50a822aa0c
@ -1,3 +1,4 @@
|
|||||||
|
import * as _RhizomeImports from "../src";
|
||||||
/**
|
/**
|
||||||
* Tests for lossless view compose() and decompose() bidirectional conversion
|
* Tests for lossless view compose() and decompose() bidirectional conversion
|
||||||
* Ensures that deltas can be composed into lossless views and decomposed back
|
* Ensures that deltas can be composed into lossless views and decomposed back
|
||||||
|
@ -58,7 +58,7 @@ describe('Concurrent Write Scenarios', () => {
|
|||||||
const result = resolver.resolve();
|
const result = resolver.resolve();
|
||||||
|
|
||||||
expect(result).toBeDefined();
|
expect(result).toBeDefined();
|
||||||
// Should resolve deterministically using the LastWriteWins resolver's tie-breaking algorithm
|
// Should resolve deterministically (likely based on delta processing order)
|
||||||
expect(typeof result!['entity1'].properties.score).toBe('number');
|
expect(typeof result!['entity1'].properties.score).toBe('number');
|
||||||
expect([100, 200]).toContain(result!['entity1'].properties.score);
|
expect([100, 200]).toContain(result!['entity1'].properties.score);
|
||||||
});
|
});
|
||||||
|
@ -670,11 +670,7 @@ describe('Custom Resolvers', () => {
|
|||||||
|
|
||||||
const result = resolver.resolve();
|
const result = resolver.resolve();
|
||||||
expect(result).toBeDefined();
|
expect(result).toBeDefined();
|
||||||
// The entity might not be present in the result if no properties were resolved
|
expect(result!['entity1'].properties.score).toBe(0); // Default value
|
||||||
if (result!['entity1']) {
|
|
||||||
expect(result!['entity1'].properties).toBeDefined();
|
|
||||||
expect(result!['entity1'].properties).not.toHaveProperty('score');
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
@ -1,3 +1,4 @@
|
|||||||
|
import * as _RhizomeImports from "../src";
|
||||||
import { Delta } from '../src/core';
|
import { Delta } from '../src/core';
|
||||||
import { NegationHelper } from '../src/features';
|
import { NegationHelper } from '../src/features';
|
||||||
import { RhizomeNode } from '../src/node';
|
import { RhizomeNode } from '../src/node';
|
||||||
|
@ -251,7 +251,7 @@ describe('Nested Object Resolution Performance', () => {
|
|||||||
while (currentView.nestedObjects.next && currentView.nestedObjects.next.length > 0) {
|
while (currentView.nestedObjects.next && currentView.nestedObjects.next.length > 0) {
|
||||||
currentView = currentView.nestedObjects.next[0];
|
currentView = currentView.nestedObjects.next[0];
|
||||||
depth++;
|
depth++;
|
||||||
if (depth >= 10) break; // Prevent infinite loop
|
if (depth >= 5) break; // Prevent infinite loop
|
||||||
}
|
}
|
||||||
|
|
||||||
expect(depth).toBeLessThanOrEqual(5);
|
expect(depth).toBeLessThanOrEqual(5);
|
||||||
|
@ -12,8 +12,7 @@
|
|||||||
import { RhizomeNode } from '../src/node';
|
import { RhizomeNode } from '../src/node';
|
||||||
import { Delta } from '../src/core';
|
import { Delta } from '../src/core';
|
||||||
import { DefaultSchemaRegistry } from '../src/schema';
|
import { DefaultSchemaRegistry } from '../src/schema';
|
||||||
import { SchemaBuilder, PrimitiveSchemas, ReferenceSchemas } from '../src/schema';
|
import { CommonSchemas, SchemaBuilder, PrimitiveSchemas, ReferenceSchemas } from '../src/schema';
|
||||||
import { CommonSchemas } from '../src/test-utils/schemas';
|
|
||||||
import { TypedCollectionImpl } from '../src/collections';
|
import { TypedCollectionImpl } from '../src/collections';
|
||||||
|
|
||||||
describe('Nested Object Resolution', () => {
|
describe('Nested Object Resolution', () => {
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
import { QueryEngine } from '../src/query';
|
import { QueryEngine } from '../src/query';
|
||||||
import { Lossless } from '../src/views';
|
import { Lossless } from '../src/views';
|
||||||
import { DefaultSchemaRegistry } from '../src/schema';
|
import { DefaultSchemaRegistry } from '../src/schema';
|
||||||
import { SchemaBuilder, PrimitiveSchemas } from '../src/schema';
|
import { CommonSchemas, SchemaBuilder, PrimitiveSchemas } from '../src/schema';
|
||||||
import { CommonSchemas } from '../src/test-utils/schemas';
|
|
||||||
import { Delta } from '../src/core';
|
import { Delta } from '../src/core';
|
||||||
import { RhizomeNode } from '../src/node';
|
import { RhizomeNode } from '../src/node';
|
||||||
|
|
||||||
|
@ -3,11 +3,10 @@ import {
|
|||||||
PrimitiveSchemas,
|
PrimitiveSchemas,
|
||||||
ReferenceSchemas,
|
ReferenceSchemas,
|
||||||
ArraySchemas,
|
ArraySchemas,
|
||||||
// CommonSchemas has been moved to ./test-utils/schemas
|
CommonSchemas,
|
||||||
ObjectSchema
|
ObjectSchema
|
||||||
} from '../src/schema';
|
} from '../src/schema';
|
||||||
import { DefaultSchemaRegistry } from '../src/schema';
|
import { DefaultSchemaRegistry } from '../src/schema';
|
||||||
import { CommonSchemas } from '../src/test-utils/schemas';
|
|
||||||
import { TypedCollectionImpl, SchemaValidationError } from '../src/collections';
|
import { TypedCollectionImpl, SchemaValidationError } from '../src/collections';
|
||||||
import { RhizomeNode } from '../src/node';
|
import { RhizomeNode } from '../src/node';
|
||||||
import { Delta } from '../src/core';
|
import { Delta } from '../src/core';
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import * as _RhizomeImports from "../src";
|
||||||
import { Delta } from '../src/core';
|
import { Delta } from '../src/core';
|
||||||
import { Lossless } from '../src/views';
|
import { Lossless } from '../src/views';
|
||||||
import { RhizomeNode } from '../src/node';
|
import { RhizomeNode } from '../src/node';
|
||||||
|
@ -198,8 +198,46 @@ export class SchemaBuilder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Common schema patterns have been moved to __tests__/test-utils/schemas.ts
|
// Common schema patterns
|
||||||
// since they are only used for testing purposes.
|
export const CommonSchemas = {
|
||||||
|
// User schema with friends references
|
||||||
|
User: () => SchemaBuilder
|
||||||
|
.create('user')
|
||||||
|
.name('User')
|
||||||
|
.description('A user entity with profile information')
|
||||||
|
.property('name', PrimitiveSchemas.requiredString())
|
||||||
|
.property('email', PrimitiveSchemas.string())
|
||||||
|
.property('age', PrimitiveSchemas.number())
|
||||||
|
.property('active', PrimitiveSchemas.boolean())
|
||||||
|
.property('friends', ArraySchemas.of(ReferenceSchemas.to('user-summary', 2)))
|
||||||
|
.required('name')
|
||||||
|
.build(),
|
||||||
|
|
||||||
|
// User summary schema for references to prevent infinite recursion
|
||||||
|
UserSummary: () => SchemaBuilder
|
||||||
|
.create('user-summary')
|
||||||
|
.name('User Summary')
|
||||||
|
.description('Abbreviated user information for references')
|
||||||
|
.property('name', PrimitiveSchemas.requiredString())
|
||||||
|
.property('email', PrimitiveSchemas.string())
|
||||||
|
.required('name')
|
||||||
|
.additionalProperties(false)
|
||||||
|
.build(),
|
||||||
|
|
||||||
|
// Document schema
|
||||||
|
Document: () => SchemaBuilder
|
||||||
|
.create('document')
|
||||||
|
.name('Document')
|
||||||
|
.description('A document with metadata')
|
||||||
|
.property('title', PrimitiveSchemas.requiredString())
|
||||||
|
.property('content', PrimitiveSchemas.string())
|
||||||
|
.property('author', ReferenceSchemas.required('user-summary'))
|
||||||
|
.property('tags', ArraySchemas.of(PrimitiveSchemas.string()))
|
||||||
|
.property('created', PrimitiveSchemas.requiredNumber())
|
||||||
|
.property('published', PrimitiveSchemas.boolean())
|
||||||
|
.required('title', 'author', 'created')
|
||||||
|
.build()
|
||||||
|
} as const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Context for tracking resolution state during nested object resolution
|
* Context for tracking resolution state during nested object resolution
|
||||||
|
@ -1,59 +0,0 @@
|
|||||||
import { SchemaBuilder } from '../../src/schema';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Common schemas used for testing purposes.
|
|
||||||
* These schemas are not part of the main application code
|
|
||||||
* and are only used in test files.
|
|
||||||
*/
|
|
||||||
export const CommonSchemas = {
|
|
||||||
// User schema with friends references
|
|
||||||
User: () => SchemaBuilder
|
|
||||||
.create('user')
|
|
||||||
.name('User')
|
|
||||||
.description('A user entity with profile information')
|
|
||||||
.property('name', { type: 'primitive', primitiveType: 'string', required: true })
|
|
||||||
.property('email', { type: 'primitive', primitiveType: 'string' })
|
|
||||||
.property('age', { type: 'primitive', primitiveType: 'number' })
|
|
||||||
.property('active', { type: 'primitive', primitiveType: 'boolean' })
|
|
||||||
.property('friends', {
|
|
||||||
type: 'array',
|
|
||||||
itemSchema: {
|
|
||||||
type: 'reference',
|
|
||||||
targetSchema: 'user-summary',
|
|
||||||
maxDepth: 2
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.required('name')
|
|
||||||
.build(),
|
|
||||||
|
|
||||||
// User summary schema for references to prevent infinite recursion
|
|
||||||
UserSummary: () => SchemaBuilder
|
|
||||||
.create('user-summary')
|
|
||||||
.name('User Summary')
|
|
||||||
.description('Abbreviated user information for references')
|
|
||||||
.property('name', { type: 'primitive', primitiveType: 'string', required: true })
|
|
||||||
.property('email', { type: 'primitive', primitiveType: 'string' })
|
|
||||||
.build(),
|
|
||||||
|
|
||||||
// Document schema
|
|
||||||
Document: () => SchemaBuilder
|
|
||||||
.create('document')
|
|
||||||
.name('Document')
|
|
||||||
.description('A document with title, content, and author')
|
|
||||||
.property('title', { type: 'primitive', primitiveType: 'string', required: true })
|
|
||||||
.property('content', { type: 'primitive', primitiveType: 'string' })
|
|
||||||
.property('author', {
|
|
||||||
type: 'reference',
|
|
||||||
targetSchema: 'user-summary',
|
|
||||||
maxDepth: 1,
|
|
||||||
required: true
|
|
||||||
})
|
|
||||||
.property('tags', {
|
|
||||||
type: 'array',
|
|
||||||
itemSchema: { type: 'primitive', primitiveType: 'string' }
|
|
||||||
})
|
|
||||||
.property('created', { type: 'primitive', primitiveType: 'number', required: true })
|
|
||||||
.property('published', { type: 'primitive', primitiveType: 'boolean' })
|
|
||||||
.required('title', 'author', 'created')
|
|
||||||
.build()
|
|
||||||
} as const;
|
|
@ -41,32 +41,11 @@ 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,10 +64,8 @@ export class AggregationResolver extends Lossy<Accumulator, Result> {
|
|||||||
super(lossless);
|
super(lossless);
|
||||||
}
|
}
|
||||||
|
|
||||||
initializer(view: LosslessViewOne): Accumulator {
|
initializer(): Accumulator {
|
||||||
return {
|
return {};
|
||||||
[view.id]: { id: view.id, properties: {} }
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
reducer(acc: Accumulator, cur: LosslessViewOne): Accumulator {
|
reducer(acc: Accumulator, cur: LosslessViewOne): Accumulator {
|
||||||
@ -122,7 +120,31 @@ 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
|
||||||
|
@ -14,8 +14,7 @@ export interface ResolverPlugin<T = unknown> {
|
|||||||
update(currentState: T, newValue: PropertyTypes, delta: CollapsedDelta): T;
|
update(currentState: T, newValue: PropertyTypes, delta: CollapsedDelta): T;
|
||||||
|
|
||||||
// Resolve the final value from the accumulated state
|
// Resolve the final value from the accumulated state
|
||||||
// Returns undefined if no valid value could be resolved
|
resolve(state: T): PropertyTypes;
|
||||||
resolve(state: T): PropertyTypes | undefined;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Configuration for custom resolver
|
// Configuration for custom resolver
|
||||||
@ -64,10 +63,8 @@ export class CustomResolver extends Lossy<CustomResolverAccumulator, CustomResol
|
|||||||
super(lossless);
|
super(lossless);
|
||||||
}
|
}
|
||||||
|
|
||||||
initializer(view: LosslessViewOne): CustomResolverAccumulator {
|
initializer(): CustomResolverAccumulator {
|
||||||
return {
|
return {};
|
||||||
[view.id]: { id: view.id, properties: {} }
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
reducer(acc: CustomResolverAccumulator, cur: LosslessViewOne): CustomResolverAccumulator {
|
reducer(acc: CustomResolverAccumulator, cur: LosslessViewOne): CustomResolverAccumulator {
|
||||||
@ -109,10 +106,7 @@ export class CustomResolver extends Lossy<CustomResolverAccumulator, CustomResol
|
|||||||
|
|
||||||
for (const [propertyId, propertyState] of Object.entries(entity.properties)) {
|
for (const [propertyId, propertyState] of Object.entries(entity.properties)) {
|
||||||
const resolvedValue = propertyState.plugin.resolve(propertyState.state);
|
const resolvedValue = propertyState.plugin.resolve(propertyState.state);
|
||||||
// Only add the property if the resolved value is not undefined
|
entityResult.properties[propertyId] = resolvedValue;
|
||||||
if (resolvedValue !== undefined) {
|
|
||||||
entityResult.properties[propertyId] = resolvedValue;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only include entities that have at least one resolved property
|
// Only include entities that have at least one resolved property
|
||||||
@ -124,7 +118,30 @@ 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
|
||||||
@ -252,8 +269,8 @@ export class MinPlugin implements ResolverPlugin<{ min?: number }> {
|
|||||||
return currentState;
|
return currentState;
|
||||||
}
|
}
|
||||||
|
|
||||||
resolve(state: { min?: number }): PropertyTypes | undefined {
|
resolve(state: { min?: number }): PropertyTypes {
|
||||||
return state.min;
|
return state.min || 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -273,7 +290,7 @@ export class MaxPlugin implements ResolverPlugin<{ max?: number }> {
|
|||||||
return currentState;
|
return currentState;
|
||||||
}
|
}
|
||||||
|
|
||||||
resolve(state: { max?: number }): PropertyTypes | undefined {
|
resolve(state: { max?: number }): PropertyTypes {
|
||||||
return state.max;
|
return state.max || 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -70,44 +70,66 @@ export function lastValueFromDeltas(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class LastWriteWins extends Lossy<Accumulator, Result> {
|
export class LastWriteWins extends Lossy<Accumulator, Result> {
|
||||||
initializer(view: LosslessViewOne): Accumulator {
|
initializer(): 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 === undefined) continue;
|
if (!value || !timeUpdated) continue;
|
||||||
|
|
||||||
const currentTime = acc[cur.id].properties[key]?.timeUpdated || 0;
|
if (timeUpdated > (acc[cur.id].properties[key]?.timeUpdated || 0)) {
|
||||||
if (timeUpdated > currentTime) {
|
acc[cur.id].properties[key] = {
|
||||||
acc[cur.id].properties[key] = { value, timeUpdated };
|
value,
|
||||||
|
timeUpdated
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return acc;
|
return acc;
|
||||||
}
|
};
|
||||||
|
|
||||||
resolver(cur: Accumulator): Result {
|
resolver(cur: Accumulator): Result {
|
||||||
const result: Result = {};
|
const res: Result = {};
|
||||||
|
|
||||||
for (const [id, entity] of Object.entries(cur)) {
|
for (const [id, ent] of Object.entries(cur)) {
|
||||||
result[id] = {
|
res[id] = {id, properties: {}};
|
||||||
id,
|
for (const [key, {value}] of Object.entries(ent.properties)) {
|
||||||
properties: Object.fromEntries(
|
res[id].properties[key] = value;
|
||||||
Object.entries(entity.properties)
|
}
|
||||||
.map(([key, { value }]) => [key, value])
|
|
||||||
)
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -72,10 +72,8 @@ export class TimestampResolver extends Lossy<Accumulator, Result> {
|
|||||||
super(lossless);
|
super(lossless);
|
||||||
}
|
}
|
||||||
|
|
||||||
initializer(view: LosslessViewOne): Accumulator {
|
initializer(): Accumulator {
|
||||||
return {
|
return {};
|
||||||
[view.id]: { id: view.id, properties: {} }
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
reducer(acc: Accumulator, cur: LosslessViewOne): Accumulator {
|
reducer(acc: Accumulator, cur: LosslessViewOne): Accumulator {
|
||||||
@ -126,7 +124,31 @@ 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