From 65a62309592265591afb09826dc37fd486645fc2 Mon Sep 17 00:00:00 2001 From: Lentil Hoffman Date: Sun, 22 Jun 2025 22:31:22 -0500 Subject: [PATCH] Revert changes to CustomResolver --- .../views/resolvers/custom-resolvers.test.ts | 89 -------- src/views/resolvers/custom-resolvers.ts | 195 +++--------------- 2 files changed, 32 insertions(+), 252 deletions(-) diff --git a/__tests__/unit/views/resolvers/custom-resolvers.test.ts b/__tests__/unit/views/resolvers/custom-resolvers.test.ts index 2484b1d..339c3c0 100644 --- a/__tests__/unit/views/resolvers/custom-resolvers.test.ts +++ b/__tests__/unit/views/resolvers/custom-resolvers.test.ts @@ -436,95 +436,6 @@ describe('Custom Resolvers', () => { }); }); - describe('Plugin Communication', () => { - test('plugins should be able to access each other\'s states', () => { - // Create a plugin that depends on another property's value - class DependentPlugin implements ResolverPlugin<{ value?: string }> { - name = 'dependent'; - - initialize() { - return { value: 'initial' }; - } - - update( - currentState: { value?: string }, - _newValue: PropertyTypes, - _delta: CollapsedDelta, - context?: { entityState: Record, resolvedValues: Record } - ) { - // This plugin's value depends on the 'source' property's resolved value - const sourceValue = context?.resolvedValues['source']; - return { - value: typeof sourceValue === 'string' ? `Processed: ${sourceValue}` : currentState.value - }; - } - - resolve( - state: { value?: string }, - context?: { entityState: Record, resolvedValues: Record } - ): PropertyTypes | undefined { - // In resolve, we can also check the context if needed - const sourceValue = context?.resolvedValues['source']; - if (typeof sourceValue === 'string' && state.value === 'initial') { - return `Processed: ${sourceValue}`; - } - return state.value; - } - } - - // Create a resolver with both plugins - const resolver = new CustomResolver(lossless, { - source: new LastWriteWinsPlugin(), - dependent: new DependentPlugin() - }); - - // First, set up the source property - const sourceDelta = createDelta('user1', 'host1') - .withTimestamp(1000) - .setProperty('entity1', 'source', 'original', 'collection') - .buildV1(); - - lossless.ingestDelta(sourceDelta); - - // Then set up the dependent property - const dependentDelta = createDelta('user1', 'host1') - .withTimestamp(2000) - .setProperty('entity1', 'dependent', 'initial', 'collection') - .buildV1(); - - lossless.ingestDelta(dependentDelta); - - // Get the first result - const result = resolver.resolve(); - expect(result).toBeDefined(); - - // The dependent plugin should see the source value - expect(result!['entity1'].properties).toMatchObject({ - source: 'original', - dependent: expect.stringContaining('Processed: original') - }); - - // Create a new delta that updates the source property - const updateDelta = createDelta('user1', 'host1') - .withTimestamp(3000) - .setProperty('entity1', 'source', 'updated', 'collection') - .buildV1(); - - // Ingest the update delta - lossless.ingestDelta(updateDelta); - - // Get the updated result - const updatedResult = resolver.resolve(); - expect(updatedResult).toBeDefined(); - - // The dependent plugin should see the updated source value - expect(updatedResult!['entity1'].properties).toMatchObject({ - source: 'updated', - dependent: expect.stringContaining('Processed: updated') - }); - }); - }); - describe('Edge Cases', () => { test('should handle empty delta sets', () => { const resolver = new CustomResolver(lossless, { diff --git a/src/views/resolvers/custom-resolvers.ts b/src/views/resolvers/custom-resolvers.ts index 1c18d4c..ee9d914 100644 --- a/src/views/resolvers/custom-resolvers.ts +++ b/src/views/resolvers/custom-resolvers.ts @@ -11,30 +11,11 @@ export interface ResolverPlugin { initialize(): T; // Process a new value for the property - update( - currentState: T, - newValue: PropertyTypes, - delta: CollapsedDelta, - // Additional context including other properties' states - context?: { - // Current state of all properties for the entity - entityState: Record; - // Current resolved values of all properties for the entity - resolvedValues: Record; - } - ): T; + update(currentState: T, newValue: PropertyTypes, delta: CollapsedDelta): T; // Resolve the final value from the accumulated state - resolve( - state: T, - // Additional context including other properties' states - context?: { - // Current state of all properties for the entity - entityState: Record; - // Current resolved values of all properties for the entity - resolvedValues: Record; - } - ): PropertyTypes | undefined; + // Returns undefined if no valid value could be resolved + resolve(state: T): PropertyTypes | undefined; } // Configuration for custom resolver @@ -94,67 +75,25 @@ export class CustomResolver extends Lossy = {}; - const resolvedValues: Record = {}; - - // Initialize all properties first - for (const propertyId of Object.keys(cur.propertyDeltas)) { + for (const [propertyId, deltas] of Object.entries(cur.propertyDeltas)) { const plugin = this.config[propertyId]; if (!plugin) continue; - + + // Initialize property state if not exists if (!acc[cur.id].properties[propertyId]) { acc[cur.id].properties[propertyId] = { plugin, state: plugin.initialize() }; } - - // Store the current state - entityState[propertyId] = acc[cur.id].properties[propertyId].state; - - // Resolve current value if possible - try { - const resolved = plugin.resolve(acc[cur.id].properties[propertyId].state, { - entityState: {}, - resolvedValues: {} - }); - if (resolved !== undefined) { - resolvedValues[propertyId] = resolved; - } - } catch (_e) { - // Ignore resolution errors during reduction - } - } - - // Second pass: process deltas with full context - for (const [propertyId, deltas] of Object.entries(cur.propertyDeltas)) { - const plugin = this.config[propertyId]; - if (!plugin) continue; const propertyState = acc[cur.id].properties[propertyId]; - const context = { entityState, resolvedValues }; // Process all deltas for this property for (const delta of deltas || []) { const value = extractValueFromDelta(propertyId, delta); if (value !== undefined) { - propertyState.state = plugin.update( - propertyState.state, - value, - delta, - context - ); - - // Update the resolved value after each update - try { - const resolved = plugin.resolve(propertyState.state, context); - if (resolved !== undefined) { - resolvedValues[propertyId] = resolved; - } - } catch (_e) { - // Ignore resolution errors during reduction - } + propertyState.state = propertyState.plugin.update(propertyState.state, value, delta); } } } @@ -167,40 +106,12 @@ export class CustomResolver extends Lossy = {}; - const resolvedValues: Record = {}; - - // Initialize with current states and resolve all properties + for (const [propertyId, propertyState] of Object.entries(entity.properties)) { - entityState[propertyId] = propertyState.state; - // Initial resolution with empty context - try { - const resolved = propertyState.plugin.resolve(propertyState.state, { - entityState: {}, - resolvedValues: {} - }); - if (resolved !== undefined) { - resolvedValues[propertyId] = resolved; - } - } catch (_e) { - // Ignore resolution errors - } - } - - // Second pass: resolve with full context - for (const [propertyId, propertyState] of Object.entries(entity.properties)) { - const context = { entityState, resolvedValues }; - try { - const resolvedValue = propertyState.plugin.resolve(propertyState.state, context); - if (resolvedValue !== undefined) { - entityResult.properties[propertyId] = resolvedValue; - // Update the resolved value for dependent properties - resolvedValues[propertyId] = resolvedValue; - } - } catch (_e) { - // Ignore resolution errors + const resolvedValue = propertyState.plugin.resolve(propertyState.state); + // Only add the property if the resolved value is not undefined + if (resolvedValue !== undefined) { + entityResult.properties[propertyId] = resolvedValue; } } @@ -226,12 +137,7 @@ export class LastWriteWinsPlugin implements ResolverPlugin<{ value?: PropertyTyp return { timestamp: 0 }; } - update( - currentState: { value?: PropertyTypes, timestamp: number }, - newValue: PropertyTypes, - delta: CollapsedDelta, - _context?: { entityState: Record, resolvedValues: Record } - ) { + update(currentState: { value?: PropertyTypes, timestamp: number }, newValue: PropertyTypes, delta: CollapsedDelta) { if (delta.timeCreated > currentState.timestamp) { return { value: newValue, @@ -254,12 +160,7 @@ export class FirstWriteWinsPlugin implements ResolverPlugin<{ value?: PropertyTy return { timestamp: Infinity }; } - update( - currentState: { value?: PropertyTypes, timestamp: number }, - newValue: PropertyTypes, - delta: CollapsedDelta, - _context?: { entityState: Record, resolvedValues: Record } - ) { + update(currentState: { value?: PropertyTypes, timestamp: number }, newValue: PropertyTypes, delta: CollapsedDelta) { if (delta.timeCreated < currentState.timestamp) { return { value: newValue, @@ -284,12 +185,7 @@ export class ConcatenationPlugin implements ResolverPlugin<{ values: { value: st return { values: [] }; } - update( - currentState: { values: { value: string, timestamp: number }[] }, - newValue: PropertyTypes, - delta: CollapsedDelta, - _context?: { entityState: Record, resolvedValues: Record } - ) { + update(currentState: { values: { value: string, timestamp: number }[] }, newValue: PropertyTypes, delta: CollapsedDelta) { if (typeof newValue === 'string') { // Check if this value already exists (avoid duplicates) const exists = currentState.values.some(v => v.value === newValue); @@ -305,10 +201,7 @@ export class ConcatenationPlugin implements ResolverPlugin<{ values: { value: st return currentState; } - resolve( - state: { values: { value: string, timestamp: number }[] }, - _context?: { entityState: Record, resolvedValues: Record } - ): PropertyTypes { + resolve(state: { values: { value: string, timestamp: number }[] }): PropertyTypes { return state.values.map(v => v.value).join(this.separator); } } @@ -321,32 +214,24 @@ export class MajorityVotePlugin implements ResolverPlugin<{ votes: Map }, - newValue: PropertyTypes, - _delta: CollapsedDelta, - _context?: { entityState: Record, resolvedValues: Record } - ) { - const count = (currentState.votes.get(newValue) || 0) + 1; - currentState.votes.set(newValue, count); + update(currentState: { votes: Map }, newValue: PropertyTypes, _delta: CollapsedDelta) { + const currentCount = currentState.votes.get(newValue) || 0; + currentState.votes.set(newValue, currentCount + 1); return currentState; } - resolve( - state: { votes: Map }, - _context?: { entityState: Record, resolvedValues: Record } - ): PropertyTypes { + resolve(state: { votes: Map }): PropertyTypes { let maxVotes = 0; - let result: PropertyTypes = ''; - - for (const [value, count] of state.votes.entries()) { - if (count > maxVotes) { - maxVotes = count; - result = value; + let winner: PropertyTypes = ''; + + for (const [value, votes] of state.votes.entries()) { + if (votes > maxVotes) { + maxVotes = votes; + winner = value; } } - - return result; + + return winner; } } @@ -358,12 +243,7 @@ export class MinPlugin implements ResolverPlugin<{ min?: number }> { return {}; } - update( - currentState: { min?: number }, - newValue: PropertyTypes, - _delta: CollapsedDelta, - _context?: { entityState: Record, resolvedValues: Record } - ) { + update(currentState: { min?: number }, newValue: PropertyTypes, _delta: CollapsedDelta) { if (typeof newValue === 'number') { if (currentState.min === undefined || newValue < currentState.min) { return { min: newValue }; @@ -372,10 +252,7 @@ export class MinPlugin implements ResolverPlugin<{ min?: number }> { return currentState; } - resolve( - state: { min?: number }, - _context?: { entityState: Record, resolvedValues: Record } - ): PropertyTypes | undefined { + resolve(state: { min?: number }): PropertyTypes | undefined { return state.min; } } @@ -387,12 +264,7 @@ export class MaxPlugin implements ResolverPlugin<{ max?: number }> { return {}; } - update( - currentState: { max?: number }, - newValue: PropertyTypes, - _delta: CollapsedDelta, - _context?: { entityState: Record, resolvedValues: Record } - ) { + update(currentState: { max?: number }, newValue: PropertyTypes, _delta: CollapsedDelta) { if (typeof newValue === 'number') { if (currentState.max === undefined || newValue > currentState.max) { return { max: newValue }; @@ -401,10 +273,7 @@ export class MaxPlugin implements ResolverPlugin<{ max?: number }> { return currentState; } - resolve( - state: { max?: number }, - _context?: { entityState: Record, resolvedValues: Record } - ): PropertyTypes | undefined { + resolve(state: { max?: number }): PropertyTypes | undefined { return state.max; } } \ No newline at end of file