فهرست منبع

Fuck it. I'll add 'dist' to git repo

Joeri Exelmans 1 سال پیش
والد
کامیت
d857d8c770
56فایلهای تغییر یافته به همراه2722 افزوده شده و 3 حذف شده
  1. 0 2
      .gitignore
  2. 0 1
      .npmignore
  3. 12 0
      dist/index.d.ts
  4. 29 0
      dist/index.js
  5. 1 0
      dist/index.js.map
  6. 127 0
      dist/lib/delta.d.ts
  7. 412 0
      dist/lib/delta.js
  8. 1 0
      dist/lib/delta.js.map
  9. 1 0
      dist/lib/delta.test.d.ts
  10. 218 0
      dist/lib/delta.test.js
  11. 1 0
      dist/lib/delta.test.js.map
  12. 13 0
      dist/lib/delta_parser.d.ts
  13. 71 0
      dist/lib/delta_parser.js
  14. 1 0
      dist/lib/delta_parser.js.map
  15. 11 0
      dist/lib/delta_registry.d.ts
  16. 88 0
      dist/lib/delta_registry.js
  17. 1 0
      dist/lib/delta_registry.js.map
  18. 121 0
      dist/lib/graph_state.d.ts
  19. 424 0
      dist/lib/graph_state.js
  20. 1 0
      dist/lib/graph_state.js.map
  21. 1 0
      dist/lib/graph_state.test.d.ts
  22. 78 0
      dist/lib/graph_state.test.js
  23. 1 0
      dist/lib/graph_state.test.js.map
  24. 3 0
      dist/lib/mock_node_util.d.ts
  25. 10 0
      dist/lib/mock_node_util.js
  26. 1 0
      dist/lib/mock_node_util.js.map
  27. 2 0
      dist/lib/types.d.ts
  28. 14 0
      dist/lib/types.js
  29. 1 0
      dist/lib/types.js.map
  30. 3 0
      dist/lib/util/assert.d.ts
  31. 28 0
      dist/lib/util/assert.js
  32. 1 0
      dist/lib/util/assert.js.map
  33. 4 0
      dist/lib/util/buffer_xor.d.ts
  34. 21 0
      dist/lib/util/buffer_xor.js
  35. 1 0
      dist/lib/util/buffer_xor.js.map
  36. 1 0
      dist/lib/util/dfs.d.ts
  37. 36 0
      dist/lib/util/dfs.js
  38. 1 0
      dist/lib/util/dfs.js.map
  39. 1 0
      dist/lib/util/dfs.test.d.ts
  40. 38 0
      dist/lib/util/dfs.test.js
  41. 1 0
      dist/lib/util/dfs.test.js.map
  42. 1 0
      dist/lib/util/permutations.d.ts
  43. 18 0
      dist/lib/util/permutations.js
  44. 1 0
      dist/lib/util/permutations.js.map
  45. 1 0
      dist/lib/util/test_helpers.d.ts
  46. 11 0
      dist/lib/util/test_helpers.js
  47. 1 0
      dist/lib/util/test_helpers.js.map
  48. 58 0
      dist/lib/version.d.ts
  49. 499 0
      dist/lib/version.js
  50. 1 0
      dist/lib/version.js.map
  51. 1 0
      dist/lib/version.test.d.ts
  52. 286 0
      dist/lib/version.test.js
  53. 1 0
      dist/lib/version.test.js.map
  54. 12 0
      dist/lib/version_parser.d.ts
  55. 50 0
      dist/lib/version_parser.js
  56. 1 0
      dist/lib/version_parser.js.map

+ 0 - 2
.gitignore

@@ -3,7 +3,5 @@ node_modules/
 # coverage analysis output
 .nyc_output/
 
-dist/
-
 # not interested
 .vscode

+ 0 - 1
.npmignore

@@ -1 +0,0 @@
-# empty file, because otherwise NPM will use .gitignore, but then 'dist' is not included....

+ 12 - 0
dist/index.d.ts

@@ -0,0 +1,12 @@
+export * from "./lib/delta_parser";
+export * from "./lib/delta_registry";
+export * from "./lib/delta";
+export * from "./lib/graph_state";
+export * from "./lib/types";
+export * from "./lib/version";
+export * from "./lib/version_parser";
+export * from "./lib/util/assert";
+export * from "./lib/util/buffer_xor";
+export * from "./lib/util/dfs";
+export * from "./lib/util/permutations";
+export * from "./lib/util/test_helpers";

+ 29 - 0
dist/index.js

@@ -0,0 +1,29 @@
+"use strict";
+var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
+    if (k2 === undefined) k2 = k;
+    var desc = Object.getOwnPropertyDescriptor(m, k);
+    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
+      desc = { enumerable: true, get: function() { return m[k]; } };
+    }
+    Object.defineProperty(o, k2, desc);
+}) : (function(o, m, k, k2) {
+    if (k2 === undefined) k2 = k;
+    o[k2] = m[k];
+}));
+var __exportStar = (this && this.__exportStar) || function(m, exports) {
+    for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
+};
+Object.defineProperty(exports, "__esModule", { value: true });
+__exportStar(require("./lib/delta_parser"), exports);
+__exportStar(require("./lib/delta_registry"), exports);
+__exportStar(require("./lib/delta"), exports);
+__exportStar(require("./lib/graph_state"), exports);
+__exportStar(require("./lib/types"), exports);
+__exportStar(require("./lib/version"), exports);
+__exportStar(require("./lib/version_parser"), exports);
+__exportStar(require("./lib/util/assert"), exports);
+__exportStar(require("./lib/util/buffer_xor"), exports);
+__exportStar(require("./lib/util/dfs"), exports);
+__exportStar(require("./lib/util/permutations"), exports);
+__exportStar(require("./lib/util/test_helpers"), exports);
+//# sourceMappingURL=index.js.map

+ 1 - 0
dist/index.js.map

@@ -0,0 +1 @@
+{"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,qDAAmC;AACnC,uDAAqC;AACrC,8CAA4B;AAC5B,oDAAkC;AAClC,8CAA4B;AAC5B,gDAA8B;AAC9B,uDAAqC;AAErC,oDAAkC;AAClC,wDAAsC;AACtC,iDAA+B;AAC/B,0DAAwC;AACxC,0DAAwC"}

+ 127 - 0
dist/lib/delta.d.ts

@@ -0,0 +1,127 @@
+/// <reference types="node" />
+import { Buffer } from "buffer";
+import { PrimitiveValue, UUID } from "./types";
+type ConflictType = "U/U" | "R/U" | "R*/U" | "U/D" | "D/D";
+export declare abstract class Delta {
+    readonly hash: Buffer;
+    readonly description: string;
+    readonly conflictsWith: [Delta, ConflictType][];
+    readonly partOf: Transaction[];
+    constructor(hash: Buffer, description: string);
+    abstract getDependencies(): readonly [Delta, string][];
+    hasTransitiveDependency(other: Delta): boolean;
+    serialize(): any;
+    abstract iterPrimitiveDeltas(): Generator<PrimitiveDelta>;
+}
+export declare abstract class PrimitiveDelta extends Delta {
+    iterPrimitiveDeltas(): Generator<this, void, unknown>;
+}
+export declare class NodeCreation extends PrimitiveDelta {
+    readonly id: UUID;
+    readonly outgoingEdges: Map<string, NewEdge>;
+    readonly incomingEdges: EdgeUpdate[];
+    readonly readAllOutgoing: ReadAllOutgoing[];
+    readonly deletions: NodeDeletion[];
+    constructor(hash: Buffer, id: UUID);
+    getDependencies(): [Delta, string][];
+    createOutgoingEdge(label: string, after?: ReadAllOutgoing[]): NewEdge;
+    registerOutgoingEdge(u: EdgeUpdate): void;
+    registerIncomingEdge(u: EdgeUpdate): void;
+    registerDeletion(d: NodeDeletion): void;
+    registerReadAllOutgoing(r: ReadAllOutgoing): void;
+    serialize(): any;
+}
+export declare class ReadAllOutgoing extends PrimitiveDelta {
+    readonly node: NodeCreation;
+    readonly after: readonly NewEdge[];
+    constructor(hash: Buffer, node: NodeCreation, after: readonly NewEdge[]);
+    getDependencies(): [Delta, string][];
+}
+export declare class NodeDeletion extends PrimitiveDelta {
+    node: NodeCreation;
+    afterSrc: readonly EdgeUpdate[];
+    afterTgt: readonly EdgeUpdate[];
+    constructor(hash: Buffer, node: NodeCreation, afterSrc: readonly EdgeUpdate[], afterTgt: readonly EdgeUpdate[]);
+    getDependencies(): [Delta, string][];
+    serialize(): any;
+}
+export declare abstract class Edge {
+    readonly source: NodeCreation;
+    readonly label: string;
+    readonly overwrittenBy: EdgeUpdate[];
+    readonly readBy: EdgeUpdate[];
+    constructor(source: NodeCreation, label: string);
+    abstract getDependencies(): [Delta, string][];
+    abstract serialize(): any;
+    iterOverwriters(filter: (u: EdgeUpdate) => boolean): Iterable<EdgeUpdate>;
+    registerWrite(write: EdgeUpdate): void;
+    registerRead(read: EdgeUpdate): void;
+}
+export declare class NewEdge extends Edge {
+    readonly after: readonly ReadAllOutgoing[];
+    readonly edgeId: string;
+    constructor(source: NodeCreation, label: string, after: readonly ReadAllOutgoing[], edgeId: string);
+    getDependencies(): [Delta, string][];
+    serialize(): {
+        type: string;
+        source: string;
+        label: string;
+    };
+}
+export declare class ExistingEdge extends Edge {
+    readonly delta: EdgeUpdate;
+    constructor(source: NodeCreation, label: string, delta: EdgeUpdate);
+    getDependencies(): [Delta, string][];
+    serialize(): {
+        type: string;
+        overwrites: string;
+    };
+}
+export interface Target {
+    value: NodeCreation | PrimitiveValue;
+    registerDependency(u: EdgeUpdate): void;
+    getDependencies(): [Delta, string][];
+    serialize(): any;
+}
+export declare class TargetNode implements Target {
+    value: NodeCreation;
+    constructor(value: NodeCreation);
+    registerDependency(u: EdgeUpdate): void;
+    getDependencies(): [Delta, string][];
+    serialize(): {
+        type: string;
+        node: string;
+    };
+}
+export declare class TargetValue implements Target {
+    value: PrimitiveValue;
+    constructor(value: PrimitiveValue);
+    registerDependency(): void;
+    getDependencies(): [Delta, string][];
+    serialize(): {
+        type: string;
+        value: PrimitiveValue;
+    };
+}
+export declare class EdgeUpdate extends PrimitiveDelta {
+    readonly overwrites: Edge;
+    readonly reads: readonly ExistingEdge[];
+    readonly target: Target;
+    readonly afterReads: readonly EdgeUpdate[];
+    readonly overwritable: ExistingEdge;
+    constructor(hash: Buffer, overwrites: Edge, target: Target, reads: readonly ExistingEdge[], afterReads: readonly EdgeUpdate[]);
+    overwrite(): ExistingEdge;
+    read(): ExistingEdge;
+    getDependencies(): [Delta, string][];
+    serialize(): any;
+}
+export declare class Transaction extends Delta {
+    readonly deltas: readonly Delta[];
+    readonly dependencies: readonly [Transaction, string][];
+    constructor(hash: Buffer, deltas: readonly Delta[], description: string, dependencies: readonly [Transaction, string][]);
+    getDependencies(): readonly [Delta, string][];
+    serialize(): any;
+    iterPrimitiveDeltas(): Generator<PrimitiveDelta, void, unknown>;
+}
+export declare function findTxDependencies(deltas: Delta[], candidates?: Set<Delta>): [Transaction, string][];
+export {};

+ 412 - 0
dist/lib/delta.js

