progress
This commit is contained in:
parent
9957dccddd
commit
880affad1c
@ -16,22 +16,7 @@ describe('Edge Cases', () => {
|
||||
lossless = new Lossless(node);
|
||||
});
|
||||
|
||||
test('should handle null and undefined values', () => {
|
||||
lossless.ingestDelta(
|
||||
createDelta('user1', 'host1')
|
||||
.withTimestamp(1000)
|
||||
.setProperty('test1', 'value', null, 'test')
|
||||
.buildV1()
|
||||
);
|
||||
|
||||
// Use null instead of undefined as it's a valid PropertyType
|
||||
lossless.ingestDelta(
|
||||
createDelta('user1', 'host1')
|
||||
.withTimestamp(2000)
|
||||
.setProperty('test1', 'value', null, 'test')
|
||||
.buildV1()
|
||||
);
|
||||
|
||||
test('should handle null values', () => {
|
||||
// Create a type-safe plugin that handles null/undefined values
|
||||
class NullSafeLastWriteWinsPlugin implements ResolverPlugin<{ value: PropertyTypes | null, timestamp: number }, never> {
|
||||
readonly dependencies = [] as const;
|
||||
@ -42,10 +27,11 @@ describe('Edge Cases', () => {
|
||||
|
||||
update(
|
||||
currentState: { value: PropertyTypes | null, timestamp: number },
|
||||
newValue: PropertyTypes,
|
||||
delta: CollapsedDelta,
|
||||
_dependencies: DependencyStates
|
||||
newValue?: PropertyTypes,
|
||||
delta?: CollapsedDelta,
|
||||
) {
|
||||
if (newValue === undefined) return currentState;
|
||||
if (!delta) return currentState;
|
||||
if (delta.timeCreated > currentState.timestamp) {
|
||||
return { value: newValue, timestamp: delta.timeCreated };
|
||||
}
|
||||
@ -54,9 +40,8 @@ describe('Edge Cases', () => {
|
||||
|
||||
resolve(
|
||||
state: { value: PropertyTypes | null, timestamp: number },
|
||||
_dependencies: DependencyStates
|
||||
): PropertyTypes | undefined {
|
||||
return state.value ?? undefined;
|
||||
return state.value;
|
||||
}
|
||||
}
|
||||
|
||||
@ -64,29 +49,20 @@ describe('Edge Cases', () => {
|
||||
value: new NullSafeLastWriteWinsPlugin()
|
||||
});
|
||||
|
||||
const results = resolver.resolve() || [];
|
||||
expect(Array.isArray(results)).toBe(true);
|
||||
const test1 = results.find(r => r.id === 'test1');
|
||||
expect(test1).toBeDefined();
|
||||
expect(test1?.properties.value).toBeUndefined();
|
||||
});
|
||||
|
||||
test('should handle concurrent updates with same timestamp', () => {
|
||||
// Two updates with the same timestamp
|
||||
lossless.ingestDelta(
|
||||
createDelta('user1', 'host1')
|
||||
.withTimestamp(1000)
|
||||
.setProperty('test2', 'value', 'first', 'test')
|
||||
.setProperty('test2', 'value', null, 'test')
|
||||
.buildV1()
|
||||
);
|
||||
|
||||
lossless.ingestDelta(
|
||||
createDelta('user2', 'host2')
|
||||
.withTimestamp(1000) // Same timestamp
|
||||
.setProperty('test2', 'value', 'second', 'test')
|
||||
.buildV1()
|
||||
);
|
||||
const results = resolver.resolve() || {};
|
||||
const test1 = results['test2']
|
||||
expect(test1).toBeDefined();
|
||||
expect(test1?.properties.value).toBeNull();
|
||||
});
|
||||
|
||||
test('should handle concurrent updates with same timestamp', () => {
|
||||
// Custom plugin that handles concurrent updates with the same timestamp
|
||||
class ConcurrentUpdatePlugin implements ResolverPlugin<{ value: PropertyTypes, timestamp: number }, never> {
|
||||
readonly dependencies = [] as const;
|
||||
@ -123,25 +99,31 @@ describe('Edge Cases', () => {
|
||||
value: new ConcurrentUpdatePlugin()
|
||||
});
|
||||
|
||||
const results = resolver.resolve() || [];
|
||||
expect(Array.isArray(results)).toBe(true);
|
||||
const test2 = results.find(r => r.id === 'test2');
|
||||
// Two updates with the same timestamp
|
||||
lossless.ingestDelta(
|
||||
createDelta('user1', 'host1')
|
||||
.withTimestamp(1000)
|
||||
.setProperty('test2', 'value', null, 'test')
|
||||
.buildV1()
|
||||
);
|
||||
|
||||
lossless.ingestDelta(
|
||||
createDelta('user2', 'host2')
|
||||
.withTimestamp(1000) // Same timestamp
|
||||
.setProperty('test2', 'value', 'xylophone', 'test')
|
||||
.buildV1()
|
||||
);
|
||||
|
||||
|
||||
|
||||
const results = resolver.resolve() || {};
|
||||
const test2 = results['test2'];
|
||||
expect(test2).toBeDefined();
|
||||
// Should pick one of the values deterministically
|
||||
expect(test2?.properties.value).toBe('first');
|
||||
expect(test2?.properties.value).toBeNull();
|
||||
});
|
||||
|
||||
test('should handle very large numbers of updates', () => {
|
||||
// Add 1000 updates
|
||||
for (let i = 0; i < 1000; i++) {
|
||||
lossless.ingestDelta(
|
||||
createDelta('user1', 'host1')
|
||||
.withTimestamp(1000 + i)
|
||||
.setProperty('test3', 'counter', i, 'test')
|
||||
.buildV1()
|
||||
);
|
||||
}
|
||||
|
||||
// Plugin that handles large numbers of updates efficiently
|
||||
class CounterPlugin implements ResolverPlugin<{ count: number }, never> {
|
||||
readonly dependencies = [] as const;
|
||||
@ -171,9 +153,18 @@ describe('Edge Cases', () => {
|
||||
counter: new CounterPlugin()
|
||||
});
|
||||
|
||||
const results = resolver.resolve() || [];
|
||||
expect(Array.isArray(results)).toBe(true);
|
||||
const test3 = results.find(r => r.id === 'test3');
|
||||
// Add 1000 updates
|
||||
for (let i = 0; i < 1000; i++) {
|
||||
lossless.ingestDelta(
|
||||
createDelta('user1', 'host1')
|
||||
.withTimestamp(1000 + i)
|
||||
.setProperty('test3', 'counter', i, 'test')
|
||||
.buildV1()
|
||||
);
|
||||
}
|
||||
|
||||
const results = resolver.resolve() || {};
|
||||
const test3 = results['test3']
|
||||
expect(test3).toBeDefined();
|
||||
// Should handle large numbers of updates efficiently
|
||||
expect(test3?.properties.counter).toBe(1000); // Should count all 1000 updates
|
||||
@ -183,28 +174,22 @@ describe('Edge Cases', () => {
|
||||
// No deltas added - should handle empty state
|
||||
// Plugin that handles missing properties gracefully
|
||||
class MissingPropertyPlugin implements ResolverPlugin<{ initialized: boolean }, never> {
|
||||
private _initialized = false;
|
||||
readonly dependencies = [] as const;
|
||||
|
||||
initialize() {
|
||||
this._initialized = true;
|
||||
return { initialized: true };
|
||||
}
|
||||
|
||||
update(
|
||||
currentState: { initialized: boolean },
|
||||
_newValue: PropertyTypes,
|
||||
_delta: CollapsedDelta,
|
||||
_dependencies: DependencyStates
|
||||
) {
|
||||
return currentState;
|
||||
}
|
||||
|
||||
resolve(
|
||||
_state: { initialized: boolean },
|
||||
_dependencies: DependencyStates
|
||||
state: { initialized: boolean }
|
||||
): boolean {
|
||||
return this._initialized;
|
||||
return state.initialized;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -62,7 +62,7 @@ export abstract class Lossy<Accumulator, Result = Accumulator> {
|
||||
// Resolve the current state of the view
|
||||
resolve(entityIds?: DomainEntityID[]): Result | undefined {
|
||||
if (!this.accumulator) {
|
||||
return undefined;
|
||||
this.accumulator =this.initializer?.() || {} as Accumulator;
|
||||
}
|
||||
|
||||
if (!entityIds) {
|
||||
|
@ -5,8 +5,8 @@ import { ResolverPlugin, DependencyStates } from "./plugin";
|
||||
import { EntityRecord } from "@src/core/entity";
|
||||
import Debug from 'debug';
|
||||
|
||||
const debug = Debug('rz:resolver');
|
||||
const debugState = Debug('rz:resolver:state');
|
||||
const debug = Debug('rz:custom-resolver');
|
||||
const debugState = Debug('rz:custom-resolver:state');
|
||||
|
||||
/**
|
||||
* The state of a property for a single entity
|
||||
@ -224,6 +224,28 @@ export class CustomResolver extends Lossy<Accumulator, Result> {
|
||||
return dependencyStates;
|
||||
}
|
||||
|
||||
private initializePlugins(acc: Accumulator, entityId: DomainEntityID) {
|
||||
if (!acc[entityId]) {
|
||||
acc[entityId] = {};
|
||||
}
|
||||
const entityState = acc[entityId];
|
||||
|
||||
for (const pluginId of this.executionOrder) {
|
||||
const pluginKey = this.pluginKeyFromBasename(pluginId);
|
||||
const plugin = this.config[pluginKey];
|
||||
if (!plugin) throw new Error(`Plugin for property ${pluginId} not found`);
|
||||
|
||||
// We need to resolve dependencies, including entity properties that are not plugins.
|
||||
const dependencies = this.getDependencyStates(entityState, plugin);
|
||||
debug('Dependencies for', pluginId, ':', JSON.stringify(dependencies));
|
||||
|
||||
// Initialize the plugin if it hasn't been initialized yet
|
||||
entityState[pluginKey] = entityState[pluginKey] ?? plugin.initialize(dependencies);
|
||||
}
|
||||
|
||||
return { entityState };
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the state with new deltas from the view
|
||||
*/
|
||||
@ -231,10 +253,7 @@ export class CustomResolver extends Lossy<Accumulator, Result> {
|
||||
debug(`Processing deltas for entity: ${entityId}`);
|
||||
debug('Property deltas:', JSON.stringify(propertyDeltas));
|
||||
|
||||
if (!acc[entityId]) {
|
||||
acc[entityId] = {};
|
||||
}
|
||||
const entityState = acc[entityId];
|
||||
const { entityState } = this.initializePlugins(acc, entityId);
|
||||
|
||||
// Now let's go through each plugin in order.
|
||||
for (const pluginId of this.executionOrder) {
|
||||
@ -244,12 +263,7 @@ export class CustomResolver extends Lossy<Accumulator, Result> {
|
||||
|
||||
debug(`Processing plugin: ${pluginId} (key: ${pluginKey})`);
|
||||
|
||||
// We need to resolve dependencies, including entity properties that are not plugins.
|
||||
const dependencies = this.getDependencyStates(entityState, plugin);
|
||||
debug('Dependencies for', pluginId, ':', JSON.stringify(dependencies));
|
||||
|
||||
// Initialize the plugin if it hasn't been initialized yet
|
||||
const pluginState = entityState[pluginKey] ?? plugin.initialize(dependencies);
|
||||
const pluginState = entityState[pluginKey];
|
||||
|
||||
const deltaPropertyValues : Record<PropertyID, PropertyTypes> = {};
|
||||
let propertyValue : PropertyTypes | undefined;
|
||||
@ -276,6 +290,7 @@ export class CustomResolver extends Lossy<Accumulator, Result> {
|
||||
}
|
||||
|
||||
// Update the plugin state with the new delta
|
||||
const dependencies = this.getDependencyStates(entityState, plugin);
|
||||
entityState[pluginKey] = plugin.update(pluginState, propertyValue, updateDelta, dependencies);
|
||||
debugState(`Updated entity state for ${entityId}:`, JSON.stringify(entityState[pluginKey]));
|
||||
}
|
||||
@ -289,6 +304,9 @@ export class CustomResolver extends Lossy<Accumulator, Result> {
|
||||
|
||||
for (const entityId in acc) {
|
||||
if (!entityIds.includes(entityId)) continue;
|
||||
|
||||
this.initializePlugins(acc, entityId);
|
||||
|
||||
result[entityId] = {
|
||||
id: entityId,
|
||||
properties: {}
|
||||
@ -313,6 +331,8 @@ export class CustomResolver extends Lossy<Accumulator, Result> {
|
||||
}
|
||||
}
|
||||
|
||||
debug(`Result:`, JSON.stringify(result));
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user