refactor: optimize lossless view and improve code quality
- Optimize pointer handling and entity reference tracking in Lossless view - Improve type safety with proper TypeScript types - Add debug logging for better troubleshooting - Clean up imports and unused variables - Update test cases for DeltaBuilder
This commit is contained in:
parent
d7c4fda93e
commit
d0941f417e
@ -46,8 +46,8 @@ describe('DeltaBuilder', () => {
|
||||
|
||||
// Verify that the entity property resolves correctly
|
||||
const lossless = new Lossless(node);
|
||||
lossless.ingestDelta(delta);
|
||||
const lossy = new TimestampResolver(lossless);
|
||||
lossless.ingestDelta(delta);
|
||||
const result = lossy.resolve();
|
||||
expect(result).toBeDefined();
|
||||
expect(result!['entity-1'].properties.name).toBe('Test Entity');
|
||||
@ -71,8 +71,8 @@ describe('DeltaBuilder', () => {
|
||||
|
||||
// Verify that the entity property resolves correctly
|
||||
const lossless = new Lossless(node);
|
||||
lossless.ingestDelta(delta);
|
||||
const lossy = new TimestampResolver(lossless);
|
||||
lossless.ingestDelta(delta);
|
||||
const result = lossy.resolve();
|
||||
expect(result).toBeDefined();
|
||||
expect(result!['entity-1'].properties.name).toBe('Test Entity');
|
||||
@ -171,8 +171,8 @@ describe('DeltaBuilder', () => {
|
||||
expect(delta.pointers).toHaveProperty('type', 'follows');
|
||||
|
||||
const lossless = new Lossless(node);
|
||||
lossless.ingestDelta(delta);
|
||||
const lossy = new TimestampResolver(lossless);
|
||||
lossless.ingestDelta(delta);
|
||||
const result = lossy.resolve([relId]);
|
||||
expect(result).toBeDefined();
|
||||
expect(result![relId]).toMatchObject({
|
||||
@ -201,8 +201,8 @@ describe('DeltaBuilder', () => {
|
||||
expect(delta.pointers).toHaveProperty('version', 1);
|
||||
|
||||
const lossless = new Lossless(node);
|
||||
lossless.ingestDelta(delta);
|
||||
const lossy = new TimestampResolver(lossless);
|
||||
lossless.ingestDelta(delta);
|
||||
const result = lossy.resolve([relId]);
|
||||
expect(result).toBeDefined();
|
||||
expect(result![relId]).toMatchObject({
|
||||
|
@ -351,8 +351,8 @@ describe('Negation System', () => {
|
||||
test('should handle self-referential entities in negations', () => {
|
||||
// Create a delta that references itself
|
||||
const selfRefDelta = createDelta('user1', 'host1')
|
||||
.setProperty('node1', 'parent', 'node1')
|
||||
.setProperty('node1', 'child', 'node1') // Self-reference
|
||||
.setProperty('node1', 'parent', 'node1', 'node-as-parent')
|
||||
.setProperty('node1', 'child', 'node1', 'node-as-child') // Self-reference
|
||||
.buildV1();
|
||||
|
||||
const negationDelta = createDelta('admin', 'host1').negate(selfRefDelta.id).buildV1();
|
||||
|
@ -4,7 +4,7 @@ import { Lossless, LosslessViewOne } from "../../../src/views/lossless";
|
||||
import { Lossy } from "../../../src/views/lossy";
|
||||
import { RhizomeNode } from "../../../src/node";
|
||||
import { valueFromCollapsedDelta } from "../../../src/views/resolvers/aggregation-resolvers";
|
||||
import { lastValueFromDeltas } from "../../../src/views/resolvers/timestamp-resolvers";
|
||||
import { latestFromCollapsedDeltas } from "../../../src/views/resolvers/timestamp-resolvers";
|
||||
import { createDelta } from "../../../src/core/delta-builder";
|
||||
const debug = Debug('rz:test:lossy');
|
||||
|
||||
@ -18,8 +18,16 @@ type Summary = {
|
||||
roles: Role[];
|
||||
};
|
||||
|
||||
class Summarizer extends Lossy<Summary, Summary> {
|
||||
class Summarizer extends Lossy<Summary> {
|
||||
private readonly debug: debug.Debugger;
|
||||
|
||||
constructor(lossless: Lossless) {
|
||||
super(lossless);
|
||||
this.debug = Debug('rz:test:lossy:summarizer');
|
||||
}
|
||||
|
||||
initializer(): Summary {
|
||||
this.debug('Initializing new summary');
|
||||
return {
|
||||
roles: []
|
||||
};
|
||||
@ -30,23 +38,53 @@ class Summarizer extends Lossy<Summary, Summary> {
|
||||
// TODO: Prove with failing test
|
||||
|
||||
reducer(acc: Summary, cur: LosslessViewOne): Summary {
|
||||
this.debug(`Processing view for entity ${cur.id} (referenced as: ${cur.referencedAs.join(', ')})`);
|
||||
this.debug(`lossless view:`, JSON.stringify(cur));
|
||||
|
||||
if (cur.referencedAs.includes("role")) {
|
||||
const {delta, value: actor} = lastValueFromDeltas("actor", cur.propertyDeltas["actor"]) ?? {};
|
||||
if (!delta) throw new Error('expected to find delta');
|
||||
if (!actor) throw new Error('expected to find actor');
|
||||
this.debug(`Found role entity: ${cur.id}`);
|
||||
|
||||
const actorDeltas = cur.propertyDeltas["actor"];
|
||||
this.debug(`Found ${actorDeltas?.length ?? 0} actor deltas`);
|
||||
|
||||
const {delta, value: actor} = latestFromCollapsedDeltas("actor", actorDeltas) ?? {};
|
||||
|
||||
if (!delta) {
|
||||
this.debug('No delta found for actor property');
|
||||
throw new Error('expected to find delta');
|
||||
}
|
||||
|
||||
if (!actor) {
|
||||
this.debug('No actor value found in deltas');
|
||||
throw new Error('expected to find actor');
|
||||
}
|
||||
|
||||
this.debug(`Found actor: ${actor}`);
|
||||
const film = valueFromCollapsedDelta("film", delta);
|
||||
if (!film) throw new Error('expected to find film');
|
||||
acc.roles.push({
|
||||
|
||||
if (!film) {
|
||||
this.debug('No film property found in delta');
|
||||
throw new Error('expected to find film');
|
||||
}
|
||||
|
||||
this.debug(`Found film: ${film}`);
|
||||
const role = {
|
||||
role: cur.id,
|
||||
actor,
|
||||
film
|
||||
});
|
||||
};
|
||||
|
||||
acc.roles.push(role);
|
||||
this.debug(`Added role: ${JSON.stringify(role)}`);
|
||||
}
|
||||
|
||||
this.debug(`Updated accumulator: ${JSON.stringify(acc, null, 2)}`);
|
||||
|
||||
return acc;
|
||||
}
|
||||
|
||||
resolver(acc: Summary): Summary {
|
||||
this.debug(`Resolving summary with ${acc.roles.length} roles`);
|
||||
return acc;
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,9 @@ import { DeltaV1, DeltaV2 } from './delta';
|
||||
import { randomUUID } from 'crypto';
|
||||
import { PropertyTypes } from './types';
|
||||
import { PointersV2 } from './delta';
|
||||
import Debug from 'debug';
|
||||
|
||||
const debug = Debug('rz:delta-builder');
|
||||
|
||||
/**
|
||||
* A fluent builder for creating Delta objects with proper validation and type safety.
|
||||
@ -77,9 +80,11 @@ export class DeltaBuilder {
|
||||
addPointer(localContext: string, target: string | number | boolean | null, targetContext?: string): this {
|
||||
const pointerTarget = (targetContext && typeof target === 'string')
|
||||
? { [target]: targetContext } : target;
|
||||
// Prevent duplicate primitive properties with the same key
|
||||
if (this.pointers[localContext] &&
|
||||
JSON.stringify(this.pointers[localContext]) !== JSON.stringify(pointerTarget)
|
||||
) {
|
||||
debug(`Pointer for '${localContext}' already exists with different value: ${JSON.stringify(this.pointers[localContext])} !== ${JSON.stringify(pointerTarget)}`);
|
||||
throw new Error(`Pointer for ${localContext} already exists with different value`);
|
||||
}
|
||||
this.pointers[localContext] = pointerTarget;
|
||||
|
@ -1,4 +1,4 @@
|
||||
import Docker, { DockerOptions } from 'dockerode';
|
||||
import Docker from 'dockerode';
|
||||
import * as path from 'path';
|
||||
import { promises as fs } from 'fs';
|
||||
import * as tar from 'tar-fs';
|
||||
@ -63,9 +63,9 @@ export class ImageManager implements IImageManager {
|
||||
debug('Created build context tar stream');
|
||||
|
||||
testImageBuildPromise = new Promise<void>((resolve, reject) => {
|
||||
const log = (...args: any[]) => {
|
||||
const log = (...args: unknown[]) => {
|
||||
const message = args.map(arg =>
|
||||
typeof arg === 'object' ? JSON.stringify(arg, null, 2) : String(arg)
|
||||
typeof arg === 'object' ? JSON.stringify(arg) : String(arg)
|
||||
).join(' ');
|
||||
debug(message);
|
||||
};
|
||||
@ -111,7 +111,7 @@ export class ImageManager implements IImageManager {
|
||||
// Log any other non-empty JSON objects
|
||||
log(`[Docker Build] ${JSON.stringify(json)}`);
|
||||
}
|
||||
} catch (e) {
|
||||
} catch (_e) {
|
||||
// If not JSON, log as plain text if not empty
|
||||
if (line.trim()) {
|
||||
log(`[Docker Build] ${line}`);
|
||||
|
@ -1,4 +1,4 @@
|
||||
import Docker, { Container } from 'dockerode';
|
||||
import { Container } from 'dockerode';
|
||||
import { IStatusManager } from './interfaces';
|
||||
import { NodeHandle, NodeStatus } from '../../types';
|
||||
import Debug from 'debug';
|
||||
|
@ -269,7 +269,7 @@ export class QueryEngine {
|
||||
case 'primitive': {
|
||||
// Use last-write-wins for primitives
|
||||
const deltasSorted = deltas.sort((a, b) => b.timeCreated - a.timeCreated);
|
||||
for (let delta of deltasSorted) {
|
||||
for (const delta of deltasSorted) {
|
||||
const primitiveValue = this.extractPrimitiveValue(delta, propertyId);
|
||||
if (primitiveValue !== null) {
|
||||
obj[propertyId] = primitiveValue;
|
||||
|
@ -72,7 +72,7 @@ class LosslessEntity {
|
||||
export class Lossless {
|
||||
domainEntities = new LosslessEntityMap();
|
||||
transactions: Transactions;
|
||||
referencedAs = new Map<string, Set<DomainEntityID>>();
|
||||
referencedAs = new Map<string, Set<string>>();
|
||||
eventStream = new EventEmitter();
|
||||
|
||||
// Track all deltas by ID for negation processing
|
||||
@ -155,19 +155,6 @@ export class Lossless {
|
||||
}
|
||||
}
|
||||
|
||||
for (const {target, localContext} of delta.pointers) {
|
||||
if (typeof target === "string" && this.domainEntities.has(target)) {
|
||||
if (this.domainEntities.has(target)) {
|
||||
let referencedAs = this.referencedAs.get(localContext);
|
||||
if (!referencedAs) {
|
||||
referencedAs = new Set<string>();
|
||||
this.referencedAs.set(localContext, referencedAs);
|
||||
}
|
||||
referencedAs.add(target);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const transactionId = this.transactions.ingestDelta(delta, targets);
|
||||
|
||||
if (!transactionId) {
|
||||
@ -232,8 +219,8 @@ export class Lossless {
|
||||
const ent = this.domainEntities.get(id);
|
||||
if (!ent) continue;
|
||||
|
||||
|
||||
const referencedAs = new Set<string>();
|
||||
|
||||
const propertyDeltas: {
|
||||
[key: PropertyID]: CollapsedDelta[]
|
||||
} = {};
|
||||
@ -272,15 +259,11 @@ export class Lossless {
|
||||
|
||||
const pointers: CollapsedPointer[] = [];
|
||||
|
||||
for (const {localContext, target, targetContext} of delta.pointers) {
|
||||
if (targetContext) {
|
||||
// Only store primitive pointers in the collapsed delta
|
||||
continue;
|
||||
}
|
||||
pointers.push({[localContext]: target});
|
||||
for (const {localContext, target} of delta.pointers) {
|
||||
if (target === ent.id) {
|
||||
referencedAs.add(localContext);
|
||||
}
|
||||
pointers.push({[localContext]: target});
|
||||
}
|
||||
|
||||
visibleDeltas.push({
|
||||
@ -295,6 +278,14 @@ export class Lossless {
|
||||
}
|
||||
}
|
||||
|
||||
if (this.referencedAs.has(ent.id)) {
|
||||
for (const ref of referencedAs) {
|
||||
this.referencedAs.get(ent.id)!.add(ref);
|
||||
}
|
||||
} else {
|
||||
this.referencedAs.set(ent.id, referencedAs);
|
||||
}
|
||||
|
||||
// Only include entity in view if it has visible deltas
|
||||
if (hasVisibleDeltas) {
|
||||
view[ent.id] = {
|
||||
|
@ -47,6 +47,7 @@ export abstract class Lossy<Accumulator, Result = Accumulator> {
|
||||
return this.deltaFilter(delta);
|
||||
};
|
||||
const losslessPartial = this.lossless.compose([entityId], combinedFilter);
|
||||
debug(`Lossless partial for entity ${entityId}:`, JSON.stringify(losslessPartial));
|
||||
|
||||
if (!losslessPartial) {
|
||||
// This should not happen; this should only be called after the lossless view has been updated
|
||||
|
@ -199,7 +199,7 @@ export class CustomResolver extends Lossy<Accumulator, Result> {
|
||||
*/
|
||||
reducer(acc: Accumulator, {id: entityId, propertyDeltas}: LosslessViewOne): Accumulator {
|
||||
debug(`Processing deltas for entity: ${entityId}`);
|
||||
debug('Property deltas:', JSON.stringify(propertyDeltas, null, 2));
|
||||
debug('Property deltas:', JSON.stringify(propertyDeltas));
|
||||
|
||||
if (!acc[entityId]) {
|
||||
acc[entityId] = {};
|
||||
@ -246,7 +246,7 @@ export class CustomResolver extends Lossy<Accumulator, Result> {
|
||||
|
||||
// Update the plugin state with the new delta
|
||||
entityState[pluginKey] = plugin.update(pluginState, propertyValue, updateDelta, dependencies);
|
||||
debugState(`Updated entity state for ${entityId}:`, JSON.stringify(entityState[pluginKey], null, 2));
|
||||
debugState(`Updated entity state for ${entityId}:`, JSON.stringify(entityState[pluginKey]));
|
||||
}
|
||||
|
||||
return acc;
|
||||
@ -254,7 +254,7 @@ export class CustomResolver extends Lossy<Accumulator, Result> {
|
||||
|
||||
resolver(acc: Accumulator, entityIds: DomainEntityID[]) {
|
||||
const result: Result = {};
|
||||
debug('Initial accumulator state:', JSON.stringify(acc, null, 2));
|
||||
debug('Initial accumulator state:', JSON.stringify(acc));
|
||||
|
||||
for (const entityId in acc) {
|
||||
if (!entityIds.includes(entityId)) continue;
|
||||
@ -270,9 +270,9 @@ export class CustomResolver extends Lossy<Accumulator, Result> {
|
||||
|
||||
debug(`Processing property: ${propertyId} (key: ${pluginKey})`);
|
||||
const dependencies = this.getDependencyStates(acc[entityId], plugin);
|
||||
debug(`Dependencies for ${propertyId}:`, JSON.stringify(dependencies, null, 2));
|
||||
debug(`Dependencies for ${propertyId}:`, JSON.stringify(dependencies));
|
||||
const state = acc[entityId][pluginKey] || plugin.initialize(dependencies);
|
||||
debug(`State for ${propertyId}:`, JSON.stringify(state, null, 2));
|
||||
debug(`State for ${propertyId}:`, JSON.stringify(state));
|
||||
|
||||
const resolvedValue = plugin.resolve(state, dependencies);
|
||||
if (resolvedValue === undefined) throw new Error(`Resolved value for property ${propertyId} is undefined`)
|
||||
|
@ -158,7 +158,7 @@ export class LexicographicTimestampResolver extends TimestampResolver {
|
||||
}
|
||||
|
||||
// Resolve a value for an entity by last write wins
|
||||
export function lastValueFromDeltas(
|
||||
export function latestFromCollapsedDeltas(
|
||||
key: string,
|
||||
deltas?: CollapsedDelta[]
|
||||
): {
|
||||
|
Loading…
x
Reference in New Issue
Block a user