@@ -0,0 +1,412 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.findTxDependencies = exports.Transaction = exports.EdgeUpdate = exports.TargetValue = exports.TargetNode = exports.ExistingEdge = exports.NewEdge = exports.Edge = exports.NodeDeletion = exports.ReadAllOutgoing = exports.NodeCreation = exports.PrimitiveDelta = exports.Delta = void 0;
+const buffer_xor_1 = require("./util/buffer_xor");
+class Delta {
+    constructor(hash, description) {
+        this.conflictsWith = [];
+        this.partOf = [];
+        this.hash = hash;
+        this.description = description;
+    }
+    hasTransitiveDependency(other) {
+        if (this === other)
+            return true;
+        for (const [d] of this.getDependencies()) {
+            if (d.hasTransitiveDependency(other))
+                return true;
+        }
+        return false;
+    }
+    serialize() {
+        return {
+            hash: this.hash.toString('hex'),
+            type: this.constructor.name,
+        };
+    }
+}
+exports.Delta = Delta;
+class PrimitiveDelta extends Delta {
+    // constructor(hash: Buffer, description: string) {
+    //   super(has, description);
+    // }
+    *iterPrimitiveDeltas() {
+        yield this;
+    }
+}
+exports.PrimitiveDelta = PrimitiveDelta;
+function registerConflict(d1, d2, type) {
+    d1.conflictsWith.push([d2, type]);
+    d2.conflictsWith.push([d1, type]);
+}
+class NodeCreation extends PrimitiveDelta {
+    constructor(hash, id) {
+        super(hash, `NEW(${JSON.stringify(id)})`);
+        // Inverse dependencies
+        this.outgoingEdges = new Map();
+        this.incomingEdges = []; // all deltas EVER that set this node as target of an edge.
+        this.readAllOutgoing = [];
+        this.deletions = [];
+        this.id = id;
+    }
+    getDependencies() {
+        return [];
+    }
+    createOutgoingEdge(label, after = []) {
+        const edgeId = label + ':' + (0, buffer_xor_1.buffersXOR)(...after.map(a => a.hash)).toString('hex');
+        return this.outgoingEdges.get(edgeId) || (() => {
+            const newEdge = new NewEdge(this, label, after, edgeId);
+            this.outgoingEdges.set(edgeId, newEdge);
+            return newEdge;
+        })();
+    }
+    registerOutgoingEdge(u) {
+        // A new outgoing edge will always conflict with all existing deletions.
+        for (const d of this.deletions) {
+            registerConflict(d, u, "U/D");
+        }
+        for (const r of this.readAllOutgoing) {
+            if (!u.overwrites.after.includes(r)) {
+                registerConflict(r, u, "R*/U");
+            }
+        }
+        // outgoing edge already stored in Edge.
+    }
+    registerIncomingEdge(u) {
+        // A new incoming edge always will conflict with all existing deletions.
+        for (const d of this.deletions) {
+            registerConflict(d, u, "U/D");
+        }
+        this.incomingEdges.push(u);
+    }
+    registerDeletion(d) {
+        // A new deletion will conflict with all earliest incoming/outgoing edge operations that the deletion does not transitively explicitly depend on.
+        for (const o of this.outgoingEdges.values()) {
+            for (const u of o.iterOverwriters(u => !d.afterSrc.some(a => a.hasTransitiveDependency(u)))) {
+                registerConflict(d, u, "U/D");
+            }
+        }
+        for (const i of this.incomingEdges.filter(i => !d.afterTgt.some(a => a.hasTransitiveDependency(i)))) {
+            registerConflict(i, d, "U/D");
+        }
+        // Delete/Delete conflict
+        for (const other of this.deletions) {
+            if (other !== d) {
+                registerConflict(d, other, "D/D");
+            }
+        }
+        this.deletions.push(d);
+    }
+    registerReadAllOutgoing(r) {
+        // ReadAllOutgoing will conflict with all outgoing edge creations that are not a dependency of ReadAllOutgoing
+        // (in essence just a R/W conflict)
+        for (const o of this.outgoingEdges.values()) {
+            if (!r.after.includes(o)) {
+                // TODO: We could turn NewEdge into an actual delta type. Then we could just conflict with that delta (possibly more efficient, maybe more elegant)
+                for (const u of o.overwrittenBy) {
+                    registerConflict(r, u, "R*/U");
+                }
+            }
+        }
+        this.readAllOutgoing.push(r);
+    }
+    serialize() {
+        return Object.assign(Object.assign({}, super.serialize()), { id: this.id });
+    }
+}
+exports.NodeCreation = NodeCreation;
+// This delta represents getting all outgoing edges of a node.
+// For instance, when getting all the elements of a set (e.g., when checking some constraint), or iterating over a dictionary.
+class ReadAllOutgoing extends PrimitiveDelta {
+    constructor(hash, node, after) {
+        super(hash, `R*(${node.id})`);
+        this.node = node;
+        this.after = after;
+        // Register inverse dependencies:
+        node.registerReadAllOutgoing(this);
+    }
+    getDependencies() {
+        return [
+            [this.node, "N"],
+            ...[].concat(...this.after.map(a => a.overwrittenBy.map(u => [u, "A"]))),
+        ];
+    }
+}
+exports.ReadAllOutgoing = ReadAllOutgoing;
+class NodeDeletion extends PrimitiveDelta {
+    constructor(hash, node, afterSrc, afterTgt) {
+        super(hash, `DEL(${JSON.stringify(node.id)})`);
+        this.node = node;
+        this.afterSrc = afterSrc;
+        this.afterTgt = afterTgt;
+        if (afterSrc.some(a => a.target.value !== null)) {
+            throw new Error("NodeDeletion can only depend on EdgeUpdates that set outgoing edges to null.");
+        }
+        if (afterTgt.some(a => a.target.value === node)) {
+            throw new Error("NodeDeletion cannot depend on EdgeUpdates that set incoming edges to node being deleted.");
+        }
+        // Register our dependencies' inverse dependencies + detect conflicts:
+        node.registerDeletion(this);
+    }
+    getDependencies() {
+        return [
+            [this.node, "D"],
+            ...this.afterSrc.map(u => [u, "A"]),
+            ...this.afterTgt.map(u => [u, "A"]),
+        ];
+    }
+    serialize() {
+        return Object.assign(Object.assign({}, super.serialize()), { node: this.node.hash.toString('hex'), afterSrc: this.afterSrc.map(u => u.hash.toString('hex')), afterTgt: this.afterTgt.map(u => u.hash.toString('hex')) });
+    }
+}
+exports.NodeDeletion = NodeDeletion;
+// Detects write/write and write/read conflicts.
+class Edge {
+    constructor(source, label) {
+        // Inverse dependencies
+        this.overwrittenBy = [];
+        this.readBy = [];
+        this.source = source;
+        this.label = label;
+    }
+    // Iterate over all overwriters, from early to late, depth-first.
+    // When in some branch the 'condition' is satisfied, the satisfying element is yielded, and the descendants ignored.
+    *iterOverwriters(filter) {
+        for (const ovr of this.overwrittenBy) {
+            if (filter(ovr)) {
+                yield ovr;
+            }
+            else {
+                yield* ovr.overwritable.iterOverwriters(filter);
+            }
+        }
+    }
+    registerWrite(write) {
+        for (const other of this.overwrittenBy) {
+            // A write conflicts with all other writes:
+            registerConflict(write, other, "U/U");
+        }
+        for (const read of this.readBy) {
+            if (read !== write && !write.afterReads.includes(read)) {
+                // A write conflicts with all reads:
+                registerConflict(read, write, "R/U");
+            }
+        }
+        this.overwrittenBy.push(write);
+        // Also check conflicts with deletions of source:
+        this.source.registerOutgoingEdge(write);
+    }
+    registerRead(read) {
+        for (const write of this.overwrittenBy) {
+            if (read !== write) {
+                // A read conflicts with all writes:
+                registerConflict(read, write, "R/U");
+            }
+        }
+        this.readBy.push(read);
+    }
+}
+exports.Edge = Edge;
+// An Edge that does not yet have a target
+class NewEdge extends Edge {
+    constructor(source, label, after, edgeId) {
+        super(source, label);
+        this.after = after;
+        this.edgeId = edgeId;
+    }
+    getDependencies() {
+        return [[this.source, "SRC"]];
+    }
+    serialize() {
+        return {
+            type: "NewEdge",
+            source: this.source.hash.toString('hex'),
+            label: this.label,
+        };
+    }
+}
+exports.NewEdge = NewEdge;
+// An Edge that has been assigned a target (by an EdgeUpdate) at least once.
+class ExistingEdge extends Edge {
+    constructor(source, label, delta) {
+        super(source, label);
+        this.delta = delta;
+    }
+    getDependencies() {
+        return [[this.delta, "U"]];
+    }
+    serialize() {
+        return {
+            type: "ExistingEdge",
+            overwrites: this.delta.hash.toString('hex'),
+        };
+    }
+}
+exports.ExistingEdge = ExistingEdge;
+class TargetNode {
+    constructor(value) {
+        this.value = value;
+    }
+    registerDependency(u) {
+        this.value.registerIncomingEdge(u);
+    }
+    getDependencies() {
+        return [[this.value, "TGT"]];
+    }
+    serialize() {
+        return { type: "TargetNode", node: this.value.hash.toString('hex') };
+    }
+}
+exports.TargetNode = TargetNode;
+class TargetValue {
+    constructor(value) {
+        this.value = value;
+    }
+    registerDependency() { }
+    getDependencies() {
+        return [];
+    }
+    serialize() {
+        return { type: "TargetValue", value: this.value };
+    }
+}
+exports.TargetValue = TargetValue;
+class EdgeUpdate extends PrimitiveDelta {
+    constructor(hash, overwrites, target, reads, afterReads) {
+        super(hash, `U(${overwrites.label}->${target.value instanceof NodeCreation ? target.value.description : target.value})`);
+        // Record our own dependencies:
+        this.overwrites = overwrites;
+        this.target = target;
+        this.reads = reads;
+        this.afterReads = afterReads;
+        this.overwritable = new ExistingEdge(overwrites.source, overwrites.label, this);
+        // Register our dependencies' inverse dependencies + detect conflicts:
+        overwrites.registerWrite(this);
+        reads.forEach(r => r.registerRead(this));
+        target.registerDependency(this);
+    }
+    // Makes code slightly easier to read
+    overwrite() {
+        return this.overwritable;
+    }
+    // Makes code slightly easier to read
+    read() {
+        return this.overwritable;
+    }
+    getDependencies() {
+        return this.overwrites.getDependencies()
+            .concat(this.target.getDependencies())
+            .concat(this.reads.map(r => [r.delta, "R"]))
+            .concat(this.afterReads.map(a => [a, "A"]));
+    }
+    serialize() {
+        return Object.assign(Object.assign({}, super.serialize()), { overwrites: this.overwrites.serialize(), target: this.target.serialize(), reads: this.reads.map(r => r.serialize()), afterReads: this.afterReads.map(a => a.hash.toString('hex')) });
+    }
+}
+exports.EdgeUpdate = EdgeUpdate;
+// export class EdgeRead extends PrimitiveDelta {
+//   // Every read has a unique ID (otherwise, different concurrent reads would be represented by the same delta)
+//   readonly id: UUID;
+//   // Dependencies
+//   readonly reads: ExistingEdge;
+//   constructor(hash: Buffer, id: UUID, reads: ExistingEdge) {
+//     super(hash, `R(${reads.label})`);
+//     // Record our own dependencies:
+//     this.reads = reads;
+//     reads.registerRead(this);
+//   }
+//   serialize(): any {
+//     return {
+//       ...super.serialize(),
+//       id: this.id,
+//       reads: this.reads.map(r => r.serialize()),
+//     };
+//   }
+// }
+class Transaction extends Delta {
+    constructor(hash, deltas, description, dependencies) {
+        super(hash, description);
+        this.deltas = deltas;
+        this.dependencies = dependencies;
+        // TODO: validate dependencies.
+        // Derive conflicts from deltas
+        for (const delta of deltas) {
+            for (const [conflictingDelta, conflictType] of delta.conflictsWith) {
+                if (deltas.includes(conflictingDelta)) {
+                    throw new Error("Cannot create a composite delta out of conflicting deltas");
+                }
+                const conflictingTxs = conflictingDelta.partOf;
+                for (const otherTx of conflictingTxs) {
+                    if (!this.conflictsWith.some(([c]) => c === otherTx)) {
+                        registerConflict(this, otherTx, conflictType);
+                    }
+                }
+            }
+        }
+        for (const [dependency] of dependencies) {
+            for (const [c, conflictKind] of this.conflictsWith) {
+                if (c === dependency) {
+                    // // only for debugging, print all the detailed dependencies:
+                    // const detailedDependencies: [Delta,Delta,string][] = [];
+                    // for (const d of deltas) {
+                    //   for (const d2 of dependency.deltas) {
+                    //     for (const [dd, kind] of d.getDependencies()) {
+                    //       if (dd === d2) {
+                    //         detailedDependencies.push([d, d2, kind]);
+                    //       }
+                    //     }
+                    //   }
+                    // }
+                    // console.log({tx: deltas, dependency: dependency.deltas, detailedDependencies});
+                    throw new Error(`Assertion failed: Transaction '${description}' (${hash.toString('hex').substring(0, 8)}) depends on another conflicting (${conflictKind}) transaction '${dependency.description}' (${dependency.hash.toString('hex').substring(0, 8)}).`);
+                }
+            }
+        }
+        deltas.forEach(d => d.partOf.push(this));
+    }
+    getDependencies() {
+        return this.dependencies;
+    }
+    serialize() {
+        return Object.assign(Object.assign({}, super.serialize()), { deltas: this.deltas.map(d => d.hash.toString('hex')), description: this.description, dependencies: this.dependencies.map(([d, kind]) => ({ tx: d.hash.toString('hex'), kind })) });
+    }
+    *iterPrimitiveDeltas() {
+        for (const d of this.deltas) {
+            yield* d.iterPrimitiveDeltas();
+        }
+    }
+}
+exports.Transaction = Transaction;
+// Given a set of deltas that we are trying to glue together in a (new) transaction, what other transactions should this new transaction depend on?
+// Argument 'currentDeltas' is a set of deltas to be considered as possible dependencies. Typically you only want to consider the deltas that make up the current version. This is decide which transaction to depend on, if a delta is contained by multiple transactions. If this argument is left undefined, then an error will be thrown if one of the deltas is contained by multiple transactions.
+function findTxDependencies(deltas, candidates) {
+    const txDependencies = new Map();
+    for (const delta of deltas) {
+        const dependencies = delta.getDependencies();
+        for (const [dependency, kind] of dependencies) {
+            if (!deltas.includes(dependency)) {
+                const txs = dependency.partOf;
+                const filteredTxs = candidates !== undefined
+                    ? txs.filter(tx => candidates.has(tx))
+                    : txs;
+                if (filteredTxs.length > 1) {
+                    // This error can never occur when passing a proper 'candidates' argument.
+                    throw new Error("Error: One of the composite's dependencies is contained by multiple composites.");
+                }
+                if (filteredTxs.length === 0) {
+                    throw new Error("Assertion failed: delta " + delta.description + " depends on " + dependency.description + " but this dependency could not be found in a composite.");
+                }
+                const [tx] = filteredTxs;
+                const kinds = txDependencies.get(tx) || (() => {
+                    const kinds = new Set();
+                    txDependencies.set(tx, kinds);
+                    return kinds;
+                })();
+                kinds.add(kind);
+            }
+        }
+    }
+    return [...txDependencies.entries()].map(([tx, kinds]) => [tx, [...kinds.values()].join(',')]);
+}
+exports.findTxDependencies = findTxDependencies;
+//# sourceMappingURL=delta.js.map

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 1 - 0
dist/lib/delta.js.map


+ 1 - 0
dist/lib/delta.test.d.ts

@@ -0,0 +1 @@
+export {};

+ 218 - 0
dist/lib/delta.test.js

