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:
parent
3ca8249510
commit
8043b67258
@ -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);
|
||||
});
|
||||
|
@ -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 });
|
||||
} else {
|
||||
// For V2, we need to handle the target context differently
|
||||
if (targetContext && typeof target === 'string') {
|
||||
this.pointersV2[localContext] = { [target]: targetContext };
|
||||
this.pointers[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);
|
||||
}
|
||||
|
@ -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
|
||||
};
|
Loading…
x
Reference in New Issue
Block a user