Delta Builder Fluent API #4
@ -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