@@ -0,0 +1,218 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+const delta_registry_1 = require("./delta_registry");
+const test_helpers_1 = require("./util/test_helpers");
+const assert_1 = require("./util/assert");
+describe("Primitive Delta", () => {
+    it("Delete/delete node conflict", () => {
+        const registry = new delta_registry_1.DeltaRegistry();
+        const getId = (0, test_helpers_1.mockUuid)();
+        const creation = registry.newNodeCreation(getId());
+        (0, assert_1.assert)(creation.conflictsWith.length === 0, "did not expect node creation to be conflicting with any other operation");
+        const deletion1 = registry.newNodeDeletion(creation, [], []);
+        (0, assert_1.assert)(deletion1.conflictsWith.length === 0, "did not expect first deletion alone to be conflicting with anything");
+        const deletion2 = registry.newNodeDeletion(creation, [], []);
+        (0, assert_1.assert)(deletion1 === deletion2, "expected deltas to be identical");
+        (0, assert_1.assert)(deletion1.conflictsWith.length === 0, "expected no conflicts");
+        (0, assert_1.assert)(deletion1.hash.equals(deletion2.hash), "deletions should have equal hash");
+    });
+    it("Create/create edge conflict", () => {
+        const registry = new delta_registry_1.DeltaRegistry();
+        const getId = (0, test_helpers_1.mockUuid)();
+        const sourceCreation = registry.newNodeCreation(getId());
+        const target1Creation = registry.newNodeCreation(getId());
+        const target2Creation = registry.newNodeCreation(getId());
+        const edge1Creation = registry.newEdgeUpdate(sourceCreation.createOutgoingEdge("label"), target1Creation);
+        (0, assert_1.assert)(edge1Creation.conflictsWith.length === 0, "expected a single edge alone to not be involved in conflicts");
+        const edge2Creation = registry.newEdgeUpdate(sourceCreation.createOutgoingEdge("label"), target2Creation);
+        (0, assert_1.assert)(edge1Creation.conflictsWith.length === 1, "expected conflict: same edge created twice, concurrently");
+        (0, assert_1.assert)(edge2Creation.conflictsWith.length === 1, "expected conflict: same edge created twice, concurrently");
+    });
+    it("Update/update edge conflict", () => {
+        const registry = new delta_registry_1.DeltaRegistry();
+        const getId = (0, test_helpers_1.mockUuid)();
+        const sourceCreation = registry.newNodeCreation(getId());
+        const targetCreation = registry.newNodeCreation(getId());
+        const newTarget1Creation = registry.newNodeCreation(getId());
+        const newTarget2Creation = registry.newNodeCreation(getId());
+        const edgeCreation = registry.newEdgeUpdate(sourceCreation.createOutgoingEdge("label"), targetCreation);
+        const update1 = registry.newEdgeUpdate(edgeCreation.overwrite(), newTarget1Creation);
+        (0, assert_1.assert)(update1.conflictsWith.length === 0, "expected no conflict with a single edge update.");
+        const update2 = registry.newEdgeUpdate(edgeCreation.overwrite(), newTarget2Creation);
+        (0, assert_1.assert)(update1.conflictsWith.length === 1, "expected conflict between concurrent edge updates.");
+        (0, assert_1.assert)(update2.conflictsWith.length === 1, "expected conflict between concurrent edge updates.");
+    });
+    it("Delete/require (edge source) conflict", () => {
+        const registry = new delta_registry_1.DeltaRegistry();
+        const getId = (0, test_helpers_1.mockUuid)();
+        const sourceCreation = registry.newNodeCreation(getId());
+        const targetCreation = registry.newNodeCreation(getId());
+        const sourceDeletion = registry.newNodeDeletion(sourceCreation, [], []);
+        (0, assert_1.assert)(sourceDeletion.conflictsWith.length === 0, "expected no conflicts so far");
+        const edgeCreation = registry.newEdgeUpdate(sourceCreation.createOutgoingEdge("label"), targetCreation);
+        (0, assert_1.assert)(edgeCreation.conflictsWith.length === 1, "expected require/delete conflict, because edge source is concurrently deleted");
+        (0, assert_1.assert)(sourceDeletion.conflictsWith.length === 1, "expected require/delete conflict, because edge source is concurrently deleted");
+    });
+    it("Delete/require (edge source) conflict (reverse order)", () => {
+        const registry = new delta_registry_1.DeltaRegistry();
+        const getId = (0, test_helpers_1.mockUuid)();
+        // same as before, but now the 'order' of edgeCreation and sourceDeletion is reversed.
+        const sourceCreation = registry.newNodeCreation(getId());
+        const targetCreation = registry.newNodeCreation(getId());
+        const edgeCreation = registry.newEdgeUpdate(sourceCreation.createOutgoingEdge("label"), targetCreation);
+        (0, assert_1.assert)(edgeCreation.conflictsWith.length === 0, "expected no conflicts so far");
+        const sourceDeletion = registry.newNodeDeletion(sourceCreation, [], []);
+        (0, assert_1.assert)(edgeCreation.conflictsWith.length === 1, "expected require/delete conflict, because edge source is concurrently deleted");
+        (0, assert_1.assert)(sourceDeletion.conflictsWith.length === 1, "expected require/delete conflict, because edge source is concurrently deleted");
+    });
+    it("Require (edge source), then delete (no conflict)", () => {
+        const registry = new delta_registry_1.DeltaRegistry();
+        const getId = (0, test_helpers_1.mockUuid)();
+        // proper way of deleting the source of an edge: the deletion must depend on the edgeCreation
+        const sourceCreation = registry.newNodeCreation(getId());
+        const targetCreation = registry.newNodeCreation(getId());
+        const edgeCreation = registry.newEdgeUpdate(sourceCreation.createOutgoingEdge("label"), targetCreation);
+        const edgeDeletion = registry.newEdgeUpdate(edgeCreation.overwrite(), null);
+        const sourceDeletion = registry.newNodeDeletion(sourceCreation, [edgeDeletion], []);
+        (0, assert_1.assert)(sourceDeletion.conflictsWith.length === 0, "Since node deletion was aware of outgoing edge deletion, there should be no conflict");
+    });
+    it("Delete/require (edge source) conflict (3)", () => {
+        const registry = new delta_registry_1.DeltaRegistry();
+        const getId = (0, test_helpers_1.mockUuid)();
+        // Same as (2), really, because the additional EdgeUpdate doesn't change anything.
+        // Only the earliest conflict, between EdgeCreation and source NodeDeletion, matters.
+        const sourceCreation = registry.newNodeCreation(getId());
+        const targetCreation = registry.newNodeCreation(getId());
+        const edgeCreation = registry.newEdgeUpdate(sourceCreation.createOutgoingEdge("label"), targetCreation);
+        const newTargetCreation = registry.newNodeCreation(getId());
+        const edgeUpdate = registry.newEdgeUpdate(edgeCreation.overwrite(), newTargetCreation);
+        // no conflicts so far
+        const sourceDeletion = registry.newNodeDeletion(sourceCreation, [], []);
+        (0, assert_1.assert)(edgeCreation.conflictsWith.length === 1, "expected require/delete conflict, because edge source is concurrently deleted");
+        (0, assert_1.assert)(sourceDeletion.conflictsWith.length === 1, "expected require/delete conflict, because edge source is concurrently deleted");
+        (0, assert_1.assert)(edgeUpdate.conflictsWith.length === 0, "edge update should not be involved in any conflict");
+    });
+    it("Update edge after deletion of source node conflict", () => {
+        const registry = new delta_registry_1.DeltaRegistry();
+        const getId = (0, test_helpers_1.mockUuid)();
+        // create edge between 2 nodes, and then properly delete the source node
+        const sourceCreation = registry.newNodeCreation(getId());
+        const targetCreation = registry.newNodeCreation(getId());
+        const edgeCreation = registry.newEdgeUpdate(sourceCreation.createOutgoingEdge("label"), targetCreation);
+        const edgeDeletion = registry.newEdgeUpdate(edgeCreation.overwrite(), null);
+        const sourceDeletion = registry.newNodeDeletion(sourceCreation, [edgeDeletion], []);
+        // no conflicts so far
+        const newTargetCreation = registry.newNodeCreation(getId()); // no conflict
+        const edgeUpdate = registry.newEdgeUpdate(edgeCreation.overwrite(), newTargetCreation);
+        // console.log("edgeUpdate.conflictsWith", edgeUpdate.conflictsWith, "edgeDeletion.conflictsWith", edgeDeletion.conflictsWith);
+        (0, assert_1.assert)(edgeUpdate.conflictsWith.length === 2, "expected U/U conflict");
+        (0, assert_1.assert)(edgeUpdate.conflictsWith.some(([d]) => d === edgeDeletion), "expected U/U conflict");
+        (0, assert_1.assert)(edgeUpdate.conflictsWith.some(([d]) => d === sourceDeletion), "expected U/U conflict");
+        (0, assert_1.assert)(edgeDeletion.conflictsWith.length === 1, "expected U/U conflict");
+        (0, assert_1.assert)(edgeDeletion.conflictsWith.some(([d]) => d === edgeUpdate), "expected U/U conflict");
+    });
+    it("Delete/require (edge target) conflict", () => {
+        const registry = new delta_registry_1.DeltaRegistry();
+        const getId = (0, test_helpers_1.mockUuid)();
+        const sourceCreation = registry.newNodeCreation(getId());
+        const targetCreation = registry.newNodeCreation(getId());
+        const targetDeletion = registry.newNodeDeletion(targetCreation, [], []);
+        const edgeCreation = registry.newEdgeUpdate(sourceCreation.createOutgoingEdge("label"), targetCreation);
+        (0, assert_1.assert)(edgeCreation.conflictsWith.length === 1, "expected require/delete conflict");
+        (0, assert_1.assert)(targetDeletion.conflictsWith.length === 1, "expected require/delete conflict");
+    });
+    it("Delete/require (edge target) conflict (reverse order)", () => {
+        const registry = new delta_registry_1.DeltaRegistry();
+        const getId = (0, test_helpers_1.mockUuid)();
+        const sourceCreation = registry.newNodeCreation(getId());
+        const targetCreation = registry.newNodeCreation(getId());
+        const edgeCreation = registry.newEdgeUpdate(sourceCreation.createOutgoingEdge("label"), targetCreation);
+        // delete target of edge, unaware that edge exists:
+        const targetDeletion = registry.newNodeDeletion(targetCreation, [], []);
+        (0, assert_1.assert)(edgeCreation.conflictsWith.length === 1, "expected require/delete conflict");
+        (0, assert_1.assert)(targetDeletion.conflictsWith.length === 1, "expected require/delete conflict");
+    });
+    it("Require (edge target), then delete (no conflict)", () => {
+        const registry = new delta_registry_1.DeltaRegistry();
+        const getId = (0, test_helpers_1.mockUuid)();
+        const sourceCreation = registry.newNodeCreation(getId());
+        const targetCreation = registry.newNodeCreation(getId());
+        const edgeCreation = registry.newEdgeUpdate(sourceCreation.createOutgoingEdge("label"), targetCreation);
+        const edgeUpdate = registry.newEdgeUpdate(edgeCreation.overwrite(), sourceCreation); // turn edge into self-edge
+        // because of the edgeUpdate, 'target' is no longer the target of the edge, and can be deleted:
+        const targetDeletion = registry.newNodeDeletion(targetCreation, [], [edgeUpdate]);
+        // console.log(edgeCreation.conflictsWith)
+        (0, assert_1.assert)(edgeCreation.conflictsWith.length === 0, "expected no require/delete conflict");
+        (0, assert_1.assert)(targetDeletion.conflictsWith.length === 0, "expected no require/delete conflict");
+    });
+    it("Delete source and target of edge (no conflict)", () => {
+        const registry = new delta_registry_1.DeltaRegistry();
+        const getId = (0, test_helpers_1.mockUuid)();
+        const sourceCreation = registry.newNodeCreation(getId());
+        const targetCreation = registry.newNodeCreation(getId());
+        const edgeCreation = registry.newEdgeUpdate(sourceCreation.createOutgoingEdge("label"), targetCreation);
+        const edgeDeletion = registry.newEdgeUpdate(edgeCreation.overwrite(), null);
+        const sourceDeletion = registry.newNodeDeletion(sourceCreation, [edgeDeletion], []);
+        (0, assert_1.assert)(sourceDeletion.conflictsWith.length === 0, "expected no conflicts");
+        const targetDeletion = registry.newNodeDeletion(targetCreation, [], [edgeDeletion]);
+        (0, assert_1.assert)(targetDeletion.conflictsWith.length === 0, "expected no conflicts");
+    });
+    it("Delete node with self-edge", () => {
+        const registry = new delta_registry_1.DeltaRegistry();
+        const getId = (0, test_helpers_1.mockUuid)();
+        const nodeCreation = registry.newNodeCreation(getId());
+        const edgeCreation = registry.newEdgeUpdate(nodeCreation.createOutgoingEdge("label"), nodeCreation);
+        const edgeDeletion = registry.newEdgeUpdate(edgeCreation.overwrite(), null);
+        const nodeDeletion = registry.newNodeDeletion(nodeCreation, [edgeDeletion], [edgeDeletion]);
+        // console.log(nodeDeletion.conflictsWith);
+        (0, assert_1.assert)(nodeDeletion.conflictsWith.length === 0, "expected no conflicts");
+    });
+    it("Read/write conflict", () => {
+        const registry = new delta_registry_1.DeltaRegistry();
+        const getId = (0, test_helpers_1.mockUuid)();
+        const envCreation = registry.newNodeCreation(getId());
+        const xInitial = registry.newEdgeUpdate(envCreation.createOutgoingEdge("x"), 1);
+        const yInitial = registry.newEdgeUpdate(envCreation.createOutgoingEdge("y"), 2);
+        // so now, x == 1, y == 2
+        // then, x := x + 1
+        const xUpdate = registry.newEdgeUpdate(xInitial.overwrite(), 2, [xInitial.overwrite()]);
+        // and concurrently, y := x + y
+        const yUpdate = registry.newEdgeUpdate(yInitial.overwrite(), 3, [xInitial.overwrite(), yInitial.overwrite()]);
+        (0, assert_1.assert)(xUpdate.conflictsWith.length === 1 && yUpdate.conflictsWith.length === 1, "expected one conflict");
+        (0, assert_1.assert)(xUpdate.conflictsWith.some(([d]) => d === yUpdate), "expected one conflict");
+    });
+    it("No Read/Read conflict", () => {
+        const registry = new delta_registry_1.DeltaRegistry();
+        const getId = (0, test_helpers_1.mockUuid)();
+        const envCreation = registry.newNodeCreation(getId());
+        const xInitial = registry.newEdgeUpdate(envCreation.createOutgoingEdge("x"), 1);
+        // so now, x == 1
+        // then, y := x + 1
+        const yInitial = registry.newEdgeUpdate(envCreation.createOutgoingEdge("y"), 2, [xInitial.overwrite()]);
+        // and concurrently, z := x + 2
+        const zInitial = registry.newEdgeUpdate(envCreation.createOutgoingEdge("z"), 3, [xInitial.overwrite()]);
+        (0, assert_1.assert)(yInitial.conflictsWith.length === 0 && zInitial.conflictsWith.length === 0, "expected no conflicts");
+    });
+    it("R*/U conflict", () => {
+        const registry = new delta_registry_1.DeltaRegistry();
+        const getId = (0, test_helpers_1.mockUuid)();
+        const collection = registry.newNodeCreation(getId());
+        const newEdge = collection.createOutgoingEdge("x");
+        (0, assert_1.assert)(newEdge === collection.createOutgoingEdge("x"), "Should return same object");
+        const addItemX = registry.newEdgeUpdate(newEdge, "x");
+        (0, assert_1.assert)(newEdge === collection.createOutgoingEdge("x"), "Should return same object");
+        (0, assert_1.assert)(addItemX.conflictsWith.length === 0, "No conflicts so far");
+        // Now we'll create our conflict:
+        const readAll = registry.newReadAllOutgoing(collection, [collection.createOutgoingEdge("x")]);
+        const addItemY = registry.newEdgeUpdate(collection.createOutgoingEdge("y"), "y");
+        console.log(collection);
+        console.log(readAll.conflictsWith);
+        console.log(addItemY.conflictsWith);
+        (0, assert_1.assert)(readAll.conflictsWith.length === 1
+            && addItemY.conflictsWith.length === 1, "Expected one conflict here");
+        (0, assert_1.assert)(readAll.conflictsWith.some(([d]) => d === addItemY)
+            && addItemY.conflictsWith.some(([d]) => d === readAll), "Expected 'addItemY' and 'readAll' to be conflicting");
+        (0, assert_1.assert)(addItemX.conflictsWith.length === 0, "Still no conflicts here");
+    });
+});
+//# sourceMappingURL=delta.test.js.map

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 1 - 0
dist/lib/delta.test.js.map


+ 13 - 0
dist/lib/delta_parser.d.ts

@@ -0,0 +1,13 @@
+import { Delta } from "./delta";
+import { DeltaRegistry } from "./delta_registry";
+export declare class DeltaParser {
+    readonly deltaRegistry: DeltaRegistry;
+    constructor(deltaRegistry: any);
+    private getDependency;
+    private loadTarget;
+    private loadEdge;
+    loadDelta({ type, ...rest }: {
+        [x: string]: any;
+        type: any;
+    }): Delta;
+}

+ 71 - 0
dist/lib/delta_parser.js

@@ -0,0 +1,71 @@
+"use strict";
+var __rest = (this && this.__rest) || function (s, e) {
+    var t = {};
+    for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
+        t[p] = s[p];
+    if (s != null && typeof Object.getOwnPropertySymbols === "function")
+        for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
+            if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
+                t[p[i]] = s[p[i]];
+        }
+    return t;
+};
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.DeltaParser = void 0;
+class DeltaParser {
+    constructor(deltaRegistry) {
+        this.deltaRegistry = deltaRegistry;
+    }
+    getDependency(hash) {
+        const result = this.deltaRegistry.deltas.get(hash);
+        if (result === undefined)
+            throw new Error("Could not dependency: " + hash);
+        return result;
+    }
+    loadTarget(_a) {
+        var { type } = _a, rest = __rest(_a, ["type"]);
+        if (type === "TargetValue") {
+            const { value } = rest;
+            return value;
+        }
+        if (type === "TargetNode") {
+            const { node } = rest;
+            return this.getDependency(node);
+        }
+        throw new Error("Unknown edge target type: " + type);
+    }
+    loadEdge(_a) {
+        var { type } = _a, rest = __rest(_a, ["type"]);
+        if (type === "NewEdge") {
+            const { source, label } = rest;
+            return this.getDependency(source).createOutgoingEdge(label);
+        }
+        else if (type === "ExistingEdge") {
+            const { overwrites } = rest;
+            return this.getDependency(overwrites).overwrite();
+        }
+        throw new Error("Unknown edge type: " + type);
+    }
+    loadDelta(_a) {
+        var { type } = _a, rest = __rest(_a, ["type"]);
+        if (type === "NodeCreation") {
+            const { id } = rest;
+            return this.deltaRegistry.newNodeCreation(id);
+        }
+        else if (type === "EdgeUpdate") {
+            const { overwrites, reads, target } = rest;
+            return this.deltaRegistry.newEdgeUpdate(this.loadEdge(overwrites), this.loadTarget(target), reads.map(r => this.loadEdge(r)));
+        }
+        else if (type === "NodeDeletion") {
+            const { node, afterSrc, afterTgt } = rest;
+            return this.deltaRegistry.newNodeDeletion(this.getDependency(node), afterSrc.map(a => this.getDependency(a)), afterTgt.map(a => this.getDependency(a)));
+        }
+        else if (type === "Transaction") {
+            const { type, deltas, dependencies, description } = rest;
+            return this.deltaRegistry.newTransaction(deltas.map(d => this.getDependency(d)), description, dependencies.map(d => this.getDependency(d)));
+        }
+        throw new Error("Unknown delta type: " + type);
+    }
+}
+exports.DeltaParser = DeltaParser;
+//# sourceMappingURL=delta_parser.js.map

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 1 - 0
dist/lib/delta_parser.js.map


+ 11 - 0
dist/lib/delta_registry.d.ts

@@ -0,0 +1,11 @@
+import { Delta, Transaction, NodeCreation, NodeDeletion, EdgeUpdate, Edge, ExistingEdge, NewEdge, ReadAllOutgoing } from "./delta";
+import { PrimitiveValue, UUID } from "./types";
+export declare class DeltaRegistry {
+    deltas: Map<string, Delta>;
+    private createIdempotent;
+    newNodeCreation(id: UUID): NodeCreation;
+    newReadAllOutgoing(node: NodeCreation, after: readonly NewEdge[]): ReadAllOutgoing;
+    newNodeDeletion(creation: NodeCreation, afterSrc: readonly EdgeUpdate[], afterTgt: readonly EdgeUpdate[]): NodeDeletion;
+    newEdgeUpdate(overwrites: Edge, target: NodeCreation | PrimitiveValue, reads?: readonly ExistingEdge[], afterReads?: readonly EdgeUpdate[]): EdgeUpdate;
+    newTransaction(deltas: Array<Delta>, description: string, dependencies?: readonly [Transaction, string][]): Transaction;
+}

+ 88 - 0
dist/lib/delta_registry.js

@@ -0,0 +1,88 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.DeltaRegistry = void 0;
+const delta_1 = require("./delta");
+const crypto_1 = require("crypto");
+const buffer_xor_1 = require("./util/buffer_xor");
+function string2Hash(data) {
+    return (0, crypto_1.createHash)('sha256')
+        .update(data)
+        .digest();
+}
+function json2Hash(data) {
+    return string2Hash(JSON.stringify(data));
+}
+function getHash(obj) {
+    return json2Hash(obj.serialize());
+}
+// Ensures that deltas with the same hash are only created once.
+class DeltaRegistry {
+    constructor() {
+        this.deltas = new Map();
+    }
+    // Given the expected hash 
+    createIdempotent(hash, callback) {
+        const hex = hash.toString('hex');
+        return this.deltas.get(hex) || (() => {
+            const delta = callback();
+            this.deltas.set(hex, delta);
+            return delta;
+        })();
+    }
+    newNodeCreation(id) {
+        const hash = (0, crypto_1.createHash)('sha256')
+            .update('id=')
+            .update(JSON.stringify(id)) // prevent collisions between 'true' (actual boolean) and '"true"' (string "true"), or 42 (number) and "42" (string)
+            .digest();
+        return this.createIdempotent(hash, () => new delta_1.NodeCreation(hash, id));
+    }
+    newReadAllOutgoing(node, after) {
+        const hash = (0, crypto_1.createHash)('sha256')
+            .update('node=')
+            .update(getHash(node))
+            .update((0, buffer_xor_1.buffersXOR)(...after.map(a => string2Hash(a.edgeId))))
+            .digest();
+        return this.createIdempotent(hash, () => new delta_1.ReadAllOutgoing(hash, node, after));
+    }
+    newNodeDeletion(creation, afterSrc, afterTgt) {
+        const hash = (0, crypto_1.createHash)('sha256')
+            .update('deletes=')
+            .update(getHash(creation))
+            .update('afterSrc=')
+            .update((0, buffer_xor_1.buffersXOR)(...afterSrc.map(getHash)))
+            .update('afterTgt=')
+            .update((0, buffer_xor_1.buffersXOR)(...afterTgt.map(getHash)))
+            .digest();
+        return this.createIdempotent(hash, () => new delta_1.NodeDeletion(hash, creation, afterSrc, afterTgt));
+    }
+    newEdgeUpdate(overwrites, target, reads = [], afterReads = []) {
+        const wrappedTarget = target instanceof delta_1.NodeCreation ? new delta_1.TargetNode(target) : new delta_1.TargetValue(target);
+        const hash = (0, crypto_1.createHash)('sha256')
+            .update('overwrites=')
+            .update(getHash(overwrites))
+            .update('target=')
+            .update(getHash(wrappedTarget))
+            .update('reads=')
+            // XOR, because order in which 'reads' are specified shouldn't matter:
+            .update((0, buffer_xor_1.buffersXOR)(...reads.map(getHash)))
+            .update('afterReads=')
+            .update((0, buffer_xor_1.buffersXOR)(...afterReads.map(getHash)))
+            .digest();
+        if (hash.toString('hex').startsWith('94298b')) {
+            console.log("registry - our special delta ... afterReads.length=", afterReads.length);
+        }
+        return this.createIdempotent(hash, () => new delta_1.EdgeUpdate(hash, overwrites, wrappedTarget, reads, afterReads));
+    }
+    newTransaction(deltas, description, dependencies = (0, delta_1.findTxDependencies)(deltas)) {
+        // XOR of hashes of deltas
+        const hash = (0, crypto_1.createHash)('sha256')
+            .update('deltas=')
+            .update((0, buffer_xor_1.buffersXOR)(...deltas.map(getHash)))
+            .update('dependencies=')
+            .update((0, buffer_xor_1.buffersXOR)(...dependencies.map(([tx]) => getHash(tx))))
+            .digest();
+        return this.createIdempotent(hash, () => new delta_1.Transaction(hash, deltas, description, dependencies));
+    }
+}
+exports.DeltaRegistry = DeltaRegistry;
+//# sourceMappingURL=delta_registry.js.map

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 1 - 0
dist/lib/delta_registry.js.map


