197 lines
6.0 KiB
TypeScript
197 lines
6.0 KiB
TypeScript
import { DeltaV1, DeltaV2 } from './delta';
|
|
import { randomUUID } from 'crypto';
|
|
import { PropertyTypes } from './types';
|
|
import { PointersV2 } from './delta';
|
|
import { DeltaNetworkImageV1, DeltaNetworkImageV2 } 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.
|
|
* Supports both V1 and V2 delta formats.
|
|
*/
|
|
export class DeltaBuilder {
|
|
private id: string;
|
|
private timeCreated?: number;
|
|
private host: string;
|
|
private creator: string;
|
|
private pointers: PointersV2 = {};
|
|
private references: Record<string, string | null> = {};
|
|
|
|
/**
|
|
* 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
|
|
*/
|
|
constructor(creator: string, host: string) {
|
|
this.id = randomUUID();
|
|
this.creator = creator;
|
|
this.host = host;
|
|
}
|
|
|
|
/**
|
|
* Set a custom ID for the delta
|
|
*/
|
|
withId(id: string): this {
|
|
this.id = id;
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Set a custom creation timestamp
|
|
*/
|
|
withTimestamp(timestamp: number): this {
|
|
this.timeCreated = timestamp;
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Set the transaction ID for this delta
|
|
*/
|
|
inTransaction(transactionId: string): this {
|
|
this.addPointer('_transaction', transactionId, 'deltas');
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Declare a transaction with a size
|
|
* @param transactionId The ID of the transaction
|
|
* @param size The size of the transaction
|
|
* @returns
|
|
*/
|
|
declareTransaction(transactionId: string, size: number): this {
|
|
this.setProperty(transactionId, 'size', size, '_transaction');
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Mark this delta as a negation of another delta
|
|
*/
|
|
negate(deltaId: string): this {
|
|
this.addPointer('_negates', deltaId, 'negated_by');
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Add a pointer to the delta
|
|
* @param localContext The local context for the pointer
|
|
* @param target The target value (string, number, boolean, or null)
|
|
* @param targetContext Optional target context for the pointer
|
|
*/
|
|
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;
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Set a property on an entity
|
|
* ! Note that the way we are doing this is awkward/problematic for deltas that set multiple properties.
|
|
* ! entityLabel and property each need to be unique within a given delta
|
|
*/
|
|
setProperty(entityId: string, property: string, value: PropertyTypes, entityLabel = "entity"): this {
|
|
this.addPointer(entityLabel, entityId, property)
|
|
this.addPointer(property, value);
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Create a relationship between two entities
|
|
* @param sourceId The ID of the source entity
|
|
* @param targetId The ID of the target entity
|
|
* @param relationship The type of relationship
|
|
* @param properties Optional properties for the relationship
|
|
*/
|
|
relate(sourceId: string, targetId: string, relationship: string, properties?: Record<string, PropertyTypes>): this {
|
|
const relId = randomUUID();
|
|
this.setProperty(relId, 'source', sourceId, '_rel_source');
|
|
this.setProperty(relId, 'target', targetId, '_rel_target');
|
|
this.setProperty(relId, 'type', relationship, '_rel_type');
|
|
if (properties) {
|
|
for (const [key, value] of Object.entries(properties)) {
|
|
this.setProperty(relId, key, value, `_rel_${key}`);
|
|
}
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
reference(entityId: string, entityLabel: string): this {
|
|
if (this.references[entityLabel]) {
|
|
debug(`Reference for '${entityLabel}' already exists with different value: ${this.references[entityLabel]} !== ${entityId}`);
|
|
throw new Error(`Reference for ${entityLabel} already exists with different value`);
|
|
}
|
|
this.references[entityLabel] = entityId;
|
|
return this;
|
|
}
|
|
|
|
static fromNetworkImage(delta: DeltaNetworkImageV1 | DeltaNetworkImageV2): DeltaBuilder {
|
|
const builder = new DeltaBuilder(delta.creator, delta.host)
|
|
.withId(delta.id)
|
|
.withTimestamp(delta.timeCreated);
|
|
if (Array.isArray(delta.pointers)) {
|
|
for (const pointer of delta.pointers) {
|
|
builder.addPointer(pointer.localContext, pointer.target, pointer.targetContext);
|
|
}
|
|
} else {
|
|
for (const [localContext, target] of Object.entries(delta.pointers)) {
|
|
if (typeof target === 'object') {
|
|
const [[targetContext, targetValue]] = Object.entries(target!);
|
|
builder.addPointer(localContext, targetValue, targetContext);
|
|
} else {
|
|
builder.addPointer(localContext, target as PropertyTypes);
|
|
}
|
|
}
|
|
}
|
|
|
|
return builder;
|
|
}
|
|
|
|
/**
|
|
* Build and return a DeltaV2 instance
|
|
*/
|
|
buildV2(): DeltaV2 {
|
|
// For V2, we'll store transaction and negation info in the pointers object
|
|
const pointers = { ...this.pointers };
|
|
|
|
// Create the delta with all pointers
|
|
return new DeltaV2({
|
|
id: this.id,
|
|
host: this.host,
|
|
creator: this.creator,
|
|
timeCreated: this.timeCreated,
|
|
pointers
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Build and return a DeltaV1 instance
|
|
*/
|
|
buildV1(): DeltaV1 {
|
|
return this.buildV2().toV1();
|
|
}
|
|
|
|
/**
|
|
* Default to V1 for now
|
|
*/
|
|
build(): DeltaV1 {
|
|
return this.buildV1();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Create a new DeltaBuilder instance (convenience function)
|
|
*/
|
|
export function createDelta(creator: string, host: string): DeltaBuilder {
|
|
return new DeltaBuilder(creator, host);
|
|
}
|