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 { DeltaV1, DeltaV2 } from '../src/core/delta';
|
||||||
import { Lossless } from '../src/views/lossless';
|
import { Lossless } from '../src/views/lossless';
|
||||||
import { RhizomeNode } from '../src/node';
|
import { RhizomeNode } from '../src/node';
|
||||||
@ -11,11 +11,10 @@ describe('DeltaBuilder', () => {
|
|||||||
|
|
||||||
describe('V1 Deltas', () => {
|
describe('V1 Deltas', () => {
|
||||||
it('should create a basic V1 delta', () => {
|
it('should create a basic V1 delta', () => {
|
||||||
const builder = new DeltaBuilder(creator, host, 'v1');
|
const delta = createDelta(creator, host)
|
||||||
const delta = builder
|
|
||||||
.addPointer('name', 'Test Delta', 'title')
|
.addPointer('name', 'Test Delta', 'title')
|
||||||
.addPointer('description', 'A test delta', 'description')
|
.addPointer('description', 'A test delta', 'description')
|
||||||
.build();
|
.buildV1();
|
||||||
|
|
||||||
expect(delta).toBeInstanceOf(DeltaV1);
|
expect(delta).toBeInstanceOf(DeltaV1);
|
||||||
expect(delta.id).toBeDefined();
|
expect(delta.id).toBeDefined();
|
||||||
@ -29,22 +28,46 @@ describe('DeltaBuilder', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it.only('should create a V1 delta with setProperty', () => {
|
it('should create a V1 delta with setProperty', () => {
|
||||||
const delta = createDelta(creator, host, 'v1')
|
const delta = createDelta(creator, host)
|
||||||
.setProperty('entity-1', 'name', 'Test Entity')
|
.setProperty('entity-1', 'name', 'Test Entity')
|
||||||
.build();
|
.buildV1();
|
||||||
|
|
||||||
expect(delta).toBeInstanceOf(DeltaV1);
|
expect(delta).toBeInstanceOf(DeltaV1);
|
||||||
expect(delta.pointers).toContainEqual({
|
|
||||||
localContext: 'name',
|
|
||||||
target: 'Test Entity',
|
|
||||||
targetContext: 'name'
|
|
||||||
});
|
|
||||||
expect(delta.pointers).toContainEqual({
|
expect(delta.pointers).toContainEqual({
|
||||||
localContext: 'entity',
|
localContext: 'entity',
|
||||||
target: 'entity-1',
|
target: 'entity-1',
|
||||||
targetContext: 'name'
|
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
|
// Verify that the entity property resolves correctly
|
||||||
const lossless = new Lossless(node);
|
const lossless = new Lossless(node);
|
||||||
@ -56,9 +79,9 @@ describe('DeltaBuilder', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should create a V1 delta with relationships', () => {
|
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')
|
.relate('user-1', 'follows', 'user-2')
|
||||||
.build();
|
.buildV1();
|
||||||
|
|
||||||
expect(delta.pointers).toContainEqual({
|
expect(delta.pointers).toContainEqual({
|
||||||
localContext: 'follows',
|
localContext: 'follows',
|
||||||
@ -75,8 +98,7 @@ describe('DeltaBuilder', () => {
|
|||||||
|
|
||||||
describe('V2 Deltas', () => {
|
describe('V2 Deltas', () => {
|
||||||
it('should create a basic V2 delta', () => {
|
it('should create a basic V2 delta', () => {
|
||||||
const builder = new DeltaBuilder(creator, host, 'v2');
|
const delta = createDelta(creator, host)
|
||||||
const delta = builder
|
|
||||||
.addPointer('name', 'Test Delta V2', 'title')
|
.addPointer('name', 'Test Delta V2', 'title')
|
||||||
.buildV2();
|
.buildV2();
|
||||||
|
|
||||||
@ -89,7 +111,7 @@ describe('DeltaBuilder', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should create a V2 delta with setProperty', () => {
|
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')
|
.setProperty('entity-1', 'name', 'Test Entity')
|
||||||
.buildV2();
|
.buildV2();
|
||||||
|
|
||||||
@ -98,7 +120,7 @@ describe('DeltaBuilder', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should create a V2 delta with relationships', () => {
|
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')
|
.relate('user-1', 'follows', 'user-2')
|
||||||
.buildV2();
|
.buildV2();
|
||||||
|
|
||||||
@ -112,7 +134,7 @@ describe('DeltaBuilder', () => {
|
|||||||
const customId = 'custom-delta-id';
|
const customId = 'custom-delta-id';
|
||||||
const delta = createDelta(creator, host)
|
const delta = createDelta(creator, host)
|
||||||
.withId(customId)
|
.withId(customId)
|
||||||
.build();
|
.buildV1();
|
||||||
|
|
||||||
expect(delta.id).toBe(customId);
|
expect(delta.id).toBe(customId);
|
||||||
});
|
});
|
||||||
@ -121,7 +143,7 @@ describe('DeltaBuilder', () => {
|
|||||||
const txId = 'tx-123';
|
const txId = 'tx-123';
|
||||||
const delta = createDelta(creator, host)
|
const delta = createDelta(creator, host)
|
||||||
.inTransaction(txId)
|
.inTransaction(txId)
|
||||||
.build();
|
.buildV1();
|
||||||
|
|
||||||
// Check for transaction ID in pointers
|
// Check for transaction ID in pointers
|
||||||
const txPointer = delta.pointers.find(p => p.localContext === '_transaction');
|
const txPointer = delta.pointers.find(p => p.localContext === '_transaction');
|
||||||
@ -131,7 +153,7 @@ describe('DeltaBuilder', () => {
|
|||||||
|
|
||||||
it('should support transactions in V2', () => {
|
it('should support transactions in V2', () => {
|
||||||
const txId = 'tx-123';
|
const txId = 'tx-123';
|
||||||
const delta = createDelta(creator, host, 'v2')
|
const delta = createDelta(creator, host)
|
||||||
.inTransaction(txId)
|
.inTransaction(txId)
|
||||||
.buildV2();
|
.buildV2();
|
||||||
|
|
||||||
@ -143,7 +165,7 @@ describe('DeltaBuilder', () => {
|
|||||||
const negatedId = 'delta-to-negate';
|
const negatedId = 'delta-to-negate';
|
||||||
const delta = createDelta(creator, host)
|
const delta = createDelta(creator, host)
|
||||||
.negate(negatedId)
|
.negate(negatedId)
|
||||||
.build();
|
.buildV1();
|
||||||
|
|
||||||
// Check for negation in pointers
|
// Check for negation in pointers
|
||||||
const negationPointer = delta.pointers.find(p => p.localContext === '_negation');
|
const negationPointer = delta.pointers.find(p => p.localContext === '_negation');
|
||||||
@ -155,7 +177,7 @@ describe('DeltaBuilder', () => {
|
|||||||
const timestamp = Date.now();
|
const timestamp = Date.now();
|
||||||
const delta = createDelta(creator, host)
|
const delta = createDelta(creator, host)
|
||||||
.withTimestamp(timestamp)
|
.withTimestamp(timestamp)
|
||||||
.build();
|
.buildV1();
|
||||||
|
|
||||||
expect(delta.timeCreated).toBe(timestamp);
|
expect(delta.timeCreated).toBe(timestamp);
|
||||||
});
|
});
|
||||||
|
@ -1,24 +1,7 @@
|
|||||||
import {
|
import { DeltaV1, DeltaV2 } from './delta';
|
||||||
DeltaID,
|
|
||||||
Delta,
|
|
||||||
DeltaV1,
|
|
||||||
DeltaV2,
|
|
||||||
DeltaNetworkImageV1,
|
|
||||||
DeltaNetworkImageV2,
|
|
||||||
PointerTarget,
|
|
||||||
PointersV2
|
|
||||||
} from './delta';
|
|
||||||
import { randomUUID } from 'crypto';
|
import { randomUUID } from 'crypto';
|
||||||
import { microtime } from '../utils/time';
|
import Debug from 'debug';
|
||||||
|
const debug = Debug('rz:delta-builder');
|
||||||
type DeltaVersion = 'v1' | 'v2';
|
|
||||||
|
|
||||||
// Local type for V1 pointers
|
|
||||||
interface PointerV1 {
|
|
||||||
localContext: string;
|
|
||||||
target: PointerTarget;
|
|
||||||
targetContext?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A fluent builder for creating Delta objects with proper validation and type safety.
|
* A fluent builder for creating Delta objects with proper validation and type safety.
|
||||||
@ -26,12 +9,10 @@ interface PointerV1 {
|
|||||||
*/
|
*/
|
||||||
export class DeltaBuilder {
|
export class DeltaBuilder {
|
||||||
private id: string;
|
private id: string;
|
||||||
private timeCreated: number;
|
private timeCreated?: number;
|
||||||
private host: string;
|
private host: string;
|
||||||
private creator: string;
|
private creator: string;
|
||||||
private version: DeltaVersion = 'v2'; // Default to V2
|
private pointers: Record<string, any> = {};
|
||||||
private pointersV1: Array<{ localContext: string; target: PointerTarget; targetContext?: string }> = [];
|
|
||||||
private pointersV2: Record<string, any> = {};
|
|
||||||
private transactionId?: string;
|
private transactionId?: string;
|
||||||
private isNegation: boolean = false;
|
private isNegation: boolean = false;
|
||||||
private negatedDeltaId?: string;
|
private negatedDeltaId?: string;
|
||||||
@ -40,14 +21,11 @@ export class DeltaBuilder {
|
|||||||
* Create a new DeltaBuilder instance
|
* Create a new DeltaBuilder instance
|
||||||
* @param creator - The ID of the entity creating this delta
|
* @param creator - The ID of the entity creating this delta
|
||||||
* @param host - The host where this delta is being created
|
* @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.id = randomUUID();
|
||||||
this.timeCreated = microtime.now();
|
|
||||||
this.creator = creator;
|
this.creator = creator;
|
||||||
this.host = host;
|
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 {
|
addPointer(localContext: string, target: string | number | boolean, targetContext?: string): this {
|
||||||
if (this.version === 'v1') {
|
if (targetContext && typeof target === 'string') {
|
||||||
this.pointersV1.push({ localContext, target, targetContext });
|
this.pointers[localContext] = { [target]: targetContext };
|
||||||
} else {
|
} else {
|
||||||
// For V2, we need to handle the target context differently
|
this.pointers[localContext] = target;
|
||||||
if (targetContext && typeof target === 'string') {
|
|
||||||
this.pointersV2[localContext] = { [target]: targetContext };
|
|
||||||
} else {
|
|
||||||
this.pointersV2[localContext] = target;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return this;
|
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 {
|
setProperty(entityId: string, property: string, value: string | number | boolean, entityLabel = "entity"): this {
|
||||||
if (this.version === 'v1') {
|
this.addPointer(entityLabel, entityId, property)
|
||||||
// For V1, we need to ensure target is a valid type
|
this.addPointer(property, value);
|
||||||
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 };
|
|
||||||
}
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -135,41 +86,17 @@ export class DeltaBuilder {
|
|||||||
* Create a relationship between two entities
|
* Create a relationship between two entities
|
||||||
*/
|
*/
|
||||||
relate(sourceId: string, relationship: string, targetId: string): this {
|
relate(sourceId: string, relationship: string, targetId: string): this {
|
||||||
if (this.version === 'v1') {
|
this.pointers[relationship] = { [targetId]: relationship };
|
||||||
this.pointersV1.push({
|
this.pointers.source = { [sourceId]: relationship };
|
||||||
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 };
|
|
||||||
}
|
|
||||||
return this;
|
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
|
* Build and return a DeltaV2 instance
|
||||||
*/
|
*/
|
||||||
buildV2(): DeltaV2 {
|
buildV2(): DeltaV2 {
|
||||||
// For V2, we'll store transaction and negation info in the pointers object
|
// For V2, we'll store transaction and negation info in the pointers object
|
||||||
const pointers = { ...this.pointersV2 };
|
const pointers = { ...this.pointers };
|
||||||
|
|
||||||
if (this.transactionId) {
|
if (this.transactionId) {
|
||||||
pointers['_transaction'] = this.transactionId;
|
pointers['_transaction'] = this.transactionId;
|
||||||
@ -182,9 +109,9 @@ export class DeltaBuilder {
|
|||||||
// Create the delta with all pointers
|
// Create the delta with all pointers
|
||||||
return new DeltaV2({
|
return new DeltaV2({
|
||||||
id: this.id,
|
id: this.id,
|
||||||
timeCreated: this.timeCreated,
|
|
||||||
host: this.host,
|
host: this.host,
|
||||||
creator: this.creator,
|
creator: this.creator,
|
||||||
|
timeCreated: this.timeCreated,
|
||||||
pointers
|
pointers
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -192,38 +119,14 @@ export class DeltaBuilder {
|
|||||||
/**
|
/**
|
||||||
* Build and return a DeltaV1 instance
|
* Build and return a DeltaV1 instance
|
||||||
*/
|
*/
|
||||||
private buildV1(): DeltaV1 {
|
buildV1(): DeltaV1 {
|
||||||
// For V1, we'll store transaction and negation info in the pointers
|
return this.buildV2().toV1();
|
||||||
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
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new DeltaBuilder instance (convenience function)
|
* Create a new DeltaBuilder instance (convenience function)
|
||||||
*/
|
*/
|
||||||
export function createDelta(creator: string, host: string, version: DeltaVersion = 'v2'): DeltaBuilder {
|
export function createDelta(creator: string, host: string): DeltaBuilder {
|
||||||
return new DeltaBuilder(creator, host, version);
|
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