+ 121 - 0
dist/lib/graph_state.d.ts

@@ -0,0 +1,121 @@
+import { Delta } from "./delta";
+import { NodeCreation, NodeDeletion, EdgeUpdate, ExistingEdge } from "./delta";
+import { DeltaRegistry } from "./delta_registry";
+import { PrimitiveValue } from "./types";
+export interface GraphStateListener {
+    createNode(ns: NodeState): void;
+    createValue(vs: ValueState): void;
+    deleteNode(id: PrimitiveValue): void;
+    deleteValue(value: PrimitiveValue): void;
+    createLinkToNode(sourceId: PrimitiveValue, label: string, targetId: PrimitiveValue): void;
+    createLinkToValue(sourceId: PrimitiveValue, label: string, targetValue: PrimitiveValue): void;
+    deleteLink(sourceId: PrimitiveValue, label: string): void;
+}
+export declare class FanOutListener implements GraphStateListener {
+    readonly listeners: GraphStateListener[];
+    constructor(listeners: GraphStateListener[]);
+    createNode(ns: NodeState): void;
+    createValue(vs: ValueState): void;
+    deleteNode(id: PrimitiveValue): void;
+    deleteValue(value: PrimitiveValue): void;
+    createLinkToNode(sourceId: PrimitiveValue, label: string, targetId: PrimitiveValue): void;
+    createLinkToValue(sourceId: PrimitiveValue, label: string, targetValue: PrimitiveValue): void;
+    deleteLink(sourceId: PrimitiveValue, label: string): void;
+}
+export declare class DummyListener implements GraphStateListener {
+    createNode(ns: NodeState): void;
+    createValue(vs: ValueState): void;
+    deleteNode(id: PrimitiveValue): void;
+    deleteValue(value: PrimitiveValue): void;
+    createLinkToNode(sourceId: PrimitiveValue, label: string, targetId: PrimitiveValue): void;
+    createLinkToValue(sourceId: PrimitiveValue, label: string, targetValue: PrimitiveValue): void;
+    deleteLink(sourceId: PrimitiveValue, label: string): void;
+}
+declare abstract class Common {
+    readonly currentlyIncoming: Array<[string, EdgeUpdate, NodeState]>;
+    readonly previouslyIncoming: Set<EdgeUpdate>;
+    addIncoming(label: string, delta: EdgeUpdate, srcState: NodeState, listener: GraphStateListener): void;
+    noLongerIncoming(overwritten: EdgeUpdate, overwriter: EdgeUpdate, listener: GraphStateListener): void;
+    unAddIncoming(delta: EdgeUpdate, listener: GraphStateListener): void;
+    incomingAgain(label: string, unOverwritten: EdgeUpdate, srcState: NodeState, unOverwriter: EdgeUpdate, listener: GraphStateListener): void;
+    getIncomingEdges(): [string, INodeState][];
+    abstract isNode(uuid: PrimitiveValue): boolean;
+    abstract isValue(value: PrimitiveValue): boolean;
+    abstract asTarget(): NodeCreation | PrimitiveValue;
+    abstract createLinkTo(sourceId: PrimitiveValue, label: string, listener: GraphStateListener): void;
+    abstract getDeltaForSetEdge(registry: DeltaRegistry, label: string, target: NodeCreation | PrimitiveValue, reads?: ExistingEdge[]): EdgeUpdate;
+    abstract getDeltasForDelete(registry: DeltaRegistry): (EdgeUpdate | NodeDeletion)[];
+    getReads(label: string): EdgeUpdate[];
+    getIncomingEdgeDependenciesForDelete(registry: DeltaRegistry): [EdgeUpdate[], EdgeUpdate[]];
+}
+interface ICommon {
+    getIncomingEdges(): [string, INodeState][];
+    getDeltaForSetEdge(registry: DeltaRegistry, label: string, target: NodeCreation | PrimitiveValue, reads?: ExistingEdge[]): EdgeUpdate;
+    getDeltasForDelete(registry: DeltaRegistry): (EdgeUpdate | NodeDeletion)[];
+    asTarget(): NodeCreation | PrimitiveValue;
+    isNode(uuid: PrimitiveValue): boolean;
+    isValue(value: PrimitiveValue): boolean;
+}
+export interface IValueState extends ICommon {
+    readonly type: "value";
+    readonly value: PrimitiveValue;
+}
+export interface INodeState extends ICommon {
+    readonly type: "node";
+    readonly outgoingDeltas: ReadonlyMap<string, EdgeUpdate>;
+    readonly outgoing: ReadonlyMap<string, IValueState | INodeState>;
+    readonly creation: NodeCreation;
+    readonly isDeleted: boolean;
+}
+declare class NodeState extends Common implements INodeState {
+    readonly type = "node";
+    readonly creation: NodeCreation;
+    readonly outgoingDeltas: Map<string, EdgeUpdate>;
+    readonly outgoingReads: Map<string, EdgeUpdate[]>;
+    readonly outgoing: Map<string, IValueState | INodeState>;
+    isDeleted: boolean;
+    constructor(creation: NodeCreation);
+    isNode(uuid: PrimitiveValue): boolean;
+    isValue(value: PrimitiveValue): boolean;
+    asTarget(): NodeCreation;
+    createLinkTo(sourceId: PrimitiveValue, label: string, listener: GraphStateListener): void;
+    getReads(label: string): EdgeUpdate[];
+    getDeltaForSetEdge(registry: DeltaRegistry, label: string, target: NodeCreation | PrimitiveValue, reads?: readonly ExistingEdge[]): EdgeUpdate;
+    getDeltasForDelete(registry: DeltaRegistry): (EdgeUpdate | NodeDeletion)[];
+}
+declare class ValueState extends Common implements IValueState {
+    readonly type = "value";
+    shown: boolean;
+    readonly value: PrimitiveValue;
+    constructor(value: PrimitiveValue);
+    isNode(uuid: PrimitiveValue): boolean;
+    isValue(value: PrimitiveValue): boolean;
+    asTarget(): PrimitiveValue;
+    createLinkTo(sourceId: PrimitiveValue, label: string, listener: GraphStateListener): void;
+    addIncoming(label: string, delta: EdgeUpdate, srcState: NodeState, listener: GraphStateListener): void;
+    noLongerIncoming(overwritten: EdgeUpdate, overwriter: EdgeUpdate, listener: GraphStateListener): void;
+    unAddIncoming(delta: EdgeUpdate, listener: GraphStateListener): void;
+    incomingAgain(label: string, unOverwritten: EdgeUpdate, srcState: NodeState, unOverwriter: EdgeUpdate, listener: GraphStateListener): void;
+    private _showOrHide;
+    getDeltaForSetEdge(registry: DeltaRegistry, label: string, target: NodeCreation | PrimitiveValue, reads?: ExistingEdge[]): EdgeUpdate;
+    getDeltasForDelete(registry: DeltaRegistry): (EdgeUpdate | NodeDeletion)[];
+}
+export declare class GraphState {
+    readonly nodes: Map<PrimitiveValue, NodeState>;
+    readonly values: Map<PrimitiveValue, ValueState>;
+    private deltasSinceCheckpoint;
+    readonly deltas: Set<Delta>;
+    pushState(): void;
+    popState(): Array<Delta>;
+    exec(delta: Delta, listener?: GraphStateListener): void;
+    unexec(delta: Delta, listener?: GraphStateListener): void;
+    private _getEdgeTargetState;
+    private getValueState;
+    private execNodeCreation;
+    private unexecNodeCreation;
+    private execNodeDeletion;
+    private unexecNodeDeletion;
+    private execEdgeUpdate;
+    private unexecEdgeUpdate;
+}
+export {};

+ 424 - 0
dist/lib/graph_state.js

