added more concise syntax for deltas
This commit is contained in:
parent
b147378bf8
commit
39d70b4680
|
@ -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'
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -1,4 +1,4 @@
|
||||||
import {Delta, DeltaFilter} from '../src/delta';
|
import {Delta, DeltaFilter, DeltaV2} from '../src/delta';
|
||||||
import {Lossless} from '../src/lossless';
|
import {Lossless} from '../src/lossless';
|
||||||
import {RhizomeNode} from '../src/node';
|
import {RhizomeNode} from '../src/node';
|
||||||
|
|
||||||
|
@ -6,10 +6,19 @@ describe('Lossless', () => {
|
||||||
const node = new RhizomeNode();
|
const node = new RhizomeNode();
|
||||||
|
|
||||||
it('creates a lossless view of keanu as neo in the matrix', () => {
|
it('creates a lossless view of keanu as neo in the matrix', () => {
|
||||||
const delta = new Delta({
|
const delta = new DeltaV2({
|
||||||
creator: 'a',
|
creator: 'a',
|
||||||
host: 'h',
|
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",
|
localContext: "actor",
|
||||||
target: "keanu",
|
target: "keanu",
|
||||||
targetContext: "roles"
|
targetContext: "roles"
|
||||||
|
@ -27,8 +36,7 @@ describe('Lossless', () => {
|
||||||
}, {
|
}, {
|
||||||
localContext: "salary_currency",
|
localContext: "salary_currency",
|
||||||
target: "usd"
|
target: "usd"
|
||||||
}]
|
}]);
|
||||||
});
|
|
||||||
|
|
||||||
const lossless = new Lossless(node);
|
const lossless = new Lossless(node);
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import Debug from 'debug';
|
import Debug from 'debug';
|
||||||
import EventEmitter from 'node:events';
|
import EventEmitter from 'node:events';
|
||||||
import objectHash from 'object-hash';
|
import objectHash from 'object-hash';
|
||||||
import {Delta, DeltaNetworkImage} from './delta';
|
import {Delta} from './delta';
|
||||||
import {RhizomeNode} from './node';
|
import {RhizomeNode} from './node';
|
||||||
const debug = Debug('rz:deltas');
|
const debug = Debug('rz:deltas');
|
||||||
|
|
||||||
|
@ -91,12 +91,13 @@ export class DeltaStream {
|
||||||
}
|
}
|
||||||
|
|
||||||
serializeDelta(delta: Delta): string {
|
serializeDelta(delta: Delta): string {
|
||||||
const deltaNetworkImage = new DeltaNetworkImage(delta);
|
const deltaNetworkImage = delta.toNetworkImage();
|
||||||
return JSON.stringify(deltaNetworkImage);
|
return JSON.stringify(deltaNetworkImage);
|
||||||
}
|
}
|
||||||
|
|
||||||
deserializeDelta(input: string): Delta {
|
deserializeDelta(input: string): Delta {
|
||||||
// TODO: Input validation
|
// TODO: Input validation
|
||||||
return JSON.parse(input);
|
const parsed = JSON.parse(input);
|
||||||
|
return Delta.fromNetworkImage(parsed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
124
src/delta.ts
124
src/delta.ts
|
@ -1,25 +1,37 @@
|
||||||
import {randomUUID} from "crypto";
|
import {randomUUID} from "crypto";
|
||||||
|
import Debug from 'debug';
|
||||||
import microtime from 'microtime';
|
import microtime from 'microtime';
|
||||||
import {CreatorID, HostID, Timestamp, TransactionID} from "./types";
|
|
||||||
import {PeerAddress} from "./peers";
|
import {PeerAddress} from "./peers";
|
||||||
|
import {CreatorID, DomainEntityID, HostID, PropertyID, Timestamp, TransactionID} from "./types";
|
||||||
|
const debug = Debug('rz:delta');
|
||||||
|
|
||||||
export type DeltaID = string;
|
export type DeltaID = string;
|
||||||
|
|
||||||
export type PointerTarget = string | number | undefined;
|
export type PointerTarget = string | number | null;
|
||||||
|
|
||||||
export type Pointer = {
|
type PointerV1 = {
|
||||||
localContext: string;
|
localContext: string;
|
||||||
target: PointerTarget;
|
target: PointerTarget;
|
||||||
targetContext?: string;
|
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;
|
id: DeltaID;
|
||||||
timeCreated: Timestamp;
|
timeCreated: Timestamp;
|
||||||
host: HostID;
|
host: HostID;
|
||||||
creator: CreatorID;
|
creator: CreatorID;
|
||||||
pointers: Pointer[];
|
pointers: PointerV1[];
|
||||||
constructor({id, timeCreated, host, creator, pointers}: DeltaNetworkImage) {
|
|
||||||
|
constructor({id, timeCreated, host, creator, pointers}: DeltaNetworkImageV1) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.host = host;
|
this.host = host;
|
||||||
this.creator = creator;
|
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;
|
receivedFrom?: PeerAddress;
|
||||||
timeReceived: Timestamp;
|
timeReceived: Timestamp;
|
||||||
transactionId?: TransactionID;
|
transactionId?: TransactionID;
|
||||||
|
|
||||||
// TODO: Verify the following assumption:
|
constructor({id, timeCreated, host, creator, pointers}: Partial<DeltaNetworkImageV1>) {
|
||||||
// We're assuming that you only call this constructor when
|
id = id ?? randomUUID();
|
||||||
// actually creating a new delta.
|
timeCreated = timeCreated ?? microtime.now();
|
||||||
// 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();
|
|
||||||
if (!host || !creator || !pointers) throw new Error('uninitializied values');
|
if (!host || !creator || !pointers) throw new Error('uninitializied values');
|
||||||
super({id, timeCreated, host, creator, pointers});
|
super({id, timeCreated, host, creator, pointers});
|
||||||
this.timeCreated = timeCreated;
|
this.timeCreated = timeCreated;
|
||||||
this.timeReceived = this.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;
|
export type DeltaFilter = (delta: Delta) => boolean;
|
||||||
|
|
||||||
|
|
|
@ -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);
|
|
||||||
});
|
|
||||||
}
|
|
|
@ -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]
|
|
||||||
};
|
|
||||||
};
|
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
import Debug from 'debug';
|
import Debug from 'debug';
|
||||||
import EventEmitter from 'events';
|
import EventEmitter from 'events';
|
||||||
import {Delta, DeltaFilter, DeltaID, DeltaNetworkImage} from './delta';
|
import {Delta, DeltaFilter, DeltaID, DeltaNetworkImageV1} from './delta';
|
||||||
import {RhizomeNode} from './node';
|
import {RhizomeNode} from './node';
|
||||||
import {Transactions} from './transactions';
|
import {Transactions} from './transactions';
|
||||||
import {DomainEntityID, PropertyID, PropertyTypes, TransactionID, ViewMany} from "./types";
|
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 CollapsedPointer = {[key: PropertyID]: PropertyTypes};
|
||||||
|
|
||||||
export type CollapsedDelta = Omit<DeltaNetworkImage, 'pointers'> & {
|
export type CollapsedDelta = Omit<DeltaNetworkImageV1, 'pointers'> & {
|
||||||
pointers: CollapsedPointer[];
|
pointers: CollapsedPointer[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ export type FilterExpr = JSONLogic;
|
||||||
|
|
||||||
export type FilterGenerator = () => FilterExpr;
|
export type FilterGenerator = () => FilterExpr;
|
||||||
|
|
||||||
export type PropertyTypes = string | number | undefined;
|
export type PropertyTypes = string | number | null;
|
||||||
|
|
||||||
export type DomainEntityID = string;
|
export type DomainEntityID = string;
|
||||||
export type PropertyID = string;
|
export type PropertyID = string;
|
||||||
|
|
Loading…
Reference in New Issue