refactor: simplify DeltaBuilder and update tests

- Removed V1-specific code paths in favor of V2 with toV1() conversion
- Simplified pointer handling to use a single internal representation
- Made entityLabel configurable in setProperty()
- Updated tests to use buildV1() and buildV2() explicitly
- Removed unused time utility module
- Added more comprehensive test coverage for entity properties

This change makes the DeltaBuilder more maintainable by reducing code duplication and following the pattern of building V2 deltas first, then converting to V1 when needed.
This commit is contained in:
Lentil Hoffman 2025-06-20 10:40:31 -05:00
parent 3ca8249510
commit 8043b67258
3 changed files with 67 additions and 157 deletions

View File

@ -1,4 +1,4 @@
import { createDelta, DeltaBuilder } from '../src/core/delta-builder';
import { createDelta } from '../src/core/delta-builder';
import { DeltaV1, DeltaV2 } from '../src/core/delta';
import { Lossless } from '../src/views/lossless';
import { RhizomeNode } from '../src/node';
@ -11,11 +11,10 @@ describe('DeltaBuilder', () => {
describe('V1 Deltas', () => {
it('should create a basic V1 delta', () => {
const builder = new DeltaBuilder(creator, host, 'v1');
const delta = builder
const delta = createDelta(creator, host)
.addPointer('name', 'Test Delta', 'title')
.addPointer('description', 'A test delta', 'description')
.build();
.buildV1();
expect(delta).toBeInstanceOf(DeltaV1);
expect(delta.id).toBeDefined();
@ -29,22 +28,46 @@ describe('DeltaBuilder', () => {
});
});
it.only('should create a V1 delta with setProperty', () => {
const delta = createDelta(creator, host, 'v1')
it('should create a V1 delta with setProperty', () => {
const delta = createDelta(creator, host)
.setProperty('entity-1', 'name', 'Test Entity')
.build();
.buildV1();
expect(delta).toBeInstanceOf(DeltaV1);
expect(delta.pointers).toContainEqual({
localContext: 'name',
target: 'Test Entity',
targetContext: 'name'
});
expect(delta.pointers).toContainEqual({
localContext: 'entity',
target: 'entity-1',
targetContext: 'name'
});
expect(delta.pointers).toContainEqual({
localContext: 'name',
target: 'Test Entity',
});
// Verify that the entity property resolves correctly
const lossless = new Lossless(node);
lossless.ingestDelta(delta);
const lossy = new LastWriteWins(lossless);
const result = lossy.resolve();
expect(result).toBeDefined();
expect(result!['entity-1'].properties.name).toBe('Test Entity');
});
it('should create a V1 delta with setProperty and entityLabel', () => {
const delta = createDelta(creator, host)
.setProperty('entity-1', 'name', 'Test Entity', 'user')
.buildV1();
expect(delta).toBeInstanceOf(DeltaV1);
expect(delta.pointers).toContainEqual({
localContext: 'user',
target: 'entity-1',
targetContext: 'name'
});
expect(delta.pointers).toContainEqual({
localContext: 'name',
target: 'Test Entity',
});
// Verify that the entity property resolves correctly
const lossless = new Lossless(node);
@ -56,9 +79,9 @@ describe('DeltaBuilder', () => {
});
it('should create a V1 delta with relationships', () => {
const delta = createDelta(creator, host, 'v1')
const delta = createDelta(creator, host)
.relate('user-1', 'follows', 'user-2')
.build();
.buildV1();
expect(delta.pointers).toContainEqual({
localContext: 'follows',
@ -75,8 +98,7 @@ describe('DeltaBuilder', () => {
describe('V2 Deltas', () => {
it('should create a basic V2 delta', () => {
const builder = new DeltaBuilder(creator, host, 'v2');
const delta = builder
const delta = createDelta(creator, host)
.addPointer('name', 'Test Delta V2', 'title')
.buildV2();
@ -89,7 +111,7 @@ describe('DeltaBuilder', () => {
});
it('should create a V2 delta with setProperty', () => {
const delta = createDelta(creator, host, 'v2')
const delta = createDelta(creator, host)
.setProperty('entity-1', 'name', 'Test Entity')
.buildV2();
@ -98,7 +120,7 @@ describe('DeltaBuilder', () => {
});
it('should create a V2 delta with relationships', () => {
const delta = createDelta(creator, host, 'v2')
const delta = createDelta(creator, host)
.relate('user-1', 'follows', 'user-2')
.buildV2();
@ -112,7 +134,7 @@ describe('DeltaBuilder', () => {
const customId = 'custom-delta-id';
const delta = createDelta(creator, host)
.withId(customId)
.build();
.buildV1();
expect(delta.id).toBe(customId);
});
@ -121,7 +143,7 @@ describe('DeltaBuilder', () => {
const txId = 'tx-123';
const delta = createDelta(creator, host)
.inTransaction(txId)
.build();
.buildV1();
// Check for transaction ID in pointers
const txPointer = delta.pointers.find(p => p.localContext === '_transaction');
@ -131,7 +153,7 @@ describe('DeltaBuilder', () => {
it('should support transactions in V2', () => {
const txId = 'tx-123';
const delta = createDelta(creator, host, 'v2')
const delta = createDelta(creator, host)
.inTransaction(txId)
.buildV2();
@ -143,7 +165,7 @@ describe('DeltaBuilder', () => {
const negatedId = 'delta-to-negate';
const delta = createDelta(creator, host)
.negate(negatedId)
.build();
.buildV1();
// Check for negation in pointers
const negationPointer = delta.pointers.find(p => p.localContext === '_negation');
@ -155,7 +177,7 @@ describe('DeltaBuilder', () => {
const timestamp = Date.now();
const delta = createDelta(creator, host)
.withTimestamp(timestamp)
.build();
.buildV1();
expect(delta.timeCreated).toBe(timestamp);
});

View File

@ -1,24 +1,7 @@
import {
DeltaID,
Delta,
DeltaV1,
DeltaV2,
DeltaNetworkImageV1,
DeltaNetworkImageV2,
PointerTarget,
PointersV2
} from './delta';
import { DeltaV1, DeltaV2 } from './delta';
import { randomUUID } from 'crypto';
import { microtime } from '../utils/time';
type DeltaVersion = 'v1' | 'v2';
// Local type for V1 pointers
interface PointerV1 {
localContext: string;
target: PointerTarget;
targetContext?: string;
}
import Debug from 'debug';
const debug = Debug('rz:delta-builder');
/**
* A fluent builder for creating Delta objects with proper validation and type safety.
@ -26,12 +9,10 @@ interface PointerV1 {
*/
export class DeltaBuilder {
private id: string;
private timeCreated: number;
private timeCreated?: number;
private host: string;
private creator: string;
private version: DeltaVersion = 'v2'; // Default to V2
private pointersV1: Array<{ localContext: string; target: PointerTarget; targetContext?: string }> = [];
private pointersV2: Record<string, any> = {};
private pointers: Record<string, any> = {};
private transactionId?: string;
private isNegation: boolean = false;
private negatedDeltaId?: string;
@ -40,14 +21,11 @@ export class DeltaBuilder {
* Create a new DeltaBuilder instance
* @param creator - The ID of the entity creating this delta
* @param host - The host where this delta is being created
* @param version - The delta version to use ('v1' or 'v2')
*/
constructor(creator: string, host: string, version: DeltaVersion = 'v2') {
constructor(creator: string, host: string) {
this.id = randomUUID();
this.timeCreated = microtime.now();
this.creator = creator;
this.host = host;
this.version = version;
}
/**
@ -84,50 +62,23 @@ export class DeltaBuilder {
}
/**
* Add a pointer to the delta (V1 style)
* Add a pointer to the delta
*/
addPointer(localContext: string, target: string | number | boolean, targetContext?: string): this {
if (this.version === 'v1') {
this.pointersV1.push({ localContext, target, targetContext });
if (targetContext && typeof target === 'string') {
this.pointers[localContext] = { [target]: targetContext };
} else {
// For V2, we need to handle the target context differently
if (targetContext && typeof target === 'string') {
this.pointersV2[localContext] = { [target]: targetContext };
} else {
this.pointersV2[localContext] = target;
}
this.pointers[localContext] = target;
}
return this;
}
/**
* Set a property on an entity (shorthand for addPointer with 'value' local context)
* Set a property on an entity
*/
setProperty(entityId: string, property: string, value: string | number | boolean, targetContext?: string): this {
if (this.version === 'v1') {
// For V1, we need to ensure target is a valid type
if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
this.pointersV1.push({
localContext: property,
target: value, // We've checked it's a valid type
targetContext: property
});
// Add a reference to the entity
this.pointersV1.push({
localContext: 'entity',
target: entityId,
targetContext: property
});
}
} else {
// V2 format
if (targetContext) {
this.pointersV2[property] = { [String(value)]: targetContext };
} else {
this.pointersV2[property] = value;
}
this.pointersV2.entity = { [entityId]: property };
}
setProperty(entityId: string, property: string, value: string | number | boolean, entityLabel = "entity"): this {
this.addPointer(entityLabel, entityId, property)
this.addPointer(property, value);
return this;
}
@ -135,41 +86,17 @@ export class DeltaBuilder {
* Create a relationship between two entities
*/
relate(sourceId: string, relationship: string, targetId: string): this {
if (this.version === 'v1') {
this.pointersV1.push({
localContext: relationship,
target: targetId,
targetContext: relationship
});
this.pointersV1.push({
localContext: 'source',
target: sourceId,
targetContext: relationship
});
} else {
this.pointersV2[relationship] = { [targetId]: relationship };
this.pointersV2.source = { [sourceId]: relationship };
}
this.pointers[relationship] = { [targetId]: relationship };
this.pointers.source = { [sourceId]: relationship };
return this;
}
/**
* Build and return a Delta instance
*/
build(): Delta {
if (this.version === 'v1') {
return this.buildV1();
} else {
return this.buildV2().toV1();
}
}
/**
* Build and return a DeltaV2 instance
*/
buildV2(): DeltaV2 {
// For V2, we'll store transaction and negation info in the pointers object
const pointers = { ...this.pointersV2 };
const pointers = { ...this.pointers };
if (this.transactionId) {
pointers['_transaction'] = this.transactionId;
@ -182,9 +109,9 @@ export class DeltaBuilder {
// Create the delta with all pointers
return new DeltaV2({
id: this.id,
timeCreated: this.timeCreated,
host: this.host,
creator: this.creator,
timeCreated: this.timeCreated,
pointers
});
}
@ -192,38 +119,14 @@ export class DeltaBuilder {
/**
* Build and return a DeltaV1 instance
*/
private buildV1(): DeltaV1 {
// For V1, we'll store transaction and negation info in the pointers
const pointers = [...this.pointersV1];
if (this.transactionId) {
pointers.push({
localContext: '_transaction',
target: this.transactionId
});
}
if (this.isNegation && this.negatedDeltaId) {
pointers.push({
localContext: '_negation',
target: this.negatedDeltaId
});
}
// Create the delta with all pointers
return new DeltaV1({
id: this.id,
timeCreated: this.timeCreated,
host: this.host,
creator: this.creator,
pointers
});
buildV1(): DeltaV1 {
return this.buildV2().toV1();
}
}
/**
* Create a new DeltaBuilder instance (convenience function)
*/
export function createDelta(creator: string, host: string, version: DeltaVersion = 'v2'): DeltaBuilder {
return new DeltaBuilder(creator, host, version);
export function createDelta(creator: string, host: string): DeltaBuilder {
return new DeltaBuilder(creator, host);
}

View File

@ -1,15 +0,0 @@
/**
* Microsecond-precision timestamp utilities
*/
/**
* Get current time in microseconds since epoch
*/
export function microtimeNow(): number {
const [seconds, nanoseconds] = process.hrtime();
return Math.floor(seconds * 1e6 + nanoseconds / 1e3);
}
export const microtime = {
now: microtimeNow
};