@@ -0,0 +1,424 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.GraphState = exports.DummyListener = exports.FanOutListener = void 0;
+const delta_1 = require("./delta");
+const delta_2 = require("./delta");
+// A 'proxy' GraphStateListener that multicasts graph state operations to a bunch of GraphStateListeners.
+class FanOutListener {
+    constructor(listeners) {
+        this.listeners = listeners;
+    }
+    createNode(ns) {
+        this.listeners.forEach(m => m.createNode(ns));
+    }
+    createValue(vs) {
+        this.listeners.forEach(m => m.createValue(vs));
+    }
+    deleteNode(id) {
+        this.listeners.forEach(m => m.deleteNode(id));
+    }
+    deleteValue(value) {
+        this.listeners.forEach(m => m.deleteValue(value));
+    }
+    createLinkToNode(sourceId, label, targetId) {
+        this.listeners.forEach(m => m.createLinkToNode(sourceId, label, targetId));
+    }
+    createLinkToValue(sourceId, label, targetValue) {
+        this.listeners.forEach(m => m.createLinkToValue(sourceId, label, targetValue));
+    }
+    deleteLink(sourceId, label) {
+        this.listeners.forEach(m => m.deleteLink(sourceId, label));
+    }
+}
+exports.FanOutListener = FanOutListener;
+class DummyListener {
+    createNode(ns) { }
+    createValue(vs) { }
+    deleteNode(id) { }
+    deleteValue(value) { }
+    createLinkToNode(sourceId, label, targetId) { }
+    createLinkToValue(sourceId, label, targetValue) { }
+    deleteLink(sourceId, label) { }
+}
+exports.DummyListener = DummyListener;
+const DUMMY = new DummyListener();
+// Helper
+function removeFromArray(arr, cb) {
+    arr.splice(arr.findIndex(cb), 1);
+}
+class Common {
+    constructor() {
+        // For every currently incoming edge, the label of the edge, the delta that set the edge, and the source of the edge.
+        this.currentlyIncoming = [];
+        // For every previously incoming edge, the delta that set the edge to point somewhere else.
+        this.previouslyIncoming = new Set();
+    }
+    addIncoming(label, delta, srcState, listener) {
+        this.currentlyIncoming.push([label, delta, srcState]);
+    }
+    noLongerIncoming(overwritten, overwriter, listener) {
+        removeFromArray(this.currentlyIncoming, ([_, d]) => d === overwritten);
+        this.previouslyIncoming.add(overwriter);
+    }
+    // only called when undoing a Delta
+    unAddIncoming(delta, listener) {
+        removeFromArray(this.currentlyIncoming, ([_, d]) => d === delta);
+    }
+    // only called when undoing a Delta
+    incomingAgain(label, unOverwritten, srcState, unOverwriter, listener) {
+        this.currentlyIncoming.push([label, unOverwritten, srcState]);
+        this.previouslyIncoming.delete(unOverwriter);
+    }
+    getIncomingEdges() {
+        return this.currentlyIncoming.map(([label, _, srcState]) => [label, srcState]);
+    }
+    getReads(label) {
+        return [];
+    }
+    // idempotent - may create some new deltas but does not execute them
+    getIncomingEdgeDependenciesForDelete(registry) {
+        const newDeltas = this.currentlyIncoming.map(([label, u, ns]) => {
+            return registry.newEdgeUpdate(u.overwrite(), null, [], ns.getReads(label).slice());
+        });
+        return [[...this.previouslyIncoming].concat(newDeltas), newDeltas];
+    }
+}
+// In order to edit a graph, we must know what operations most recently "touched" every node, and every edge. This is because new edit operations can depend on earlier operations (that they overwrite).
+// This class captures, for a single node, a set of most-recent operations. It also has methods for editing the node. These methods are "pure" (they have no side-effects): they only return Deltas that capture the change. The change doesn't happen until those Deltas are (re)played, with GraphState.
+class NodeState extends Common {
+    constructor(creation) {
+        super();
+        this.type = "node";
+        // For every outgoing edge, the Delta that set this edge to its current value
+        this.outgoingDeltas = new Map();
+        // For every outgoing edge, the set of deltas that read the value of this edge.
+        // A new EdgeUpdate of this edge must depend ("after") on all these reads.
+        // Careful: the values (which are arrays) are updated in-place as GraphState (un)executes deltas!
+        this.outgoingReads = new Map();
+        // All currently outgoing edges. Edges that were set to null will not be part of this mapping.
+        this.outgoing = new Map();
+        this.isDeleted = false; // has the node been deleted?
+        this.creation = creation;
+    }
+    isNode(uuid) {
+        return this.creation.id == uuid;
+    }
+    isValue(value) {
+        return false;
+    }
+    asTarget() {
+        return this.creation;
+    }
+    createLinkTo(sourceId, label, listener) {
+        listener.createLinkToNode(sourceId, label, this.creation.id);
+    }
+    getReads(label) {
+        return this.outgoingReads.get(label) || [];
+    }
+    // Has no side effects - instead returns the deltas that capture the creation or update of the given outgoing edge
+    getDeltaForSetEdge(registry, label, target, reads = []) {
+        const previousEdgeUpdate = this.outgoingDeltas.get(label);
+        if (previousEdgeUpdate === undefined) {
+            return registry.newEdgeUpdate(this.creation.createOutgoingEdge(label), target, reads, []);
+        }
+        else {
+            return registry.newEdgeUpdate(previousEdgeUpdate.overwrite(), target, reads, this.getReads(label).slice());
+        }
+    }
+    // Has no side effects - instead returns the deltas that capture the deletion of this node (and its incoming+outgoing edges)
+    getDeltasForDelete(registry) {
+        // set all incoming edges to 'null' (if they aren't already):
+        const [afterTgt, newDeltas] = this.getIncomingEdgeDependenciesForDelete(registry);
+        console.log({ afterTgt });
+        // set all outgoing edges to 'null':
+        const afterSrc = [...this.outgoingDeltas.values()].map(u => {
+            return registry.newEdgeUpdate(u.overwrite(), null, [], this.getReads(u.overwrites.label).slice());
+        });
+        // console.log("deleting", this.creation.id, {afterTgt, afterSrc});
+        const nodeDeletion = registry.newNodeDeletion(this.creation, afterSrc, afterTgt);
+        return [...newDeltas, ...afterSrc, nodeDeletion];
+    }
+}
+class ValueState extends Common {
+    constructor(value) {
+        super();
+        this.type = "value";
+        this.shown = false; // does a (visible) node currently exist for this value?
+        this.value = value;
+    }
+    isNode(uuid) {
+        return false;
+    }
+    isValue(value) {
+        return this.value === value;
+    }
+    asTarget() {
+        return this.value;
+    }
+    createLinkTo(sourceId, label, listener) {
+        listener.createLinkToValue(sourceId, label, this.value);
+    }
+    addIncoming(label, delta, srcState, listener) {
+        super.addIncoming(label, delta, srcState, listener);
+        this._showOrHide(listener);
+    }
+    noLongerIncoming(overwritten, overwriter, listener) {
+        super.noLongerIncoming(overwritten, overwriter, listener);
+        this._showOrHide(listener);
+    }
+    // only called when undoing a Delta
+    unAddIncoming(delta, listener) {
+        super.unAddIncoming(delta, listener);
+        this._showOrHide(listener);
+    }
+    // only called when undoing a Delta
+    incomingAgain(label, unOverwritten, srcState, unOverwriter, listener) {
+        super.incomingAgain(label, unOverwritten, srcState, unOverwriter, listener);
+        this._showOrHide(listener);
+    }
+    // Value nodes are "always already there", but they are only rendered when they are currently the target of an edge. This function determines whether a value node should be rendered, and creates/deletes the corresponding node using the 'listener'.
+    _showOrHide(listener) {
+        const willShow = this.currentlyIncoming.length > 0;
+        if (!this.shown && willShow) {
+            listener.createValue(this);
+        }
+        else if (this.shown && !willShow) {
+            listener.deleteValue(this.value);
+        }
+        this.shown = willShow;
+    }
+    getDeltaForSetEdge(registry, label, target, reads = []) {
+        // A value cannot be the source of an edge, so we return no deltas.
+        throw new Error("Assertion failed: A value cannot be the source of an edge.");
+    }
+    getDeltasForDelete(registry) {
+        const [edgeUnsettings, _] = this.getIncomingEdgeDependenciesForDelete(registry);
+        return edgeUnsettings;
+    }
+}
+// Executes (primitive) deltas, and updates the graph state accordingly.
+// External representations (e.g., d3) can be kept in sync through GraphStateListener.
+class GraphState {
+    constructor() {
+        this.nodes = new Map();
+        this.values = new Map();
+        this.deltasSinceCheckpoint = [];
+        // Deltas that are part of current state
+        this.deltas = new Set();
+    }
+    // Stores a snapshot of the current graph state. Kind of like Git stash.
+    pushState() {
+        this.deltasSinceCheckpoint.push([]);
+    }
+    // Restores a previously stored graph state snapshot
+    popState() {
+        const toUnexec = this.deltasSinceCheckpoint.at(-1);
+        if (toUnexec === undefined) {
+            throw new Error("GraphState: cannot popState(), must pushState() first");
+        }
+        const result = toUnexec.slice(); // clone this array
+        toUnexec.reduceRight((_, d) => { this.unexec(d); return null; }, null);
+        this.deltasSinceCheckpoint.pop();
+        return result;
+    }
+    exec(delta, listener = DUMMY) {
+        var _a;
+        if (delta instanceof delta_2.NodeCreation) {
+            this.execNodeCreation(delta, listener);
+        }
+        else if (delta instanceof delta_2.NodeDeletion) {
+            this.execNodeDeletion(delta, listener);
+        }
+        else if (delta instanceof delta_2.EdgeUpdate) {
+            this.execEdgeUpdate(delta, listener);
+        }
+        else if (delta instanceof delta_1.Transaction) {
+            delta.deltas.forEach(d => this.exec(d, listener));
+        }
+        else {
+            throw new Error("Assertion failed: Unexpected delta type");
+        }
+        (_a = this.deltasSinceCheckpoint.at(-1)) === null || _a === void 0 ? void 0 : _a.push(delta);
+        this.deltas.add(delta);
+    }
+    unexec(delta, listener = DUMMY) {
+        this.deltas.delete(delta);
+        if (delta instanceof delta_2.NodeCreation) {
+            this.unexecNodeCreation(delta, listener);
+        }
+        else if (delta instanceof delta_2.NodeDeletion) {
+            this.unexecNodeDeletion(delta, listener);
+        }
+        else if (delta instanceof delta_2.EdgeUpdate) {
+            this.unexecEdgeUpdate(delta, listener);
+        }
+        else if (delta instanceof delta_1.Transaction) {
+            delta.deltas.reduceRight((_, d) => { this.unexec(d, listener); return null; }, null);
+        }
+        else {
+            throw new Error("Assertion failed: Unexpected delta type");
+        }
+        const checkpoint = this.deltasSinceCheckpoint.at(-1);
+        if (checkpoint !== undefined) {
+            const lastDelta = checkpoint.pop();
+            if (lastDelta !== delta) {
+                throw new Error("GraphState: asymmetrical call to unexec");
+            }
+        }
+    }
+    _getEdgeTargetState(target) {
+        if (target instanceof delta_2.TargetNode) {
+            return this.nodes.get(target.value.id); // may return undefined
+        }
+        else if (target instanceof delta_2.TargetValue && target.value !== null) {
+            return this.getValueState(target.value);
+        }
+    }
+    getValueState(value) {
+        let vs = this.values.get(value);
+        if (vs === undefined) {
+            vs = new ValueState(value);
+            this.values.set(value, vs);
+        }
+        return vs;
+    }
+    execNodeCreation(delta, listener) {
+        // console.log("execNodeCreation", delta)
+        const nodeState = new NodeState(delta);
+        this.nodes.set(delta.id, nodeState);
+        listener.createNode(nodeState);
+    }
+    unexecNodeCreation(delta, listener) {
+        // console.log("unexecNodeCreation", delta)
+        this.nodes.delete(delta.id);
+        listener.deleteNode(delta.id);
+    }
+    execNodeDeletion(delta, listener) {
+        // console.log("execNodeDeletion", delta)
+        const id = delta.node.id;
+        const nodeState = this.nodes.get(id);
+        if (nodeState === undefined) {
+            throw new Error("Assertion failed: deleted node does not exist");
+        }
+        nodeState.isDeleted = true;
+        listener.deleteNode(id);
+    }
+    unexecNodeDeletion(delta, listener) {
+        // restore outgoing links
+        const id = delta.node.id;
+        const nodeState = this.nodes.get(id);
+        if (nodeState === undefined) {
+            throw new Error("Assertion failed: deleted node does not exist");
+        }
+        nodeState.isDeleted = false;
+        listener.createNode(nodeState);
+    }
+    execEdgeUpdate(delta, listener) {
+        const edge = delta.overwrites;
+        const label = edge.label;
+        const sourceId = edge.source.id;
+        const sourceState = this.nodes.get(sourceId);
+        if (sourceState === undefined) {
+            throw new Error(`Assertion failed: Must have sourceState -> cannot execute EdgeUpdate. (${delta.description})`);
+        }
+        // Remove edge from old target
+        if (edge instanceof delta_2.NewEdge) {
+            // Nothing was overwritten
+        }
+        else if (edge instanceof delta_2.ExistingEdge) {
+            const overwrittenUpdate = edge.delta;
+            const oldTarget = overwrittenUpdate.target;
+            const oldTargetState = this._getEdgeTargetState(oldTarget);
+            if (oldTargetState === undefined) {
+                if (oldTarget.value !== null) {
+                    console.log("TODO: Check: Possibly an assertion error here.");
+                }
+            }
+            else {
+                oldTargetState.noLongerIncoming(overwrittenUpdate, delta, listener);
+                listener.deleteLink(sourceId, label);
+            }
+        }
+        // Add edge to new target
+        const newTarget = delta.target;
+        const newTargetState = this._getEdgeTargetState(newTarget);
+        if (newTargetState === undefined) {
+            if (newTarget.value !== null) {
+                console.log("TODO: Check: Possibly an assertion error here.");
+            }
+            sourceState.outgoing.delete(label);
+        }
+        else {
+            newTargetState.addIncoming(label, delta, sourceState, listener);
+            newTargetState.createLinkTo(sourceId, label, listener);
+            sourceState.outgoing.set(label, newTargetState);
+        }
+        sourceState.outgoingDeltas.set(label, delta);
+        // Add reads
+        for (const r of delta.reads) {
+            const readsNode = this.nodes.get(r.source.id);
+            const reads = readsNode.outgoingReads.get(r.label) || (() => {
+                const newReads = [];
+                readsNode.outgoingReads.set(r.label, newReads);
+                return newReads;
+            })();
+            reads.push(delta);
+        }
+    }
+    unexecEdgeUpdate(delta, listener) {
+        const edge = delta.overwrites;
+        const label = edge.label;
+        const sourceId = edge.source.id;
+        const sourceState = this.nodes.get(sourceId);
+        if (sourceState === undefined) {
+            throw new Error("Assertion failed: Must have sourceState -> cannot un-execute EdgeUpdate.");
+        }
+        // Remove reads
+        for (const r of delta.reads) {
+            const readsNode = this.nodes.get(r.source.id);
+            const reads = readsNode.outgoingReads.get(r.label) || (() => {
+                const newReads = [];
+                readsNode.outgoingReads.set(r.label, newReads);
+                return newReads;
+            })();
+            removeFromArray(reads, elem => elem === delta);
+        }
+        // Remove edge from new target
+        const newTarget = delta.target;
+        const newTargetState = this._getEdgeTargetState(newTarget);
+        if (newTargetState === undefined) {
+            if (newTarget.value !== null) {
+                console.log("TODO: Check: Possibly an assertion error here.");
+            }
+        }
+        else {
+            newTargetState.unAddIncoming(delta, listener);
+            listener.deleteLink(sourceId, label);
+        }
+        // Add edge to old target
+        if (edge instanceof delta_2.NewEdge) {
+            // Nothing was overwritten
+            sourceState.outgoingDeltas.delete(label);
+            sourceState.outgoing.delete(label);
+        }
+        else if (edge instanceof delta_2.ExistingEdge) {
+            const overwrittenUpdate = edge.delta;
+            const oldTarget = overwrittenUpdate.target;
+            const oldTargetState = this._getEdgeTargetState(oldTarget);
+            if (oldTargetState === undefined) {
+                if (oldTarget.value !== null) {
+                    console.log("TODO: Check: Possibly an assertion error here.");
+                }
+                sourceState.outgoing.delete(label);
+            }
+            else {
+                oldTargetState.incomingAgain(label, overwrittenUpdate, sourceState, delta, listener);
+                oldTargetState.createLinkTo(sourceId, label, listener);
+                sourceState.outgoing.set(label, oldTargetState);
+            }
+            sourceState.outgoingDeltas.set(label, overwrittenUpdate);
+        }
+    }
+}
+exports.GraphState = GraphState;
+//# sourceMappingURL=graph_state.js.map

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 1 - 0
dist/lib/graph_state.js.map


+ 1 - 0
dist/lib/graph_state.test.d.ts

@@ -0,0 +1 @@
+export {};

+ 78 - 0
dist/lib/graph_state.test.js

@@ -0,0 +1,78 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+const graph_state_1 = require("./graph_state");
+const test_helpers_1 = require("./util/test_helpers");
+const delta_registry_1 = require("./delta_registry");
+const delta_1 = require("./delta");
+const assert_1 = require("./util/assert");
+class MyGraphStateListener {
+    createNode(ns) {
+        console.log("created node", ns);
+    }
+    createValue(vs) {
+        console.log("created value", vs);
+    }
+    deleteNode(id) {
+        console.log("deleted node", id);
+    }
+    deleteValue(value) {
+        console.log("deleted value", value);
+    }
+    createLinkToNode(sourceId, label, targetId) {
+        console.log("created link to node", sourceId, label, targetId);
+    }
+    createLinkToValue(sourceId, label, targetValue) {
+        console.log("created link to value", sourceId, label, targetValue);
+    }
+    deleteLink(sourceId, label) {
+        console.log("deleted link", sourceId, label);
+    }
+}
+describe("GraphState", () => {
+    // need to test more scenarios!
+    // taken from primitive delta test:
+    it("Delete node with self-edge", () => {
+        const graphState = new graph_state_1.GraphState();
+        const registry = new delta_registry_1.DeltaRegistry();
+        const getId = (0, test_helpers_1.mockUuid)();
+        function newTransaction(deltas, description) {
+            return registry.newTransaction(deltas, description, (0, delta_1.findTxDependencies)(deltas, graphState.deltas));
+        }
+        const myListener = new MyGraphStateListener();
+        // Better not to create these primitive deltas by hand (is hard!). Instead, use 'getDeltasForSetEdge' and 'getDeltasForDelete' (see below).
+        // const edgeCreation = registry.newEdgeUpdate(nodeCreation.createOutgoingEdge("label"), nodeCreation);
+        // const edgeUpdate = registry.newEdgeUpdate(edgeCreation, null);
+        // const nodeDeletion = registry.newNodeDeletion(nodeCreation, [edgeUpdate], [edgeUpdate]);
+        (0, assert_1.assert)(graphState.nodes.size === 0, "Expected no nodes initially");
+        const nodeId = getId();
+        const nodeCreation = registry.newNodeCreation(nodeId);
+        const comp0 = newTransaction([nodeCreation], "node creation");
+        graphState.exec(comp0, myListener);
+        (0, assert_1.assert)(graphState.nodes.size === 1, "Expected one node after NodeCreation");
+        const nodeState = graphState.nodes.get(nodeId);
+        const deltaForSetEdge = nodeState.getDeltaForSetEdge(registry, "x", 42);
+        const comp1 = newTransaction([deltaForSetEdge], "set edge x == 42");
+        graphState.exec(comp1, myListener);
+        (0, assert_1.assert)(graphState.nodes.get(nodeId).outgoingDeltas.get("x") !== undefined, "Expected outgoing edge 'x' for node " + nodeId);
+        (0, assert_1.assert)(graphState.nodes.get(nodeId).outgoing.get("x").value === 42, "Expected value of outgoing edge 'x' to be 42");
+        const deltasForDelete = nodeState.getDeltasForDelete(registry);
+        console.log({ deltasForDelete });
+        const comp2 = newTransaction(deltasForDelete, "delete node (and edge)");
+        graphState.exec(comp2, myListener);
+        console.log(nodeState.outgoing.get("x"));
+        // assert(nodeState!.outgoing.get("x") === null, "Exepected no outgoing edge 'x'");
+        graphState.unexec(comp2, myListener);
+        // assert((graphState.nodes.get(nodeId)!.outgoing.get("x") as IValueState).value === 42, "Expected value of outgoing edge 'x' to be 42");
+        // assert(graphState.nodes.size === 0, "Expected no more nodes in graphState");
+        // // graphState.exec(edgeCreation);
+        // assert(graphState.nodes.size === 1, "Expected one node after EdgeCreation");
+        // // assert(graphState.edges.length === 1, "Expected one edge after EdgeCreation");
+        // graphState.exec(edgeUpdate);
+        // assert(graphState.nodes.size === 1, "Expected one node after EdgeUpdate");
+        // // assert(graphState.edges.length === 0, "Expected no edges after EdgeUpdate");
+        // graphState.exec(nodeDeletion);
+        // assert(graphState.nodes.size === 0, "Expected no nodes after NodeDeletion");
+        // // assert(graphState.edges.length === 0, "Expected no edges after NodeDeletion");
+    });
+});
+//# sourceMappingURL=graph_state.test.js.map

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 1 - 0
dist/lib/graph_state.test.js.map


+ 3 - 0
dist/lib/mock_node_util.d.ts

@@ -0,0 +1,3 @@
+export declare const inspect: {
+    custom: symbol;
+};

+ 10 - 0
dist/lib/mock_node_util.js

@@ -0,0 +1,10 @@
+"use strict";
+// When building the 'onion' library for browser, the 'util' NodeJS library will not be found.
+// A real polyfill would be overkill, so we just provide this 'mock'.
+// The polyfill configuration is in 'webpack.config.js'.
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.inspect = void 0;
+exports.inspect = {
+    custom: Symbol(),
+};
+//# sourceMappingURL=mock_node_util.js.map

+ 1 - 0
dist/lib/mock_node_util.js.map

@@ -0,0 +1 @@
+{"version":3,"file":"mock_node_util.js","sourceRoot":"","sources":["../../lib/mock_node_util.ts"],"names":[],"mappings":";AAAA,8FAA8F;AAC9F,qEAAqE;AACrE,wDAAwD;;;AAE3C,QAAA,OAAO,GAAG;IACrB,MAAM,EAAE,MAAM,EAAE;CACjB,CAAC"}

+ 2 - 0
dist/lib/types.d.ts

@@ -0,0 +1,2 @@
+export type PrimitiveValue = string | number | boolean | null;
+export type UUID = string | number | boolean;

+ 14 - 0
dist/lib/types.js

@@ -0,0 +1,14 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+// // This class is here to distinguish UUIDs from ordinary strings
+// export class UUID {
+//   value: PrimitiveValue;
+//   constructor(value: PrimitiveValue) {
+//     this.value = value;
+//   }
+//   // pretty print to console under NodeJS
+//   [inspect.custom](depth: number, options: object) {
+//     return "UUID{" + inspect(this.value, options) + "}"
+//   }
+// }
+//# sourceMappingURL=types.js.map

+ 1 - 0
dist/lib/types.js.map

@@ -0,0 +1 @@
+{"version":3,"file":"types.js","sourceRoot":"","sources":["../../lib/types.ts"],"names":[],"mappings":";;AAMA,mEAAmE;AACnE,sBAAsB;AACtB,2BAA2B;AAE3B,yCAAyC;AACzC,0BAA0B;AAC1B,MAAM;AAEN,4CAA4C;AAC5C,uDAAuD;AACvD,0DAA0D;AAC1D,MAAM;AACN,IAAI"}

+ 3 - 0
dist/lib/util/assert.d.ts

@@ -0,0 +1,3 @@
+export declare function assertNever(x: never): never;
+export declare function assert(expression: boolean, msg: string): void;
+export declare function assertThrows(callback: any, msg: any): void;

+ 28 - 0
dist/lib/util/assert.js

@@ -0,0 +1,28 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.assertThrows = exports.assert = exports.assertNever = void 0;
+// this function can be used to ensure there are no missing cases in the handling of a union type
+function assertNever(x) {
+    throw new Error(`Unexpected object: ${x}`);
+}
+exports.assertNever = assertNever;
+function assert(expression, msg) {
+    if (!expression) {
+        throw new Error(msg);
+    }
+}
+exports.assert = assert;
+function assertThrows(callback, msg) {
+    let threw = false;
+    try {
+        callback();
+    }
+    catch (e) {
+        threw = true;
+    }
+    if (!threw) {
+        throw new Error(msg);
+    }
+}
+exports.assertThrows = assertThrows;
+//# sourceMappingURL=assert.js.map

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 1 - 0
dist/lib/util/assert.js.map


+ 4 - 0
dist/lib/util/buffer_xor.d.ts

@@ -0,0 +1,4 @@
+/// <reference types="node" />
+import { Buffer } from "buffer";
+export declare function bufferXOR(a: Buffer, b: Buffer): Buffer;
+export declare function buffersXOR(...args: Buffer[]): Buffer;

+ 21 - 0
dist/lib/util/buffer_xor.js

@@ -0,0 +1,21 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.buffersXOR = exports.bufferXOR = void 0;
+const buffer_1 = require("buffer");
+// Often in the code, when we want to calculate a content-based ID, where the content is an unordered set, we compute the XOR of the content-based IDs of the set elements. This is because XOR is insensitive to order.
+// Precondition that is NOT CHECKED: buffers must be of equal length
+// Returns new buffer that is bitwise XOR of inputs.
+function bufferXOR(a, b) {
+    const result = buffer_1.Buffer.allocUnsafe(a.length);
+    for (let i = 0; i < a.length; i += 4) {
+        // Little endian is fastest, because native to Intel CPUs
+        result.writeInt32LE(a.readInt32LE(i) ^ b.readInt32LE(i), i);
+    }
+    return result;
+}
+exports.bufferXOR = bufferXOR;
+function buffersXOR(...args) {
+    return args.reduce((a, b) => bufferXOR(a, b), buffer_1.Buffer.alloc(32));
+}
+exports.buffersXOR = buffersXOR;
+//# sourceMappingURL=buffer_xor.js.map

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 1 - 0
dist/lib/util/buffer_xor.js.map


+ 1 - 0
dist/lib/util/dfs.d.ts

@@ -0,0 +1 @@
+export declare function findDFS<T, L>(start: T, searchFor: T, getNeighbors: (T: any) => Array<[L, T]>): L[] | undefined;

+ 36 - 0
dist/lib/util/dfs.js

@@ -0,0 +1,36 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.findDFS = void 0;
+// Generic depth-first search.
+// getNeighbors should return an array of 'outgoing links', where each link is a pair [label, neighboring_element]
+// neighbors are visited in the order that getNeighbors returns them, so getNeighbors can be optimized (e.g., via some heuristic) to minimize the number of visits.
+// If the element 'searchFor' was reachable from the 'start', returns a PATH, i.e., an array of link-labels to get from start to 'searchFor'.
+// If not reachable, returns undefined.
+function findDFS(start, searchFor, getNeighbors) {
+    const alreadyVisited = new Set([start]); // only visit every 'thing' once
+    let recursiveInvocations = 0;
+    function findDFSRecursive(current, currentPath) {
+        recursiveInvocations++;
+        if (current === searchFor) {
+            return currentPath;
+        }
+        const neighbors = getNeighbors(current);
+        // console.log("current", current, "neighbors:", neighbors)
+        for (const [link, neighbor] of neighbors) {
+            if (alreadyVisited.has(neighbor)) {
+                continue;
+            }
+            alreadyVisited.add(neighbor);
+            const recursiveResult = findDFSRecursive(neighbor, [...currentPath, link]);
+            if (recursiveResult !== undefined) {
+                return recursiveResult;
+            }
+            // continue with next neighbor...
+        }
+    }
+    const result = findDFSRecursive(start, []);
+    // console.log("findDFS recursiveInvocations (performance metric, less is better):", recursiveInvocations);
+    return result;
+}
+exports.findDFS = findDFS;
+//# sourceMappingURL=dfs.js.map

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 1 - 0
dist/lib/util/dfs.js.map


+ 1 - 0
dist/lib/util/dfs.test.d.ts

@@ -0,0 +1 @@
+export {};

+ 38 - 0
dist/lib/util/dfs.test.js

@@ -0,0 +1,38 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+const dfs_1 = require("./dfs");
+const assert_1 = require("./assert");
+const _ = require("lodash");
+const graph = new Map([
+    [0, new Map([['a', 1], ['b', 2]])],
+    [1, new Map([['c', 0]])],
+    [2, new Map([['d', 3]])],
+]);
+function getNeighbors(node) {
+    const outgoing = graph.get(node);
+    if (outgoing === undefined) {
+        return [];
+    }
+    return [...outgoing.entries()];
+}
+describe("DFS", () => {
+    it("Find node one hop", () => {
+        const path = (0, dfs_1.findDFS)(0, 1, getNeighbors);
+        (0, assert_1.assert)(_.isEqual(path, ['a']), "Expected node to be found.");
+    });
+    it("Find node two hops", () => {
+        const path = (0, dfs_1.findDFS)(0, 3, getNeighbors);
+        // There is more than one path from 0 to 3 (in fact, there are infinitely many, if we allow loops),
+        // but we don't allow visiting the same element more than once, so only one path is possible:
+        (0, assert_1.assert)(_.isEqual(path, ['b', 'd']), "Expected node to be found.");
+    });
+    it("Find node zero hops", () => {
+        const path = (0, dfs_1.findDFS)(0, 0, getNeighbors);
+        (0, assert_1.assert)(_.isEqual(path, []), "Expected node to be found.");
+    });
+    it("Find unreachable node", () => {
+        const path = (0, dfs_1.findDFS)(3, 0, getNeighbors);
+        (0, assert_1.assert)(path === undefined, "Expected node to be unreachable.");
+    });
+});
+//# sourceMappingURL=dfs.test.js.map

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 1 - 0
dist/lib/util/dfs.test.js.map


+ 1 - 0
dist/lib/util/permutations.d.ts

@@ -0,0 +1 @@
+export declare function permutations<T>(arr: Array<T>, m?: Array<T>): any;

+ 18 - 0
dist/lib/util/permutations.js

@@ -0,0 +1,18 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.permutations = void 0;
+function* permutations(arr, m = []) {
+    if (arr.length === 0) {
+        // @ts-ignore:
+        yield m;
+    }
+    else {
+        for (let i = 0; i < arr.length; i++) {
+            let curr = arr.slice();
+            let next = curr.splice(i, 1);
+            yield* permutations(curr.slice(), m.concat(next));
+        }
+    }
+}
+exports.permutations = permutations;
+//# sourceMappingURL=permutations.js.map

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 1 - 0
dist/lib/util/permutations.js.map


+ 1 - 0
dist/lib/util/test_helpers.d.ts

@@ -0,0 +1 @@
+export declare function mockUuid(): () => number;

+ 11 - 0
dist/lib/util/test_helpers.js

@@ -0,0 +1,11 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.mockUuid = void 0;
+function mockUuid() {
+    let nextId = 0;
+    return function () {
+        return nextId++;
+    };
+}
+exports.mockUuid = mockUuid;
+//# sourceMappingURL=test_helpers.js.map

+ 1 - 0
dist/lib/util/test_helpers.js.map

@@ -0,0 +1 @@
+{"version":3,"file":"test_helpers.js","sourceRoot":"","sources":["../../../lib/util/test_helpers.ts"],"names":[],"mappings":";;;AACA,SAAgB,QAAQ;IACtB,IAAI,MAAM,GAAG,CAAC,CAAC;IACf,OAAO;QACL,OAAO,MAAM,EAAE,CAAC;IAClB,CAAC,CAAA;AACH,CAAC;AALD,4BAKC"}

+ 58 - 0
dist/lib/version.d.ts

@@ -0,0 +1,58 @@
+/// <reference types="node" />
+/// <reference types="node" />
+import { inspect } from "util";
+import { Buffer } from "buffer";
+import { PrimitiveDelta } from "./delta";
+import { Delta } from "./delta";
+type DiffType = Delta;
+type OverridingsMap = Map<Delta, Delta>;
+export interface Embedding {
+    version: Version;
+    overridings: OverridingsMap;
+}
+export type Embeddings = Map<string, Embedding>;
+export type ParentLink = [Version, DiffType];
+export type ChildLink = [Version, DiffType];
+type PathLink = [('p' | 'c'), DiffType, Version];
+export declare class Version {
+    readonly parents: Array<ParentLink>;
+    readonly children: Array<ChildLink>;
+    readonly embeddings: Embeddings;
+    readonly reverseEmbeddings: Map<string, Version[]>;
+    readonly hash: Buffer;
+    readonly size: number;
+    constructor(parents: Array<ParentLink>, hash: Buffer, size: number, embeddings: (Version: any) => Embeddings);
+    implicitSelfEmbedding: Embedding;
+    [Symbol.iterator](): Iterator<DiffType>;
+    iterPrimitiveDeltas(): Iterable<PrimitiveDelta>;
+    [inspect.custom](depth: number, options: object): string;
+    isSubSetOf(otherVersion: Version): boolean;
+    contains(delta: Delta): boolean;
+    containsPrimitive(delta: PrimitiveDelta): boolean;
+    findPathTo(otherVersion: Version): Array<PathLink> | undefined;
+    findDescendant(otherVersion: Version): Array<PathLink> | undefined;
+    getEmbedding(key: string): Embedding;
+    getReverseEmbeddings(key: string): Version[];
+    serialize(alreadyHave?: Set<Version>): {
+        externalDependencies: string[];
+        deltas: never[];
+        versions: never[];
+    };
+    private serializeInternal;
+}
+export declare class VersionRegistry {
+    readonly initialVersion: Version;
+    readonly versionMap: Map<string, Version>;
+    lookupOptional(hash: Buffer): Version | undefined;
+    lookup(hash: Buffer): Version;
+    private putVersion;
+    createVersion(parent: Version, delta: DiffType, embeddings?: (Version: any) => Embeddings): Version;
+    createVersionUnsafe(parent: Version, delta: DiffType, embeddings?: (Version: any) => Embeddings): Version;
+    quickVersion(deltas: Array<DiffType>): Version;
+    getIntersection(versions: Array<Version>): Version;
+    private getLCAInternal;
+    getLCA(versions: Array<Version>): Version;
+    merge(versions: Array<Version>, debugNames?: (Delta: any) => string): Array<Version>;
+    crazyMerge(versions: Array<Version>, debugNames?: (Delta: any) => string): Array<Version>;
+}
+export {};

+ 499 - 0
dist/lib/version.js

@@ -0,0 +1,499 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.VersionRegistry = exports.Version = void 0;
+const util_1 = require("util"); // NodeJS library
+const buffer_1 = require("buffer"); // NodeJS library
+const dfs_1 = require("./util/dfs");
+const permutations_1 = require("./util/permutations");
+const buffer_xor_1 = require("./util/buffer_xor");
+// not exported -> use VersionRegistry to create versions
+class Version {
+    // DO NOT USE constructor directly - instead use VersionRegistry.createVersion.
+    constructor(parents, hash, size, embeddings) {
+        this.children = []; // reverse parents
+        this.reverseEmbeddings = new Map(); // (host) versions in which this (guest) version is embedded.
+        this.implicitSelfEmbedding = {
+            version: this,
+            overridings: new Map(), // no overrides
+        };
+        this.parents = parents;
+        this.hash = hash;
+        this.size = size;
+        this.embeddings = embeddings(this);
+    }
+    // Returns iterator that yields all deltas of this version, from recent to early.
+    // Or put more precisely: a delta's dependencies will be yielded AFTER the delta itself.
+    *[Symbol.iterator]() {
+        let current = this;
+        while (current.parents.length !== 0) {
+            // There may be multiple parents due to commutativity (multiple orders of deltas that yield the same version), but it doesn't matter which one we pick: all paths will yield the same set of deltas.
+            const [parent, delta] = current.parents[0];
+            yield delta;
+            current = parent;
+        }
+    }
+    *iterPrimitiveDeltas() {
+        const executionOrder = [...this].reverse();
+        for (const d of executionOrder) {
+            yield* d.iterPrimitiveDeltas();
+        }
+    }
+    [util_1.inspect.custom](depth, options) {
+        return "Version{" + [...this].map(d => (0, util_1.inspect)(d, options)).join(",") + "}";
+    }
+    isSubSetOf(otherVersion) {
+        // current implementation is probably quite slow
+        for (const delta of this) {
+            if (!otherVersion.contains(delta)) {
+                return false;
+            }
+        }
+        return true;
+    }
+    contains(delta) {
+        for (const d of this) {
+            if (d === delta) {
+                return true;
+            }
+        }
+        return false;
+    }
+    containsPrimitive(delta) {
+        for (const d of this) {
+            for (const p of d.iterPrimitiveDeltas()) {
+                if (p === delta) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+    // Returns a sequence of Delta's to be undone/redone to go from this version to the 'otherVersion'
+    // Returned sequence is empty if otherVersion === this
+    // Returns undefined when there is no path (which is impossible if both versions are part of the same VersionRegistry).
+    // TODO: implement Breadth-First Search (BFS) for better performance.
+    findPathTo(otherVersion) {
+        const getNeighbors = (v) => {
+            const parentVersions = v.parents.map(([parentVersion, delta]) => [['p', delta, parentVersion], parentVersion]);
+            const childVersions = v.children.map(([childVersion, delta]) => [['c', delta, childVersion], childVersion]);
+            // heuristic: look in newer or older versions first?
+            if (v.size < otherVersion.size) {
+                // make the common case fast: most of the time,
+                // we just want to advance to the next version (i.e., after a user edit)
+                return [...childVersions, ...parentVersions];
+            }
+            else {
+                return [...parentVersions, ...childVersions];
+            }
+        };
+        // console.time("findDFS")
+        const result = (0, dfs_1.findDFS)(this, otherVersion, getNeighbors);
+        // console.timeEnd("findDFS");
+        // if (result)
+        //   console.log("findPath:", result.map(([linkType])=>linkType));
+        return result;
+    }
+    // Like findPathTo, but only searches 'down' (younger versions).
+    findDescendant(otherVersion) {
+        const getNeighbors = (v) => {
+            const result = v.children.map(([childVersion, delta]) => [['c', delta, childVersion], childVersion]);
+            return result;
+        };
+        return (0, dfs_1.findDFS)(this, otherVersion, getNeighbors);
+    }
+    getEmbedding(key) {
+        return this.embeddings.get(key) || this.implicitSelfEmbedding;
+    }
+    getReverseEmbeddings(key) {
+        return this.reverseEmbeddings.get(key) || [this];
+    }
+    // Serialize a path of Deltas from a Version in alreadyHave, to fully reconstruct this version.
+    serialize(alreadyHave = new Set()) {
+        const deltas = [];
+        const versions = [];
+        this.serializeInternal(new Set(alreadyHave), new Set(), deltas, versions);
+        return {
+            externalDependencies: [...alreadyHave].map(v => v.hash.toString('hex')),
+            deltas,
+            versions,
+        };
+    }
+    serializeInternal(alreadyHaveVersions, alreadyHaveDeltas, deltas, versions) {
+        if (alreadyHaveVersions.has(this)) {
+            return;
+        }
+        alreadyHaveVersions.add(this);
+        const embeddings = new Array();
+        for (const [guestId, { version, overridings }] of this.embeddings) {
+            version.serializeInternal(alreadyHaveVersions, alreadyHaveDeltas, deltas, versions);
+            const ovr = {};
+            for (const [key, val] of overridings.entries()) {
+                ovr[key.hash.toString('hex')] = val.hash.toString('hex');
+            }
+            embeddings.push({ guestId, v: version.hash.toString('hex'), ovr });
+        }
+        if (this.parents.length > 0) {
+            const [parentVersion, delta] = this.parents[0];
+            parentVersion.serializeInternal(alreadyHaveVersions, alreadyHaveDeltas, deltas, versions);
+            function visitDelta(delta) {
+                for (const d of delta.getDependencies()) {
+                    visitDelta(d);
+                }
+                if (!alreadyHaveDeltas.has(delta)) {
+                    deltas.push(delta.serialize());
+                    alreadyHaveDeltas.add(delta);
+                }
+            }
+            visitDelta(delta);
+            versions.push({
+                id: this.hash.toString('hex'),
+                parent: parentVersion.hash.toString('hex'),
+                delta: delta.hash.toString('hex'),
+                embeddings,
+            });
+        }
+    }
+}
+exports.Version = Version;
+const initialHash = buffer_1.Buffer.alloc(32); // all zeros
+function isConflicting(deltaA, deltaB) {
+    // for performance, iterate over the delta that has the least conflicts:
+    if (deltaA.conflictsWith.length < deltaB.conflictsWith.length) {
+        return deltaA.conflictsWith.some(([d]) => d === deltaB);
+    }
+    else {
+        return deltaB.conflictsWith.some(([d]) => d === deltaA);
+    }
+}
+class VersionRegistry {
+    constructor() {
+        this.initialVersion = new Version([], initialHash, 0, () => new Map());
+        // Maps version ID (as string, because a Buffer cannot be a map key) to Version
+        this.versionMap = new Map([
+            [initialHash.toString('hex'), this.initialVersion], // the initial version, always already there
+        ]);
+    }
+    lookupOptional(hash) {
+        return this.versionMap.get(hash.toString('hex'));
+    }
+    lookup(hash) {
+        const hex = hash.toString('hex');
+        const version = this.versionMap.get(hex);
+        if (version === undefined) {
+            throw new Error("no such version: " + hex);
+        }
+        return version;
+    }
+    putVersion(hash, version) {
+        this.versionMap.set(hash.toString('hex'), version);
+    }
+    // Idempotent
+    // Pre-condition 1: all of the dependencies of delta must exist in parent.
+    // Pre-condition 2: delta must be non-conflicting with any delta in parent.
+    // Pre-condition 3: if the to-be-created ("host") version embeds other ("guest") versions,
+    //   then all of the guest versions' deltas must exist in the host version, or be explicitly overridden.
+    createVersion(parent, delta, embeddings = () => new Map()) {
+        // Check pre-condition 1:
+        for (const [dep] of delta.getDependencies()) {
+            if (!parent.contains(dep)) {
+                throw new Error("createVersion: precondition failed: Missing dependency: " + dep.description);
+            }
+        }
+        // Check pre-condition 2:
+        for (const [conflictingDelta] of delta.conflictsWith) {
+            if (parent.contains(conflictingDelta)) {
+                throw new Error("createVersion: precondition failed: Delta " + delta.description + " conflicts with " + conflictingDelta.description);
+            }
+        }
+        const newVersion = this.createVersionUnsafe(parent, delta, embeddings);
+        // Check pre-condition 3:
+        const primitiveDeltas = [...delta.iterPrimitiveDeltas()];
+        for (const [guestId, { version: guest, overridings }] of newVersion.embeddings.entries()) {
+            const { version: guestParent } = parent.getEmbedding(guestId);
+            const guestDiff = guestParent.findDescendant(guest);
+            for (const [_, guestDelta] of guestDiff) {
+                for (const guestPDelta of guestDelta.iterPrimitiveDeltas()) {
+                    if (!primitiveDeltas.includes(overridings.get(guestPDelta) || guestPDelta)) {
+                        throw new Error("createVersion: precondition failed: Guest's primitive delta " + guestPDelta.description + " does not occur in host, nor is it overridden.");
+                    }
+                }
+            }
+        }
+        return newVersion;
+    }
+    // Faster than createVersion, but does not check pre-conditions.
+    // Idempotent
+    createVersionUnsafe(parent, delta, embeddings = () => new Map()) {
+        const newHash = (0, buffer_xor_1.bufferXOR)(parent.hash, delta.hash);
+        // TODO: include embeddings in hash digest.
+        const existingVersion = this.lookupOptional(newHash);
+        if (existingVersion !== undefined) {
+            // this Version already exists
+            const havePath = existingVersion.parents.some(([parentVersion, delta]) => parentVersion === parent);
+            if (!havePath) {
+                // but the path is new (there can be multiple 'paths' to the same version, because of commutation of deltas)
+                existingVersion.parents.push([parent, delta]);
+                parent.children.push([existingVersion, delta]);
+            }
+            for (const [guestId, { version, overridings }] of embeddings(existingVersion)) {
+                const found = existingVersion.embeddings.get(guestId);
+                if (!found) {
+                    existingVersion.embeddings.set(guestId, { version, overridings });
+                    // throw new Error("Assertion failed: created version already exists, but does not embed '" + guestId + "'");
+                }
+                else {
+                    const { version: v, overridings: o } = found;
+                    if (v !== version) {
+                        throw new Error("Assertion failed: created version already exists and embeds a differrent version '" + guestId + "'");
+                    }
+                    // Merge overridings:
+                    for (const [guestDelta, hostDelta] of overridings.entries()) {
+                        const alreadyHostDelta = o.get(guestDelta) || hostDelta;
+                        if (hostDelta !== alreadyHostDelta) {
+                            throw new Error("Assertion failed: created version already exists BUT overrides delta '" + guestDelta.description + "' with another delta.");
+                        }
+                        o.set(guestDelta, hostDelta);
+                    }
+                }
+            }
+            return existingVersion;
+        }
+        else {
+            const newVersion = new Version([[parent, delta]], newHash, parent.size + 1, embeddings);
+            // Create reverse parent links:
+            for (const [parent, delta] of newVersion.parents) {
+                parent.children.push([newVersion, delta]);
+            }
+            // Create reverse embedding:
+            for (const [key, embedding] of newVersion.embeddings.entries()) {
+                const reverse = embedding.version.reverseEmbeddings.get(key);
+                if (reverse !== undefined) {
+                    reverse.push(newVersion);
+                }
+                else {
+                    embedding.version.reverseEmbeddings.set(key, [newVersion]);
+                }
+            }
+            this.putVersion(newHash, newVersion);
+            return newVersion;
+        }
+    }
+    // Mostly used for testing purposes.
+    // Order of deltas should be recent -> early
+    // Or put more precisely: a delta's dependencies should occur AFTER the delta in the array.
+    quickVersion(deltas) {
+        return deltas.reduceRight((parentVersion, delta) => this.createVersion(parentVersion, delta), this.initialVersion);
+    }
+    // Get the version whose deltas are a subset of all given versions AKA the 'largest common ancestor'.
+    getIntersection(versions) {
+        // treat special case first:
+        if (versions.length === 0) {
+            return this.initialVersion;
+        }
+        // sort versions (out place) from few deltas to many (FASTEST):
+        const sortedVersions = versions.slice().sort((versionA, versionB) => versionA.size - versionB.size);
+        const intersection = [];
+        for (const delta of sortedVersions[0]) {
+            let allVersionsHaveIt = true;
+            for (let i = 1; i < sortedVersions.length; i++) {
+                let thisVersionHasIt = false;
+                for (const otherDelta of sortedVersions[i]) {
+                    if (delta === otherDelta) {
+                        thisVersionHasIt = true;
+                        break;
+                    }
+                }
+                if (!thisVersionHasIt) {
+                    allVersionsHaveIt = false;
+                    break;
+                }
+            }
+            if (allVersionsHaveIt) {
+                intersection.push(delta);
+            }
+        }
+        return this.quickVersion(intersection);
+    }
+    getLCAInternal(versionA, versionB) {
+        const ancestorsA = [versionA];
+        const ancestorsB = [versionB];
+        while (true) {
+            const a = ancestorsA[ancestorsA.length - 1];
+            // @ts-ignore: TypeScript doesn't know about 'findLast' method yet.
+            if (ancestorsB.findLast(v => v === a)) {
+                return a;
+            }
+            if (a.parents.length > 0) {
+                const [parent] = a.parents[0];
+                ancestorsA.push(parent);
+            }
+            const b = ancestorsB[ancestorsB.length - 1];
+            // @ts-ignore: TypeScript doesn't know about 'findLast' method yet.
+            if (ancestorsA.findLast(v => v === b)) {
+                return b;
+            }
+            if (b.parents.length > 0) {
+                const [parent] = b.parents[0];
+                ancestorsB.push(parent);
+            }
+        }
+    }
+    getLCA(versions) {
+        if (versions.length === 0) {
+            return this.initialVersion;
+        }
+        return versions.reduce((a, b) => this.getLCAInternal(a, b));
+    }
+    // Idempotent
+    // Of the union of all deltas of the versions given, compute the maximal left-closed conflict-free subsets.
+    // These are the subsets to which no delta can be added without introducing a conflict or missing dependency.
+    merge(versions, debugNames) {
+        function printDebug(...args) {
+            if (debugNames !== undefined) {
+                for (const [i, arg] of args.entries()) {
+                    try {
+                        const name = debugNames(arg);
+                        if (name !== undefined) {
+                            args[i] = name;
+                        }
+                    }
+                    catch (e) { }
+                }
+                console.log(...args);
+            }
+        }
+        const lca = this.getLCA(versions);
+        // ATTENTION: Constructing 'diff' must be made recursive (guest could also be a host)
+        function visitEmbeddings(currentHost, nextHost) {
+            const guestDeltaMap = new Map();
+            for (const [guestId, { version: nextGuest, overridings }] of nextHost.embeddings.entries()) {
+                const currentGuest = currentHost.getEmbedding(guestId).version;
+                const guestPath = currentGuest.findDescendant(nextGuest);
+                if (guestPath.length > 1) {
+                    throw new Error("Did not expect guestPath to be longer than one delta");
+                }
+                if (guestPath.length === 1) {
+                    const guestDelta = guestPath[0][1];
+                    if (currentHost === currentGuest && nextHost === nextGuest) {
+                        // prevent infinite recursion in case of self-embedding:
+                        guestDeltaMap.set(guestId, [[guestDelta, guestDeltaMap], overridings]);
+                    }
+                    else {
+                        const recursiveGuestDeltaMap = visitEmbeddings(currentGuest, nextGuest);
+                        guestDeltaMap.set(guestId, [[guestDelta, recursiveGuestDeltaMap], overridings]);
+                    }
+                }
+                else {
+                    guestDeltaMap.set(guestId, [null, new Map()]);
+                }
+            }
+            return guestDeltaMap;
+        }
+        let diff = [];
+        for (const v of versions) {
+            const path = lca.findDescendant(v);
+            // all deltas on path from lca to 'v':
+            let currentVersion = lca;
+            for (const [_, delta, nextVersion] of path) {
+                const guestDeltaMap = visitEmbeddings(currentVersion, nextVersion);
+                // The following condition may be wrong, check this later:
+                if (!diff.some(([d]) => d === delta)) {
+                    diff.push([delta, guestDeltaMap]);
+                }
+                currentVersion = nextVersion;
+            }
+        }
+        // Now we're ready to actually start merging...
+        const result = [];
+        // Recursively attempts to add deltas from 'deltasToTry' to 'startVersion'
+        // When nothing can be added anymore without introducing a conflict, we have a 'maximal version' (a result).
+        // It's possible that there is more than one 'maximal version'.
+        // Precondition: We assume that any single delta of 'deltasToTry' will not be conflicting with 'startVersion'.
+        const depthFirst = (startVersion, candidates, depth) => {
+            function printIndent(...args) {
+                printDebug("  ".repeat(depth), ...args);
+            }
+            // printIndent("deltasToTry=", ...candidates.map(([d])=>d));
+            let couldNotRecurse = true;
+            for (const [delta, guestDeltaMap] of candidates) {
+                const haveMissingDependency = delta.getDependencies().some(([dependency]) => !startVersion.contains(dependency));
+                if (haveMissingDependency) {
+                    printIndent("missing dependency, trying next delta");
+                    continue; // skip this delta, but keep it in deltasToTry (its missing dependency may be in deltasToTry)
+                }
+                // ATTENTION: Constructing embeddings must be made recursive (guest could also be a host)
+                // 'delta' can be added
+                // printIndent("including", delta, " => new version: ", delta, ...startVersion);
+                const createMergedVersionRecursive = (startVersion, [delta, guestDeltaMap]) => {
+                    const embeddings = new Map();
+                    const selfEmbeddingKeys = new Set();
+                    for (const [guestId, [guestDelta, overridings]] of guestDeltaMap.entries()) {
+                        const { version: guestStartVersion } = startVersion.getEmbedding(guestId);
+                        // console.log(guestId, "guestStartVersion: ", guestStartVersion);
+                        let nextGuestVersion;
+                        if (guestDelta === null) {
+                            nextGuestVersion = guestStartVersion;
+                        }
+                        else {
+                            if (guestStartVersion === startVersion && guestDelta[0] === delta && guestDelta[1] === guestDeltaMap) {
+                                selfEmbeddingKeys.add(guestId);
+                                continue;
+                            }
+                            else {
+                                nextGuestVersion = createMergedVersionRecursive(guestStartVersion, guestDelta);
+                            }
+                        }
+                        embeddings.set(guestId, { version: nextGuestVersion, overridings });
+                    }
+                    // printIndent("Creating version (", delta, ...[...startVersion].map(d => d), ") with embeddings", embeddings);
+                    const nextVersion = this.createVersion(startVersion, delta, newVersion => {
+                        // add self-embeddings:
+                        for (const guestId of selfEmbeddingKeys) {
+                            embeddings.set(guestId, { version: newVersion, overridings: new Map() });
+                        }
+                        return embeddings;
+                    });
+                    // printIndent("Created version (", ...[...nextVersion].map(d => d), ") with embeddings", embeddings);
+                    return nextVersion;
+                };
+                const nextVersion = createMergedVersionRecursive(startVersion, [delta, guestDeltaMap]);
+                // current delta does not have to be included again in next loop iterations
+                candidates = candidates.filter(([d]) => d !== delta);
+                // const [conflicting, nonConflicting] = _.partition(deltasToTry, ([d]) => isConflicting(d, delta));
+                const nonConflicting = candidates.filter(([candidate]) => !isConflicting(candidate, delta));
+                // if (conflicting.length > 0)
+                //   printIndent("will be skipped (conflicts with", delta, "):", ...conflicting);
+                depthFirst(nextVersion, nonConflicting, depth + 1);
+                couldNotRecurse = false;
+                if (nonConflicting.length === candidates.length) {
+                    // all deltas from deltasToTry were included -> no need to try alternatives
+                    break;
+                }
+            }
+            if (couldNotRecurse) {
+                // possibly have a new maximal version
+                if (!result.some(v => startVersion.isSubSetOf(v))) {
+                    // printIndent("new result");
+                    result.push(startVersion);
+                }
+            }
+        };
+        depthFirst(lca, diff, 0);
+        // printDebug("result of merge:", ..._.flatten(result.map(v => [...v, ","])));
+        return result;
+    }
+    // Idempotent
+    // Calls the merge-function for every permutation of 'versions'
+    // This is useful for didactic purposes: a path is created from every input to at least one output.
+    // Of course it won't scale to many inputs.
+    crazyMerge(versions, debugNames) {
+        let result;
+        for (const v of (0, permutations_1.permutations)(versions)) {
+            // whatever the permutation, result will always be the same:
+            result = this.merge(v, debugNames);
+        }
+        return result;
+    }
+}
+exports.VersionRegistry = VersionRegistry;
+//# sourceMappingURL=version.js.map

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 1 - 0
dist/lib/version.js.map


+ 1 - 0
dist/lib/version.test.d.ts

@@ -0,0 +1 @@
+export {};

+ 286 - 0
dist/lib/version.test.js

@@ -0,0 +1,286 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+const _ = require("lodash");
+const version_1 = require("./version");
+const delta_registry_1 = require("./delta_registry");
+const test_helpers_1 = require("./util/test_helpers");
+const assert_1 = require("./util/assert");
+describe("Version", () => {
+    it("Get deltas", () => {
+        const deltaRegistry = new delta_registry_1.DeltaRegistry();
+        const getId = (0, test_helpers_1.mockUuid)();
+        const registry = new version_1.VersionRegistry();
+        const nodeCreation = deltaRegistry.newNodeCreation(getId());
+        const nodeDeletion = deltaRegistry.newNodeDeletion(nodeCreation, [], []);
+        const version1 = registry.createVersion(registry.initialVersion, nodeCreation);
+        const version2 = registry.createVersion(version1, nodeDeletion);
+        (0, assert_1.assert)(_.isEqual([...registry.initialVersion], []), "expected initialVersion to be empty");
+        (0, assert_1.assert)(_.isEqual([...version1], [nodeCreation]), "expected version1 to contain creation");
+        (0, assert_1.assert)(_.isEqual([...version2], [nodeDeletion, nodeCreation]), "expected version2 to contain creation and deletion");
+    });
+    it("Commutating operations yield equal versions", () => {
+        const deltaRegistry = new delta_registry_1.DeltaRegistry();
+        const getId = (0, test_helpers_1.mockUuid)();
+        const registry = new version_1.VersionRegistry();
+        const nodeCreationA = deltaRegistry.newNodeCreation(getId());
+        const nodeCreationB = deltaRegistry.newNodeCreation(getId());
+        const versionA = registry.createVersion(registry.initialVersion, nodeCreationA);
+        const versionAB = registry.createVersion(versionA, nodeCreationB);
+        const versionB = registry.createVersion(registry.initialVersion, nodeCreationB);
+        const versionBA = registry.createVersion(versionB, nodeCreationA);
+        (0, assert_1.assert)(versionAB === versionBA, "expected versions to be equal");
+    });
+    it("Intersection", () => {
+        const deltaRegistry = new delta_registry_1.DeltaRegistry();
+        const getId = (0, test_helpers_1.mockUuid)();
+        const registry = new version_1.VersionRegistry();
+        const A = deltaRegistry.newNodeCreation(getId());
+        const B = deltaRegistry.newNodeCreation(getId());
+        const C = deltaRegistry.newNodeCreation(getId());
+        const D = deltaRegistry.newNodeDeletion(A, [], []);
+        const v1 = registry.quickVersion([D, B, A]);
+        const v2 = registry.quickVersion([C, B]);
+        const intersection0 = registry.getIntersection([v1, v2]);
+        (0, assert_1.assert)(intersection0 === registry.quickVersion([B]), "expected intersection of v1 and v2 to be B.");
+        const intersection1 = registry.getIntersection([v1, v1]);
+        (0, assert_1.assert)(intersection1 === v1, "expected intersection of v1 with itself to be v1");
+        const intersection2 = registry.getIntersection([v1]);
+        (0, assert_1.assert)(intersection2 === v1, "expected intersection of v1 with itself to be v1");
+        const intersection3 = registry.getIntersection([]);
+        (0, assert_1.assert)(intersection3 === registry.initialVersion, "expected intersection of empty set to be initial (empty) version");
+    });
+    describe("Merging", () => {
+        // Helper
+        function mergeAgain(registry, merged, nameMap) {
+            const mergedAgain = registry.merge(merged, nameMap);
+            (0, assert_1.assert)(mergedAgain.length === merged.length
+                && mergedAgain.every(version => merged.includes(version)), "merging a merge result should just give the same result again.");
+        }
+        it("Merge empty set", () => {
+            const registry = new version_1.VersionRegistry();
+            const merged = registry.merge([]);
+            (0, assert_1.assert)(merged.length === 1 && merged[0] === registry.initialVersion, "expected intial version");
+            mergeAgain(registry, merged);
+        });
+        it("Merge non-conflicting versions", () => {
+            const deltaRegistry = new delta_registry_1.DeltaRegistry();
+            const getId = (0, test_helpers_1.mockUuid)();
+            const registry = new version_1.VersionRegistry();
+            const nodeCreationA = deltaRegistry.newNodeCreation(getId());
+            const nodeCreationB = deltaRegistry.newNodeCreation(getId());
+            const versionA = registry.createVersion(registry.initialVersion, nodeCreationA);
+            const versionB = registry.createVersion(registry.initialVersion, nodeCreationB);
+            const nameMap = new Map([[nodeCreationA, "A"], [nodeCreationB, "B"]]);
+            const merged = registry.merge([versionA, versionB], delta => nameMap.get(delta));
+            (0, assert_1.assert)(merged.length === 1, "expected 1 merged version");
+            const deltas = [...merged[0]];
+            (0, assert_1.assert)(deltas.length === 2
+                && deltas.includes(nodeCreationA)
+                && deltas.includes(nodeCreationB), "expected merged version to contain nodes A and B");
+            mergeAgain(registry, merged, delta => nameMap.get(delta));
+        });
+        it("Merge complex conflicting versions", () => {
+            const deltaRegistry = new delta_registry_1.DeltaRegistry();
+            const getId = (0, test_helpers_1.mockUuid)();
+            const registry = new version_1.VersionRegistry();
+            // the names of the deltas and the versions in this test trace back to an illustration in a Xournal++ file.
+            const X = deltaRegistry.newNodeCreation(getId());
+            const Y = deltaRegistry.newNodeCreation(getId());
+            const Z = deltaRegistry.newNodeCreation(getId());
+            const A = deltaRegistry.newNodeDeletion(X, [], []);
+            const B = deltaRegistry.newEdgeUpdate(X.createOutgoingEdge("label"), Y); // conflicts with A
+            (0, assert_1.assert)(_.isEqual(B.conflictsWith, [[A, 'U/D']]), "Expected B to conflict with A");
+            const C = deltaRegistry.newEdgeUpdate(Y.createOutgoingEdge("label"), Z);
+            const BB = deltaRegistry.newEdgeUpdate(B.overwrite(), null); // unset edge B.
+            const D = deltaRegistry.newNodeDeletion(Y, [], [BB]); // conflicts with C
+            (0, assert_1.assert)(_.isEqual(D.conflictsWith, [[C, 'U/D']]), "Expected D to conflict with C");
+            const nameMap = new Map([
+                [X, "X"],
+                [Y, "Y"],
+                [Z, "Z"],
+                [A, "A"],
+                [B, "B"],
+                [BB, "BB"],
+                [C, "C"],
+                [D, "D"],
+            ]);
+            const three = registry.quickVersion([A, X, Y, Z]);
+            const seven = registry.quickVersion([C, X, Y, Z]);
+            const five = registry.quickVersion([D, BB, B, X, Y, Z]);
+            const merged = registry.merge([three, seven, five], d => nameMap.get(d));
+            (0, assert_1.assert)(merged.length === 3, "expected three maximal versions");
+            (0, assert_1.assert)(merged.includes(registry.quickVersion([A, C, X, Y, Z])), "expected [X,Y,Z,A,C] to be a maximal version");
+            (0, assert_1.assert)(merged.includes(registry.quickVersion([BB, B, C, X, Y, Z])), "expected [X,Y,Z,B,C] to be a maximal version");
+            (0, assert_1.assert)(merged.includes(registry.quickVersion([D, BB, B, X, Y, Z])), "expected [X,Y,Z,B,D] to be a maximal version");
+            mergeAgain(registry, merged, d => nameMap.get(d));
+        });
+        it("Merge many non-conflicting versions (scalability test)", () => {
+            const deltaRegistry = new delta_registry_1.DeltaRegistry();
+            const getId = (0, test_helpers_1.mockUuid)();
+            const registry = new version_1.VersionRegistry();
+            // Bunch of non-conflicting deltas:
+            const deltas = [];
+            const nameMap = new Map();
+            for (let i = 0; i < 10; i++) {
+                const delta = deltaRegistry.newNodeCreation(getId());
+                deltas.push(delta);
+                nameMap.set(delta, i.toString());
+            }
+            // Create a version for each delta, containing only that delta:
+            const versions = deltas.map(d => registry.createVersion(registry.initialVersion, d));
+            const merged = registry.merge(versions, d => nameMap.get(d));
+            (0, assert_1.assert)(merged.length === 1, "only one merged version should result");
+            const mergedAgain = registry.merge(merged, d => nameMap.get(d));
+            (0, assert_1.assert)(mergedAgain.length === merged.length
+                && mergedAgain.every(version => merged.includes(version)), "merging a merge result should just give the same result again.");
+        });
+        it("Merge many conflicting versions (scalability test 2)", () => {
+            const deltaRegistry = new delta_registry_1.DeltaRegistry();
+            const getId = (0, test_helpers_1.mockUuid)();
+            const registry = new version_1.VersionRegistry();
+            const HOW_MANY = 3;
+            const creations = [];
+            const deletions = [];
+            const edges = [];
+            const versions = [];
+            const nameMap = new Map();
+            for (let i = 0; i < HOW_MANY; i++) {
+                const creation = deltaRegistry.newNodeCreation(getId());
+                const deletion = deltaRegistry.newNodeDeletion(creation, [], []);
+                const edge = deltaRegistry.newEdgeUpdate(creation.createOutgoingEdge("l"), creation); // conflicts with deletion0
+                creations.push(creation);
+                deletions.push(deletion);
+                edges.push(edge);
+                nameMap.set(creation, "C" + i.toString());
+                nameMap.set(deletion, "D" + i.toString());
+                nameMap.set(edge, "E" + i.toString());
+                versions.push(registry.quickVersion([deletion, creation]));
+                versions.push(registry.quickVersion([edge, creation]));
+            }
+            const merged = registry.merge(versions, d => nameMap.get(d));
+            (0, assert_1.assert)(merged.length === Math.pow(2, HOW_MANY), HOW_MANY.toString() + " binary choices should result in " + Math.pow(2, HOW_MANY).toString() + " possible conflict resolutions and therefore merge results.");
+            console.log("merging again...");
+            mergeAgain(registry, merged, d => nameMap.get(d));
+        });
+    });
+    describe("Embedding of versions", () => {
+        it("Creating embedded versions", () => {
+            const deltaRegistry = new delta_registry_1.DeltaRegistry();
+            const getId = (0, test_helpers_1.mockUuid)();
+            const registry = new version_1.VersionRegistry();
+            const guestCreate = deltaRegistry.newNodeCreation(getId());
+            const guestV1 = registry.createVersion(registry.initialVersion, guestCreate);
+            const guestDel = deltaRegistry.newNodeDeletion(guestCreate, [], []);
+            const guestV2 = registry.createVersion(guestV1, guestDel);
+            const hostCreate = deltaRegistry.newNodeCreation(getId());
+            const hostLink = deltaRegistry.newEdgeUpdate(hostCreate.createOutgoingEdge("guest"), guestCreate);
+            const allCreate = deltaRegistry.newTransaction([guestCreate, hostCreate, hostLink], "");
+            const hostV1 = registry.createVersion(registry.initialVersion, allCreate, () => new Map([
+                ["guest", { version: guestV1, overridings: new Map() }], // no overridings
+            ]));
+            const hostUnlink = deltaRegistry.newEdgeUpdate(hostLink.overwrite(), null);
+            const hostDel = deltaRegistry.newNodeDeletion(hostCreate, [hostUnlink], []);
+            const guestDelOverride = deltaRegistry.newNodeDeletion(guestCreate, [], [hostUnlink]);
+            const allDel = deltaRegistry.newTransaction([hostUnlink, hostDel, guestDelOverride], "");
+            (0, assert_1.assertThrows)(() => {
+                registry.createVersion(hostV1, allDel, () => new Map([
+                    ["guest", { version: guestV2, overridings: new Map() }]
+                ]));
+            }, "should not be able to create host version without explicitly stating that guestDel was overridden by guestDelOver");
+            const hostV2 = registry.createVersion(hostV1, allDel, () => new Map([
+                ["guest", { version: guestV2, overridings: new Map([[guestDel, guestDelOverride]]) }],
+            ]));
+        });
+        it("Merging host versions", () => {
+            const deltaRegistry = new delta_registry_1.DeltaRegistry();
+            const getId = (0, test_helpers_1.mockUuid)();
+            const registry = new version_1.VersionRegistry();
+            const createA = deltaRegistry.newNodeCreation(getId());
+            const createB = deltaRegistry.newNodeCreation(getId());
+            const guestA = registry.createVersion(registry.initialVersion, createA);
+            const guestB = registry.createVersion(registry.initialVersion, createB);
+            const createC = deltaRegistry.newNodeCreation(getId());
+            const createD = deltaRegistry.newNodeCreation(getId());
+            const createAC = deltaRegistry.newTransaction([createA, createC], "");
+            const createBD = deltaRegistry.newTransaction([createB, createD], "");
+            const debugNames = new Map([
+                [createA, "createA"],
+                [createB, "createB"],
+                [createC, "createC"],
+                [createD, "createD"],
+                [createAC, "createAC"],
+                [createBD, "createBD"],
+            ]);
+            const hostAC = registry.createVersion(registry.initialVersion, createAC, () => new Map([
+                ["guest", { version: guestA, overridings: new Map() }],
+            ]));
+            const hostBD = registry.createVersion(registry.initialVersion, createBD, () => new Map([
+                ["guest", { version: guestB, overridings: new Map() }],
+            ]));
+            console.log("Merging hosts...");
+            const mergedHosts = registry.merge([hostAC, hostBD], d => debugNames.get(d));
+            (0, assert_1.assert)(mergedHosts.length === 1, "expected no host merge conflict");
+            console.log("Merging guests...");
+            const mergedGuests = registry.merge([guestA, guestB], d => debugNames.get(d));
+            (0, assert_1.assert)(mergedGuests.length === 1, "expected no guest merge conflict");
+            const [guestAB] = mergedGuests;
+            const [hostABCD] = mergedHosts;
+            const { version: mergedGuest, overridings: mergedOverridings } = hostABCD.getEmbedding("guest");
+            (0, assert_1.assert)(mergedGuest === guestAB, "merged host should embed merged guest");
+            (0, assert_1.assert)(mergedOverridings.size === 0, "expected no overridings in merged embedding");
+        });
+        it("Multi-level embedding", () => {
+            var _a, _b;
+            const deltaRegistry = new delta_registry_1.DeltaRegistry();
+            const getId = (0, test_helpers_1.mockUuid)();
+            const registry = new version_1.VersionRegistry();
+            const createA = deltaRegistry.newNodeCreation(getId());
+            const createB = deltaRegistry.newNodeCreation(getId());
+            const createC = deltaRegistry.newNodeCreation(getId());
+            const createAB = deltaRegistry.newTransaction([createA, createB], "");
+            const createABC = deltaRegistry.newTransaction([createAB, createC], "");
+            const vA = registry.createVersion(registry.initialVersion, createA);
+            const vAB = registry.createVersion(registry.initialVersion, createAB, () => new Map([
+                ["L0", { version: vA, overridings: new Map() }],
+            ]));
+            const vABC = registry.createVersion(registry.initialVersion, createABC, () => new Map([
+                ["L1", { version: vAB, overridings: new Map() }],
+            ]));
+            const createD = deltaRegistry.newNodeCreation(getId());
+            const createE = deltaRegistry.newNodeCreation(getId());
+            const createF = deltaRegistry.newNodeCreation(getId());
+            const createDE = deltaRegistry.newTransaction([createD, createE], "");
+            const createDEF = deltaRegistry.newTransaction([createDE, createF], "");
+            const vD = registry.createVersion(registry.initialVersion, createD);
+            const vDE = registry.createVersion(registry.initialVersion, createDE, () => new Map([
+                ["L0", { version: vD, overridings: new Map() }],
+            ]));
+            const vDEF = registry.createVersion(registry.initialVersion, createDEF, () => new Map([
+                ["L1", { version: vDE, overridings: new Map() }],
+            ]));
+            const [vABCDEF] = registry.merge([vABC, vDEF]);
+            const vABDE = (_a = vABCDEF.embeddings.get("L1")) === null || _a === void 0 ? void 0 : _a.version;
+            (0, assert_1.assert)(vABDE !== undefined, "No L1 merged version");
+            (0, assert_1.assert)([...vABDE].length === 2, "L1 merge result unexpected number of deltas: " + [...vABDE].length);
+            const vAD = (_b = vABDE.embeddings.get("L0")) === null || _b === void 0 ? void 0 : _b.version;
+            (0, assert_1.assert)(vAD !== undefined, "No L0 merged version");
+            (0, assert_1.assert)([...vAD].length === 2, "L1 merge result unexpected number of deltas: " + [...vAD].length);
+        });
+        it("Self-embedding", () => {
+            const deltaRegistry = new delta_registry_1.DeltaRegistry();
+            const getId = (0, test_helpers_1.mockUuid)();
+            const registry = new version_1.VersionRegistry();
+            const createA = deltaRegistry.newNodeCreation(getId());
+            const createB = deltaRegistry.newNodeCreation(getId());
+            const vA = registry.createVersion(registry.initialVersion, createA, version => new Map([
+                ["self", { version, overridings: new Map() }]
+            ]));
+            const vB = registry.createVersion(registry.initialVersion, createB, version => new Map([
+                ["self", { version, overridings: new Map() }]
+            ]));
+            const [vAB] = registry.merge([vA, vB]);
+            (0, assert_1.assert)(vAB.embeddings.get("self") !== undefined, "Expected merge result to embed itself");
+        });
+    });
+});
+//# sourceMappingURL=version.test.js.map

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 1 - 0
dist/lib/version.test.js.map


+ 12 - 0
dist/lib/version_parser.d.ts

@@ -0,0 +1,12 @@
+import { VersionRegistry } from "./version";
+import { DeltaParser } from "./delta_parser";
+export declare class VersionParser {
+    readonly deltaParser: DeltaParser;
+    readonly versionRegistry: VersionRegistry;
+    constructor(deltaParser: DeltaParser, versionRegistry: VersionRegistry);
+    load({ externalDependencies, deltas, versions }: {
+        externalDependencies: any;
+        deltas: any;
+        versions: any;
+    }, onLoadDelta: (Delta: any) => void, onLoadVersion: (Version: any) => void): void;
+}

+ 50 - 0
dist/lib/version_parser.js

@@ -0,0 +1,50 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.VersionParser = void 0;
+const buffer_1 = require("buffer"); // NodeJS library
+class VersionParser {
+    constructor(deltaParser, versionRegistry) {
+        this.deltaParser = deltaParser;
+        this.versionRegistry = versionRegistry;
+    }
+    load({ externalDependencies, deltas, versions }, onLoadDelta, onLoadVersion) {
+        for (const e of externalDependencies) {
+            if (this.versionRegistry.lookupOptional(buffer_1.Buffer.from(e, 'hex')) === undefined) {
+                throw new Error("Cannot load versions: missing dependency: " + e);
+            }
+        }
+        for (const d of deltas) {
+            const loadedDelta = this.deltaParser.loadDelta(d);
+            onLoadDelta(loadedDelta);
+        }
+        for (const { id, delta, parent, embeddings } of versions) {
+            const parentVersion = this.versionRegistry.lookup(buffer_1.Buffer.from(parent, 'hex'));
+            const parentDelta = this.deltaParser.deltaRegistry.deltas.get(delta);
+            const theEmbeddings = new Map();
+            const selfEmbeddingKeys = new Set();
+            for (const { guestId, v, ovr } of embeddings) {
+                console.log({ guestId, v, id });
+                if (v === id) {
+                    selfEmbeddingKeys.add(guestId);
+                }
+                else {
+                    const guestVersion = this.versionRegistry.lookupOptional(buffer_1.Buffer.from(v, 'hex'));
+                    for (const [key, val] of Object.entries(ovr)) {
+                        const guestDelta = this.deltaParser.deltaRegistry.deltas.get(key);
+                        const hostDelta = this.deltaParser.deltaRegistry.deltas.get(val);
+                        theEmbeddings.set(guestDelta, hostDelta);
+                    }
+                }
+            }
+            const loadedVersion = this.versionRegistry.createVersion(parentVersion, parentDelta, newVersion => {
+                for (const guestId of selfEmbeddingKeys) {
+                    theEmbeddings.set(guestId, { version: newVersion, overridings: new Map() });
+                }
+                return theEmbeddings;
+            });
+            onLoadVersion(loadedVersion);
+        }
+    }
+}
+exports.VersionParser = VersionParser;
+//# sourceMappingURL=version_parser.js.map

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 1 - 0
dist/lib/version_parser.js.map