added more concise syntax for deltas

This commit is contained in:
Ladd Hoffman 2025-01-02 16:58:51 -06:00
parent b147378bf8
commit 39d70b4680
8 changed files with 181 additions and 80 deletions

53
__tests__/delta.ts Normal file
View File

@ -0,0 +1,53 @@
import {DeltaV1, DeltaV2} from "../src/delta";
describe("Delta", () => {
it("can convert DeltaV1 to DeltaV2", () => {
const deltaV1 = new DeltaV1({
creator: 'a',
host: 'h',
pointers: [{
localContext: 'color',
target: 'red'
}, {
localContext: 'furniture',
target: 'chair-1',
targetContext: 'color'
}]
});
const deltaV2 = DeltaV2.fromV1(deltaV1);
expect(deltaV2).toMatchObject({
...deltaV1,
pointers: {
color: 'red',
furniture: {'chair-1': 'color'}
}
});
});
it("can convert DeltaV2 to DeltaV1", () => {
const deltaV2 = new DeltaV2({
creator: 'a',
host: 'h',
pointers: {
color: 'red',
furniture: {'chair-1': 'color'}
}
});
const deltaV1 = deltaV2.toV1();
expect(deltaV1).toMatchObject({
...deltaV2,
pointers: [{
localContext: 'color',
target: 'red'
}, {
localContext: 'furniture',
target: 'chair-1',
targetContext: 'color'
}]
});
});
});

View File

@ -1,4 +1,4 @@
import {Delta, DeltaFilter} from '../src/delta';
import {Delta, DeltaFilter, DeltaV2} from '../src/delta';
import {Lossless} from '../src/lossless';
import {RhizomeNode} from '../src/node';
@ -6,10 +6,19 @@ describe('Lossless', () => {
const node = new RhizomeNode();
it('creates a lossless view of keanu as neo in the matrix', () => {
const delta = new Delta({
const delta = new DeltaV2({
creator: 'a',
host: 'h',
pointers: [{
pointers: {
actor: {"keanu": "roles"},
role: {"neo": "actor"},
film: {"the_matrix": "cast"},
base_salary: 1000000,
salary_currency: "usd"
}
}).toV1();
expect(delta.pointers).toMatchObject([{
localContext: "actor",
target: "keanu",
targetContext: "roles"
@ -27,8 +36,7 @@ describe('Lossless', () => {
}, {
localContext: "salary_currency",
target: "usd"
}]
});
}]);
const lossless = new Lossless(node);

View File

@ -1,7 +1,7 @@
import Debug from 'debug';
import EventEmitter from 'node:events';
import objectHash from 'object-hash';
import {Delta, DeltaNetworkImage} from './delta';
import {Delta} from './delta';
import {RhizomeNode} from './node';
const debug = Debug('rz:deltas');
@ -91,12 +91,13 @@ export class DeltaStream {
}
serializeDelta(delta: Delta): string {
const deltaNetworkImage = new DeltaNetworkImage(delta);
const deltaNetworkImage = delta.toNetworkImage();
return JSON.stringify(deltaNetworkImage);
}
deserializeDelta(input: string): Delta {
// TODO: Input validation
return JSON.parse(input);
const parsed = JSON.parse(input);
return Delta.fromNetworkImage(parsed);
}
}

View File

@ -1,25 +1,37 @@
import {randomUUID} from "crypto";
import Debug from 'debug';
import microtime from 'microtime';
import {CreatorID, HostID, Timestamp, TransactionID} from "./types";
import {PeerAddress} from "./peers";
import {CreatorID, DomainEntityID, HostID, PropertyID, Timestamp, TransactionID} from "./types";
const debug = Debug('rz:delta');
export type DeltaID = string;
export type PointerTarget = string | number | undefined;
export type PointerTarget = string | number | null;
export type Pointer = {
type PointerV1 = {
localContext: string;
target: PointerTarget;
targetContext?: string;
};
export class DeltaNetworkImage {
export type Scalar = string | number | null;
export type Reference = {
[key: PropertyID]: DomainEntityID
};
export type PointersV2 = {
[key: PropertyID]: Scalar | Reference
};
export class DeltaNetworkImageV1 {
id: DeltaID;
timeCreated: Timestamp;
host: HostID;
creator: CreatorID;
pointers: Pointer[];
constructor({id, timeCreated, host, creator, pointers}: DeltaNetworkImage) {
pointers: PointerV1[];
constructor({id, timeCreated, host, creator, pointers}: DeltaNetworkImageV1) {
this.id = id;
this.host = host;
this.creator = creator;
@ -28,26 +40,106 @@ export class DeltaNetworkImage {
}
};
export class Delta extends DeltaNetworkImage {
export class DeltaNetworkImageV2 {
id: DeltaID;
timeCreated: Timestamp;
host: HostID;
creator: CreatorID;
pointers: PointersV2;
constructor({id, timeCreated, host, creator, pointers}: DeltaNetworkImageV2) {
this.id = id;
this.host = host;
this.creator = creator;
this.timeCreated = timeCreated;
this.pointers = pointers;
}
};
export class DeltaV1 extends DeltaNetworkImageV1 {
receivedFrom?: PeerAddress;
timeReceived: Timestamp;
transactionId?: TransactionID;
// TODO: Verify the following assumption:
// We're assuming that you only call this constructor when
// actually creating a new delta.
// When receiving one from the network, you can
constructor({host, creator, pointers}: Partial<DeltaNetworkImage>) {
// TODO: Verify that when receiving a delta from the network we can
// retain the delta's id.
const id = randomUUID();
const timeCreated = microtime.now();
constructor({id, timeCreated, host, creator, pointers}: Partial<DeltaNetworkImageV1>) {
id = id ?? randomUUID();
timeCreated = timeCreated ?? microtime.now();
if (!host || !creator || !pointers) throw new Error('uninitializied values');
super({id, timeCreated, host, creator, pointers});
this.timeCreated = timeCreated;
this.timeReceived = this.timeCreated;
}
toNetworkImage() {
return new DeltaNetworkImageV1(this);
}
static fromNetworkImage(delta: DeltaNetworkImageV1) {
return new DeltaV1(delta);
}
}
export class DeltaV2 extends DeltaNetworkImageV2 {
receivedFrom?: PeerAddress;
timeReceived: Timestamp;
transactionId?: TransactionID;
constructor({id, timeCreated, host, creator, pointers}: Partial<DeltaNetworkImageV2>) {
id = id ?? randomUUID();
timeCreated = timeCreated ?? microtime.now();
if (!host || !creator || !pointers) throw new Error('uninitializied values');
super({id, timeCreated, host, creator, pointers});
this.timeCreated = timeCreated;
this.timeReceived = this.timeCreated;
}
toNetworkImage() {
return new DeltaNetworkImageV2(this);
}
static fromNetworkImage(delta: DeltaNetworkImageV2) {
return new DeltaV2(delta);
}
static fromV1(delta: DeltaV1) {
const pointersV2: PointersV2 = {};
for (const {localContext, target, targetContext} of delta.pointers) {
if (targetContext && typeof target === "string") {
pointersV2[localContext] = {[target]: targetContext};
} else {
pointersV2[localContext] = target;
}
}
debug(`fromV1, pointers in: ${JSON.stringify(delta.pointers)}`);
debug(`fromV1, pointers out: ${JSON.stringify(pointersV2)}`);
return DeltaV2.fromNetworkImage({
...delta,
pointers: pointersV2
});
}
toV1() {
const pointersV1: PointerV1[] = [];
for (const [localContext, pointerTarget] of Object.entries(this.pointers)) {
if (pointerTarget && typeof pointerTarget === "object") {
const [obj] = Object.entries(pointerTarget)
if (!obj) throw new Error("invalid pointer target");
const [target, targetContext] = Object.entries(pointerTarget)[0];
pointersV1.push({localContext, target, targetContext});
} else {
pointersV1.push({localContext, target: pointerTarget});
}
}
return new DeltaV1({
...this,
pointers: pointersV1
});
}
}
// Alias
export class Delta extends DeltaV1 {}
export type DeltaFilter = (delta: Delta) => boolean;

View File

@ -1,20 +0,0 @@
import { add_operation, apply } from 'json-logic-js';
import { Delta } from '../delta';
type DeltaContext = Delta & {
creatorAddress: string;
};
add_operation('in', (needle, haystack) => {
return [...haystack].includes(needle);
});
export function applyFilter(deltas: Delta[], filterExpr: JSON): Delta[] {
return deltas.filter(delta => {
const context: DeltaContext = {
...delta,
creatorAddress: [delta.creator, delta.host].join('@'),
};
return apply(filterExpr, context);
});
}

View File

@ -1,33 +0,0 @@
import { FilterExpr } from "../types";
// import { map } from 'radash';
// A creator as seen by a host
type OriginPoint = {
creator: string;
host: string;
};
class Party {
originPoints: OriginPoint[];
constructor(og: OriginPoint) {
this.originPoints = [og];
}
getAddress() {
const { creator, host } = this.originPoints[0];
return `${creator}@${host}`;
}
}
const knownParties = new Set<Party>();
export const countKnownParties = () => knownParties.size;
export function generateFilter(): FilterExpr {
// map(knownParties, (p: Party) => p.address]
//
const addresses = [...knownParties.values()].map(p => p.getAddress());
return {
'in': ['$creatorAddress', addresses]
};
};

View File

@ -3,7 +3,7 @@
import Debug from 'debug';
import EventEmitter from 'events';
import {Delta, DeltaFilter, DeltaID, DeltaNetworkImage} from './delta';
import {Delta, DeltaFilter, DeltaID, DeltaNetworkImageV1} from './delta';
import {RhizomeNode} from './node';
import {Transactions} from './transactions';
import {DomainEntityID, PropertyID, PropertyTypes, TransactionID, ViewMany} from "./types";
@ -11,7 +11,7 @@ const debug = Debug('rz:lossless');
export type CollapsedPointer = {[key: PropertyID]: PropertyTypes};
export type CollapsedDelta = Omit<DeltaNetworkImage, 'pointers'> & {
export type CollapsedDelta = Omit<DeltaNetworkImageV1, 'pointers'> & {
pointers: CollapsedPointer[];
};

View File

@ -4,7 +4,7 @@ export type FilterExpr = JSONLogic;
export type FilterGenerator = () => FilterExpr;
export type PropertyTypes = string | number | undefined;
export type PropertyTypes = string | number | null;
export type DomainEntityID = string;
export type PropertyID = string;