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 {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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
124
src/delta.ts
124
src/delta.ts
|
@ -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;
|
||||
|
||||
|
|
|
@ -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 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[];
|
||||
};
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue