浏览代码

Move 'onion-core' library to separate package

Joeri Exelmans 2 年之前
父节点
当前提交
01b17749e4
共有 50 个文件被更改,包括 970 次插入4779 次删除
  1. 3 1
      .gitignore
  2. 1 0
      package.json
  3. 880 797
      pnpm-lock.yaml
  4. 2 2
      src/frontend/d3graph/d3graph_editable.tsx
  5. 2 2
      src/frontend/d3graph/reducers/delta_graph.ts
  6. 2 2
      src/frontend/d3graph/reducers/history_graph.ts
  7. 2 2
      src/frontend/d3graph/reducers/onion_graph.ts
  8. 3 3
      src/frontend/demos/demo_bm.tsx
  9. 3 3
      src/frontend/demos/demo_corr.tsx
  10. 3 3
      src/frontend/demos/demo_editor.tsx
  11. 3 7
      src/frontend/demos/demo_le.tsx
  12. 1 8
      src/frontend/demos/demo_live.tsx
  13. 5 7
      src/frontend/demos/demo_pd.tsx
  14. 3 3
      src/frontend/onion_context.tsx
  15. 1 1
      src/frontend/rountangleEditor/RountangleActions.ts
  16. 1 1
      src/frontend/rountangleEditor/RountangleComponent.tsx
  17. 4 4
      src/frontend/rountangleEditor/RountangleEditor.tsx
  18. 1 1
      src/frontend/rountangleEditor/RountangleResizeHandleComponent.tsx
  19. 3 3
      src/frontend/versioned_model/correspondence.tsx
  20. 1 1
      src/frontend/versioned_model/graph_view.tsx
  21. 6 6
      src/frontend/versioned_model/manual_renderer.tsx
  22. 5 7
      src/frontend/versioned_model/merge_view.tsx
  23. 7 7
      src/frontend/versioned_model/single_model.tsx
  24. 0 301
      src/onion/delta.test.ts
  25. 0 523
      src/onion/delta.ts
  26. 0 82
      src/onion/delta_parser.ts
  27. 0 97
      src/onion/delta_registry.ts
  28. 0 106
      src/onion/graph_state.test.ts
  29. 0 541
      src/onion/graph_state.ts
  30. 0 206
      src/onion/legacy/delta.ts.legacy
  31. 0 75
      src/onion/legacy/delta_parser.ts.legacy
  32. 0 82
      src/onion/legacy/delta_registry.ts.legacy
  33. 0 635
      src/onion/legacy/primitive_delta.ts.legacy
  34. 0 19
      src/onion/types.ts
  35. 0 395
      src/onion/version.test.ts
  36. 0 599
      src/onion/version.ts
  37. 0 55
      src/onion/version_parser.ts
  38. 1 1
      src/parser/parser.ts
  39. 0 15
      src/parser/rountangle_parser.test.ts
  40. 25 6
      src/parser/rountangle_parser.ts
  41. 0 25
      src/util/assert.ts
  42. 0 18
      src/util/buffer_xor.ts
  43. 0 49
      src/util/dfs.test.ts
  44. 0 35
      src/util/dfs.ts
  45. 0 0
      src/util/mock_node_util.ts
  46. 0 19
      src/util/partial_ordering.ts
  47. 0 12
      src/util/permutations.ts
  48. 0 7
      src/util/test_helpers.ts
  49. 1 4
      tsconfig.json
  50. 1 1
      webpack.config.cjs

+ 3 - 1
.gitignore

@@ -4,4 +4,6 @@ node_modules/
 .nyc_output/
 
 # this file will be generated when running webpack:
-dist/*
+dist/*
+
+.vscode/

+ 1 - 0
package.json

@@ -15,6 +15,7 @@
 		"d3-force": "^3.0.0",
 		"d3-scale": "^4.0.2",
 		"d3-selection": "^3.0.0",
+		"onion-core": "git+https://msdl.uantwerpen.be/git/jexelmans/onion-core.git",
 		"react": "^18.2.0",
 		"react-dom": "^18.2.0",
 		"stream-browserify": "^3.0.0",

文件差异内容过多而无法显示
+ 880 - 797
pnpm-lock.yaml


+ 2 - 2
src/frontend/d3graph/d3graph_editable.tsx

@@ -3,8 +3,8 @@ import {D3Graph, D3Forces} from "./d3graph";
 import {D3OnionGraphData, D3OnionNodeData} from "./reducers/onion_graph";
 
 import {OnionContext, OnionContextType} from "../onion_context";
-import {PrimitiveDelta} from "onion/delta";
-import {INodeState, IValueState, GraphState} from "onion/graph_state";
+import {PrimitiveDelta} from "onion-core";
+import {INodeState, IValueState, GraphState} from "onion-core";
 
 export type UserEditCallback = (deltas: PrimitiveDelta[], description: string) => void;
 type SetNodePositionCallback = (x:number, y:number) => void;

+ 2 - 2
src/frontend/d3graph/reducers/delta_graph.ts

@@ -1,5 +1,5 @@
-import {Delta, Transaction} from "onion/delta";
-import {NodeCreation, NodeDeletion, EdgeUpdate} from "onion/delta";
+import {Delta, Transaction} from "onion-core";
+import {NodeCreation, NodeDeletion, EdgeUpdate} from "onion-core";
 import {D3GraphData, D3NodeData, D3LinkData} from "../d3graph";
 
 export type DeltaGraphState = D3GraphData<Delta,null>;

+ 2 - 2
src/frontend/d3graph/reducers/history_graph.ts

@@ -1,5 +1,5 @@
-import {Version} from "onion/version";
-import {Delta} from "onion/delta";
+import {Version} from "onion-core";
+import {Delta} from "onion-core";
 import {D3GraphData, D3NodeData, D3LinkData} from "../d3graph";
 
 export type HistoryGraphState = D3GraphData<Version,Delta|null>;

+ 2 - 2
src/frontend/d3graph/reducers/onion_graph.ts

@@ -1,7 +1,7 @@
 // The React state and reducer of a D3Graph component showing an onion graph state.
 
-import {PrimitiveValue, UUID} from "onion/types";
-import {INodeState, IValueState, GraphStateListener} from "onion/graph_state";
+import {PrimitiveValue, UUID} from "onion-core";
+import {INodeState, IValueState, GraphStateListener} from "onion-core";
 import {D3GraphData, D3NodeData, D3LinkData} from "../d3graph";
 
 export type D3OnionNodeData = D3NodeData<INodeState|IValueState>;

+ 3 - 3
src/frontend/demos/demo_bm.tsx

@@ -2,9 +2,9 @@ import * as React from 'react';
 import {Center, Group, SimpleGrid, Space, Stack, Text, Title} from '@mantine/core';
 
 import {OnionContext} from "../onion_context";
-import {DeltaRegistry} from "onion/delta_registry";
-import {VersionRegistry} from "onion/version";
-import {mockUuid} from "../../util/test_helpers";
+import {DeltaRegistry} from "onion-core";
+import {VersionRegistry} from "onion-core";
+import {mockUuid} from "onion-core";
 import {InfoHoverCard} from '../info_hover_card';
 import {newOnion, undoButtonHelpText} from "../versioned_model/single_model";
 import {newCorrespondence, undoButtonHelpTextCorr} from '../versioned_model/correspondence';

+ 3 - 3
src/frontend/demos/demo_corr.tsx

@@ -1,10 +1,10 @@
 import * as React from "react";
 import {SimpleGrid, Text, Title, Stack, Center, Image, Group, Space} from "@mantine/core";
 
-import {DeltaRegistry} from "onion/delta_registry";;
-import {mockUuid} from "../../util/test_helpers";
+import {DeltaRegistry} from "onion-core";
+import {mockUuid} from "onion-core";
 import {OnionContext} from "../onion_context";
-import {VersionRegistry} from "onion/version";
+import {VersionRegistry} from "onion-core";
 import {newOnion, undoButtonHelpText} from "../versioned_model/single_model";
 import {newCorrespondence, undoButtonHelpTextCorr} from "../versioned_model/correspondence";
 import {ModalManualRenderer, ManualRendererProps} from "../versioned_model/manual_renderer";

+ 3 - 3
src/frontend/demos/demo_editor.tsx

@@ -1,9 +1,9 @@
 import * as React from "react";
 import {SimpleGrid, Text, Title, Stack, Center, Group, Space, Image} from "@mantine/core";
 
-import {DeltaRegistry} from "onion/delta_registry";;
-import {VersionRegistry} from "onion/version";
-import {mockUuid} from "../../util/test_helpers";
+import {DeltaRegistry} from "onion-core";;
+import {VersionRegistry} from "onion-core";
+import {mockUuid} from "onion-core";
 
 import {newOnion} from "../versioned_model/single_model";
 import {OnionContext} from "../onion_context";

+ 3 - 7
src/frontend/demos/demo_le.tsx

@@ -1,19 +1,15 @@
 import * as React from "react";
-import {SimpleGrid, Text, Title, Stack, Center, Group, Space, Image, Button, Paper, Alert, createStyles, Switch} from "@mantine/core";
+import {SimpleGrid, Text, Title, Stack, Center, Group, Space,  Button, Paper, Alert, Switch} from "@mantine/core";
 import {IconTrash, IconRowInsertTop, IconRowInsertBottom, IconAlertCircle} from '@tabler/icons';
 
-import {DeltaRegistry} from "onion/delta_registry";
-import {PrimitiveDelta, TargetNode, TargetValue} from "onion/delta";
-import {VersionRegistry} from "onion/version";
-import {mockUuid} from "../../util/test_helpers";
-import {PrimitiveValue} from "onion/types";
-import {INodeState, IValueState} from "onion/graph_state";
+import {DeltaRegistry, PrimitiveDelta, VersionRegistry, PrimitiveValue, INodeState, IValueState, mockUuid} from "onion-core";
 
 import {newOnion} from "../versioned_model/single_model";
 import {InfoHoverCard} from "../info_hover_card";
 import {OnionContext} from "../onion_context";
 import {names} from "../../util/names";
 
+
 export const demo_LE_description =
     <>
         <Title order={4}>

+ 1 - 8
src/frontend/demos/demo_live.tsx

@@ -9,14 +9,7 @@ import {newOnion} from '../versioned_model/single_model';
 import {InfoHoverCard, InfoHoverCardOverlay} from "../info_hover_card";
 import {OnionContext} from "../onion_context";
 
-import {mockUuid} from "../../util/test_helpers";
-import {NodeDeletion, EdgeUpdate} from "onion/delta";
-import {DeltaRegistry} from "onion/delta_registry";
-import {VersionRegistry} from "onion/version";
-import {Delta} from "onion/delta";
-import {INodeState, IValueState} from "onion/graph_state";
-import {Version} from "onion/version";
-import {GraphState} from "onion/graph_state"; 
+import {mockUuid, NodeDeletion, EdgeUpdate, INodeState, DeltaRegistry, IValueState, Version, GraphState, Delta, VersionRegistry} from "onion-core";
 
 export const demo_Live_description = <>
   <Title order={4}>

+ 5 - 7
src/frontend/demos/demo_pd.tsx

@@ -1,15 +1,13 @@
 import * as React from 'react';
-import * as Icons from '@tabler/icons';
-import {Button, Divider, Group, Image, SimpleGrid, Space, Text, Title, Stack} from '@mantine/core';
+import {Group, Image, SimpleGrid, Space, Text, Title, Stack} from '@mantine/core';
 
 import {OnionContext} from "../onion_context";
-import {DeltaRegistry} from "onion/delta_registry";
-import {VersionRegistry} from "onion/version";
-import {PrimitiveValue} from 'onion/types';
-import {mockUuid} from '../../util/test_helpers';
+import {DeltaRegistry} from "onion-core";
+import {VersionRegistry} from "onion-core";
+import {mockUuid} from 'onion-core';
 import {Actionblock, Resultblock} from './blocks';
 import pdImage from './assets/pd.svg';
-import {newOnion, undoButtonHelpText, VersionedModelState} from '../versioned_model/single_model';
+import {newOnion, undoButtonHelpText} from '../versioned_model/single_model';
 import {InfoHoverCard} from '../info_hover_card';
 
 export const demo_PD_description =

+ 3 - 3
src/frontend/onion_context.tsx

@@ -1,8 +1,8 @@
 import * as React from "react";
 
-import {DeltaRegistry} from "onion/delta_registry";;
-import {UUID} from "onion/types";
-import {mockUuid} from "../util/test_helpers";
+import {DeltaRegistry} from "onion-core";;
+import {UUID} from "onion-core";
+import {mockUuid} from "onion-core";
 import {useConst} from "./use_const";
 
 export interface OnionContextType {

+ 1 - 1
src/frontend/rountangleEditor/RountangleActions.ts

@@ -1,4 +1,4 @@
-import {PrimitiveValue, UUID} from "onion/types";
+import {PrimitiveValue, UUID} from "onion-core";
 
 interface CreateRountangle  {tag: 'createRountangle',  id: UUID, posX: number, posY: number, width: number, height: number}
 interface MoveRountangle    {tag: 'moveRountangle',    id: PrimitiveValue, newPosX: number, newPosY: number}

+ 1 - 1
src/frontend/rountangleEditor/RountangleComponent.tsx

@@ -2,7 +2,7 @@ import * as React from "react";
 import {RountangleAction} from "./RountangleActions";
 import {RountangleResizeHandleComponent} from "./RountangleResizeHandleComponent";
 import {Rountangle} from "./RountangleEditor";
-import {UUID} from "onion/types";
+import {UUID} from "onion-core";
 
 export interface RountangleProps extends Rountangle {
     id:       UUID;

+ 4 - 4
src/frontend/rountangleEditor/RountangleEditor.tsx

@@ -1,12 +1,12 @@
 import * as React from "react";
 import {RountangleComponent} from "./RountangleComponent";
 import {OnionContext, OnionContextType} from "../onion_context";
-import {PrimitiveValue, UUID} from "onion/types";
+import {PrimitiveValue, UUID} from "onion-core";
 import {D3OnionGraphData, D3OnionNodeData, D3OnionLinkData} from "../d3graph/reducers/onion_graph";
 import {RountangleAction} from "./RountangleActions";
-import {INodeState, IValueState, GraphState} from "onion/graph_state";
-import {PrimitiveDelta, TargetValue} from "onion/delta";
-import {assert, assertNever} from "../../util/assert";
+import {INodeState, IValueState, GraphState} from "onion-core";
+import {PrimitiveDelta, TargetValue} from "onion-core";
+import {assertNever} from "onion-core";
 
 export interface Rountangle {
     readonly posX:   number;

+ 1 - 1
src/frontend/rountangleEditor/RountangleResizeHandleComponent.tsx

@@ -1,6 +1,6 @@
 import * as React from "react";
 import {RountangleState} from "./RountangleComponent";
-import {PrimitiveValue} from "onion/types";
+import {PrimitiveValue} from "onion-core";
 
 interface RountangleResizeHandleProps {
     id:               PrimitiveValue;

+ 3 - 3
src/frontend/versioned_model/correspondence.tsx

@@ -6,9 +6,9 @@ import {newOnion} from "./single_model";
 import {emptyGraph} from "../d3graph/d3graph";
 import {D3GraphUpdater} from "../d3graph/reducers/onion_graph";
 import {RountangleParser} from "../../parser/rountangle_parser";
-import {Version} from "onion/version";
-import {GraphState} from "onion/graph_state"; 
-import {PrimitiveDelta} from "onion/delta";
+import {Version} from "onion-core";
+import {GraphState} from "onion-core"; 
+import {PrimitiveDelta} from "onion-core";
 
 export const undoButtonHelpTextCorr =
   `Navigating to a version in the correspondence model,

+ 1 - 1
src/frontend/versioned_model/graph_view.tsx

@@ -6,7 +6,7 @@ import {D3Graph, D3GraphData, D3NodeData, defaultGraphForces} from "../d3graph/d
 import {InfoHoverCardOverlay} from "../info_hover_card";
 import { D3OnionGraphData } from "../d3graph/reducers/onion_graph";
 import { D3GraphEditable, UserEditCallback } from "../d3graph/d3graph_editable";
-import { GraphState } from "onion/graph_state";
+import { GraphState } from "onion-core";
 
 function esc(str) {
   return str.replace(/\\/g, '\\\\').replace(/"/g, '\\"');

+ 6 - 6
src/frontend/versioned_model/manual_renderer.tsx

@@ -10,15 +10,15 @@ import {
 import {Button, Group, List, Modal, Stack, Table, Text, Title} from "@mantine/core";
 
 import {RountangleEditor} from "../rountangleEditor/RountangleEditor";
-import {Version} from "onion/version";
+import {Version} from "onion-core";
 import {InfoHoverCardOverlay} from "../info_hover_card";
 import {D3Graph, defaultGraphForces} from "../d3graph/d3graph";
 import {D3OnionGraphData, D3GraphUpdater} from "../d3graph/reducers/onion_graph";
-import {EdgeUpdate, NodeCreation, NodeDeletion} from "onion/delta";
-import {PrimitiveDelta, ExistingEdge} from "onion/delta";
-import {DeltaRegistry} from "onion/delta_registry";
-import {PrimitiveValue} from "onion/types";
-import {GraphState, INodeState} from "onion/graph_state";
+import {EdgeUpdate, NodeCreation, NodeDeletion} from "onion-core";
+import {PrimitiveDelta, ExistingEdge} from "onion-core";
+import {DeltaRegistry} from "onion-core";
+import {PrimitiveValue} from "onion-core";
+import {GraphState, INodeState} from "onion-core";
 import {Geometry2DRect, getGeometry, isInside} from "../../parser/rountangle_parser";
 
 export interface ManualRendererProps {

+ 5 - 7
src/frontend/versioned_model/merge_view.tsx

@@ -2,14 +2,12 @@ import * as React from "react";
 import * as Mantine from "@mantine/core";
 import * as Icons from "@tabler/icons";
 
-import {Version} from "onion/version";
-import {Delta} from "onion/delta";
-import {DeltaParser} from "onion/delta_parser";
-import {VersionParser} from "onion/version_parser";
+import {Version} from "onion-core";
+import {Delta} from "onion-core";
+import {DeltaParser} from "onion-core";
+import {VersionParser} from "onion-core";
 import {genericGraphVizLayout, GraphView} from "./graph_view";
-import {fullVersionId, HistoryGraphState, historyGraphReducer} from "../d3graph/reducers/history_graph";
-import {InfoHoverCardOverlay} from "../info_hover_card";
-import {OnionContext, OnionContextType} from "../onion_context";
+import {fullVersionId, historyGraphReducer} from "../d3graph/reducers/history_graph";
 
 const inputColor = 'seashell';
 const outputColor = 'lightblue';

+ 7 - 7
src/frontend/versioned_model/single_model.tsx

@@ -27,13 +27,13 @@ import {RountangleEditor} from "../rountangleEditor/RountangleEditor";
 import {InfoHoverCardOverlay} from "../info_hover_card";
 import {OnionContext, OnionContextType} from "../onion_context";
 
-import {Version, VersionRegistry, Embeddings} from "onion/version";
-import {PrimitiveDelta, Transaction, findTxDependencies} from "onion/delta";
-import {DeltaRegistry} from "onion/delta_registry";
-import {PrimitiveValue, UUID} from "onion/types";
-import {GraphState} from "onion/graph_state"; 
-import {Delta} from "onion/delta";
-import {DeltaParser} from "onion/delta_parser";
+import {Version, VersionRegistry, Embeddings} from "onion-core";
+import {PrimitiveDelta, Transaction, findTxDependencies} from "onion-core";
+import {DeltaRegistry} from "onion-core";
+import {PrimitiveValue, UUID} from "onion-core";
+import {GraphState} from "onion-core"; 
+import {Delta} from "onion-core";
+import {DeltaParser} from "onion-core";
 
 export const undoButtonHelpText = "Use the Undo/Redo buttons or the History panel to navigate to any version.";
 

+ 0 - 301
src/onion/delta.test.ts

@@ -1,301 +0,0 @@
-import {Delta, TargetNode, TargetValue} from "./delta";
-import {DeltaRegistry} from "./delta_registry";
-
-import {mockUuid,} from "../util/test_helpers";
-import {assert,} from "../util/assert";
-
-
-describe("Primitive Delta", () => {
-
-  it("Delete/delete node conflict", () => {
-    const registry = new DeltaRegistry();
-    const getId = mockUuid();
-
-    const creation = registry.newNodeCreation(getId());
-    assert(creation.conflictsWith.length === 0, "did not expect node creation to be conflicting with any other operation");
-
-    const deletion1 = registry.newNodeDeletion(creation, [], []);
-    assert(deletion1.conflictsWith.length === 0, "did not expect first deletion alone to be conflicting with anything");
-
-    const deletion2 = registry.newNodeDeletion(creation, [], []);
-    assert(deletion1 === deletion2, "expected deltas to be identical");
-    assert(deletion1.conflictsWith.length === 0, "expected no conflicts");
-
-    assert(deletion1.hash.equals(deletion2.hash), "deletions should have equal hash");
-  });
-
-  it("Create/create edge conflict", () => {
-    const registry = new DeltaRegistry();
-    const getId = mockUuid();
-
-    const sourceCreation = registry.newNodeCreation(getId());
-    const target1Creation = registry.newNodeCreation(getId());
-    const target2Creation = registry.newNodeCreation(getId());
-
-    const edge1Creation = registry.newEdgeUpdate(sourceCreation.createOutgoingEdge("label"), target1Creation);
-    assert(edge1Creation.conflictsWith.length === 0, "expected a single edge alone to not be involved in conflicts");
-
-    const edge2Creation = registry.newEdgeUpdate(sourceCreation.createOutgoingEdge("label"), target2Creation);
-    assert(edge1Creation.conflictsWith.length === 1, "expected conflict: same edge created twice, concurrently")
-    assert(edge2Creation.conflictsWith.length === 1, "expected conflict: same edge created twice, concurrently")
-  });
-
-  it("Update/update edge conflict", () => {
-    const registry = new DeltaRegistry();
-    const getId = 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);
-    assert(update1.conflictsWith.length === 0, "expected no conflict with a single edge update.")
-
-    const update2 = registry.newEdgeUpdate(edgeCreation.overwrite(),newTarget2Creation);
-    assert(update1.conflictsWith.length === 1, "expected conflict between concurrent edge updates.")
-    assert(update2.conflictsWith.length === 1, "expected conflict between concurrent edge updates.")
-  });
-
-  it("Delete/require (edge source) conflict", () => {
-    const registry = new DeltaRegistry();
-    const getId = mockUuid();
-
-    const sourceCreation = registry.newNodeCreation(getId());
-    const targetCreation = registry.newNodeCreation(getId());
-
-    const sourceDeletion = registry.newNodeDeletion(sourceCreation, [], []);
-    assert(sourceDeletion.conflictsWith.length === 0, "expected no conflicts so far");
-
-    const edgeCreation = registry.newEdgeUpdate(sourceCreation.createOutgoingEdge("label"), targetCreation);
-    assert(edgeCreation.conflictsWith.length === 1, "expected require/delete conflict, because edge source is concurrently deleted");
-    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 DeltaRegistry();
-    const getId = 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);
-    assert(edgeCreation.conflictsWith.length === 0, "expected no conflicts so far");
-
-    const sourceDeletion = registry.newNodeDeletion(sourceCreation, [], []);
-    assert(edgeCreation.conflictsWith.length === 1, "expected require/delete conflict, because edge source is concurrently deleted");
-    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 DeltaRegistry();
-    const getId = 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], []);
-    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 DeltaRegistry();
-    const getId = 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, [], []);
-
-    assert(edgeCreation.conflictsWith.length === 1, "expected require/delete conflict, because edge source is concurrently deleted");
-    assert(sourceDeletion.conflictsWith.length === 1, "expected require/delete conflict, because edge source is concurrently deleted");
-
-    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 DeltaRegistry();
-    const getId = 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);
-
-
-    assert(edgeUpdate.conflictsWith.length === 2, "expected U/U conflict");
-    assert(edgeUpdate.conflictsWith.some(([d])=>d===edgeDeletion), "expected U/U conflict");
-    assert(edgeUpdate.conflictsWith.some(([d])=>d===sourceDeletion), "expected U/U conflict");
-
-    assert(edgeDeletion.conflictsWith.length === 1, "expected U/U conflict");
-    assert(edgeDeletion.conflictsWith.some(([d])=>d===edgeUpdate), "expected U/U conflict");
-  });
-
-  it("Delete/require (edge target) conflict", () => {
-    const registry = new DeltaRegistry();
-    const getId = mockUuid();
-
-    const sourceCreation = registry.newNodeCreation(getId());
-    const targetCreation = registry.newNodeCreation(getId());
-
-    const targetDeletion = registry.newNodeDeletion(targetCreation, [], []);
-
-    const edgeCreation = registry.newEdgeUpdate(sourceCreation.createOutgoingEdge("label"), targetCreation);
-
-    assert(edgeCreation.conflictsWith.length === 1, "expected require/delete conflict");
-    assert(targetDeletion.conflictsWith.length === 1, "expected require/delete conflict");
-  });
-
-  it("Delete/require (edge target) conflict (reverse order)", () => {
-    const registry = new DeltaRegistry();
-    const getId = 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, [], []);
-
-    assert(edgeCreation.conflictsWith.length === 1, "expected require/delete conflict");
-    assert(targetDeletion.conflictsWith.length === 1, "expected require/delete conflict");
-  });
-
-  it("Require (edge target), then delete (no conflict)", () => {
-    const registry = new DeltaRegistry();
-    const getId = 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)
-    assert(edgeCreation.conflictsWith.length === 0, "expected no require/delete conflict");
-    assert(targetDeletion.conflictsWith.length === 0, "expected no require/delete conflict");
-  });
-
-  it("Delete source and target of edge (no conflict)", () => {
-    const registry = new DeltaRegistry();
-    const getId = 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], []);
-
-    assert(sourceDeletion.conflictsWith.length === 0, "expected no conflicts");
-
-    const targetDeletion = registry.newNodeDeletion(targetCreation, [], [edgeDeletion]);
-
-    assert(targetDeletion.conflictsWith.length === 0, "expected no conflicts");
-  });
-
-  it("Delete node with self-edge", () => {
-    const registry = new DeltaRegistry();
-    const getId = 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);
-    assert(nodeDeletion.conflictsWith.length === 0, "expected no conflicts");
-  });
-
-  it("Read/write conflict", () => {
-    const registry = new DeltaRegistry();
-    const getId = 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()]);
-
-    assert(xUpdate.conflictsWith.length === 1 && yUpdate.conflictsWith.length === 1, "expected one conflict");
-    assert(xUpdate.conflictsWith.some(([d]) => d === yUpdate), "expected one conflict");
-  });
-  
-  it("No Read/Read conflict", () => {
-    const registry = new DeltaRegistry();
-    const getId = 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()]);
-  
-    assert(yInitial.conflictsWith.length === 0 && zInitial.conflictsWith.length === 0, "expected no conflicts");
-  });
-
-  it("R*/U conflict", () => {
-    const registry = new DeltaRegistry();
-    const getId = mockUuid();
-
-    const collection = registry.newNodeCreation(getId());
-    const newEdge = collection.createOutgoingEdge("x");
-    assert(newEdge === collection.createOutgoingEdge("x"), "Should return same object");
-
-    const addItemX = registry.newEdgeUpdate(newEdge, "x");
-    assert(newEdge === collection.createOutgoingEdge("x"), "Should return same object");
-
-    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)
-
-    assert(readAll.conflictsWith.length === 1
-       && addItemY.conflictsWith.length === 1, "Expected one conflict here");
-    assert(readAll.conflictsWith.some(([d]) => d === addItemY)
-       && addItemY.conflictsWith.some(([d]) => d === readAll), "Expected 'addItemY' and 'readAll' to be conflicting");
-
-    assert(addItemX.conflictsWith.length === 0, "Still no conflicts here");
-  })
-});
-

+ 0 - 523
src/onion/delta.ts

@@ -1,523 +0,0 @@
-import {Buffer} from "buffer";
-import {PrimitiveValue, UUID} from "./types";
-import { buffersXOR } from "../util/buffer_xor";
-
-type ConflictType = "U/U" | "R/U" | "R*/U" | "U/D" | "D/D";
-
-export abstract class Delta {
-  readonly hash: Buffer;
-  readonly description: string;
-  readonly conflictsWith: [Delta, ConflictType][] = [];
-  readonly partOf: Transaction[] = [];
-
-  constructor(hash: Buffer, description: string) {
-    this.hash = hash;
-    this.description = description;
-  }
-
-  abstract getDependencies(): readonly [Delta,string][];
-
-  hasTransitiveDependency(other: Delta) {
-    if (this === other) return true;
-    for (const [d] of this.getDependencies()) {
-      if (d.hasTransitiveDependency(other)) return true;
-    }
-    return false;
-  }
-
-  serialize(): any {
-    return {
-      hash: this.hash.toString('hex'),
-      type: this.constructor.name,
-    }
-  }
-
-  abstract iterPrimitiveDeltas(): Generator<PrimitiveDelta>;
-}
-
-export abstract class PrimitiveDelta extends Delta {
-  // constructor(hash: Buffer, description: string) {
-  //   super(has, description);
-  // }
-  *iterPrimitiveDeltas() {
-    yield this;
-  }
-}
-
-function registerConflict(d1: Delta, d2: Delta, type: ConflictType) {
-  d1.conflictsWith.push([d2, type]);
-  d2.conflictsWith.push([d1, type]);
-}
-
-export class NodeCreation extends PrimitiveDelta {
-  readonly id: UUID;
-
-  // Inverse dependencies
-  readonly outgoingEdges: Map<string, NewEdge> = new Map();
-  readonly incomingEdges: EdgeUpdate[] = []; // all deltas EVER that set this node as target of an edge.
-  readonly readAllOutgoing: ReadAllOutgoing[] = [];
-  readonly deletions: NodeDeletion[] = [];
-
-  constructor(hash: Buffer, id: UUID) {
-    super(hash, `NEW(${JSON.stringify(id)})`);
-    this.id = id;
-  }
-
-  getDependencies(): [Delta,string][] {
-    return [];
-  }
-
-  createOutgoingEdge(label: string, after: ReadAllOutgoing[] = []): NewEdge {
-    const edgeId = label + ':' + 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: EdgeUpdate) {
-    // 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 as NewEdge).after.includes(r)) {
-        registerConflict(r, u, "R*/U");
-      }
-    }
-    // outgoing edge already stored in Edge.
-  }
-
-  registerIncomingEdge(u: EdgeUpdate) {
-    // 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: NodeDeletion) {
-    // 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) {
-    // 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(): any {
-    return {...super.serialize(), id: this.id};
-  }
-}
-
-// 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.
-export class ReadAllOutgoing extends PrimitiveDelta {
-  // Dependencies:
-  readonly node: NodeCreation;
-  readonly after: readonly NewEdge[];
-
-  constructor(hash: Buffer, node: NodeCreation, after: readonly NewEdge[]) {
-    super(hash, `R*(${node.id})`);
-    this.node = node;
-    this.after = after;
-    // Register inverse dependencies:
-    node.registerReadAllOutgoing(this);
-  }
-
-  getDependencies(): [Delta, string][] {
-    return [
-      [this.node, "N"],
-      ...([] as [Delta,string][]).concat(...this.after.map(a => a.overwrittenBy.map(u => [u, "A"] as [Delta, string]))),
-    ];
-  }
-}
-
-export class NodeDeletion extends PrimitiveDelta {
-  // Dependencies:
-  node: NodeCreation;
-  afterSrc: readonly EdgeUpdate[];
-  afterTgt: readonly EdgeUpdate[];
-
-  constructor(hash: Buffer, node: NodeCreation, afterSrc: readonly EdgeUpdate[], afterTgt: readonly EdgeUpdate[]) {
-    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(): [Delta, string][] {
-    return [
-      [this.node, "D"],
-      ...this.afterSrc.map(u => [u, "A"] as [Delta, string]),
-      ...this.afterTgt.map(u => [u, "A"] as [Delta, string]),
-    ];
-  }
-
-  serialize(): any {
-    return {
-      ...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')),
-    };
-  }
-}
-
-// Detects write/write and write/read conflicts.
-export abstract class Edge {
-  readonly source: NodeCreation;
-  readonly label: string;
-
-  // Inverse dependencies
-  readonly overwrittenBy: EdgeUpdate[] = [];
-  readonly readBy: EdgeUpdate[] = [];
-
-  constructor(source: NodeCreation, label: string) {
-    this.source = source;
-    this.label = label;
-  }
-
-  abstract getDependencies(): [Delta,string][];
-  abstract serialize(): any;
-
-  // 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: (u: EdgeUpdate) => boolean): Iterable<EdgeUpdate> {
-    for (const ovr of this.overwrittenBy) {
-      if (filter(ovr)) {
-        yield ovr;
-      }
-      else {
-        yield* ovr.overwritable.iterOverwriters(filter);
-      }
-    }
-  }
-
-  registerWrite(write: EdgeUpdate) {
-    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: EdgeUpdate) {
-    for (const write of this.overwrittenBy) {
-      if (read !== write) {
-        // A read conflicts with all writes:
-        registerConflict(read, write, "R/U");
-      }
-    }
-    this.readBy.push(read);
-  }
-}
-
-// An Edge that does not yet have a target
-export class NewEdge extends Edge {
-  readonly after: readonly ReadAllOutgoing[];
-  readonly edgeId: string;
-
-  constructor(source: NodeCreation, label: string, after: readonly ReadAllOutgoing[], edgeId: string) {
-    super(source, label);
-    this.after = after;
-    this.edgeId = edgeId;
-  }
-
-  getDependencies(): [Delta,string][] {
-    return [[this.source, "SRC"]];
-  }
-
-  serialize() {
-    return {
-      type: "NewEdge",
-      source: this.source.hash.toString('hex'),
-      label: this.label,
-    };
-  }
-}
-
-// An Edge that has been assigned a target (by an EdgeUpdate) at least once.
-export class ExistingEdge extends Edge {
-  readonly delta: EdgeUpdate;
-
-  constructor(source: NodeCreation, label: string, delta: EdgeUpdate) {
-    super(source, label);
-    this.delta = delta;
-  }
-
-  getDependencies(): [Delta,string][] {
-    return [[this.delta, "U"]];
-  }
-
-  serialize() {
-    return {
-      type: "ExistingEdge",
-      overwrites: this.delta.hash.toString('hex'),
-    };
-  }
-}
-
-// Target of an edge
-export interface Target {
-  value: NodeCreation | PrimitiveValue;
-  registerDependency(u: EdgeUpdate): void;
-  getDependencies(): [Delta, string][];
-  serialize(): any;
-}
-
-export class TargetNode implements Target {
-  value: NodeCreation;
-
-  constructor(value: NodeCreation) {
-    this.value = value;
-  }
-  registerDependency(u: EdgeUpdate): void {
-    this.value.registerIncomingEdge(u);
-  }
-  getDependencies(): [Delta, string][] {
-    return [[this.value, "TGT"]];
-  }
-  serialize() {
-    return {type: "TargetNode", node: this.value.hash.toString('hex')};
-  }
-}
-
-export class TargetValue implements Target {
-  value: PrimitiveValue;
-
-  constructor(value: PrimitiveValue) {
-    this.value = value;
-  }
-  registerDependency(): void {}
-  getDependencies(): [Delta, string][] {
-    return [];
-  }
-  serialize() {
-    return {type: "TargetValue", value: this.value};
-  }
-}
-
-export class EdgeUpdate extends PrimitiveDelta {
-  // Dependencies
-  readonly overwrites: Edge;
-  readonly reads: readonly ExistingEdge[];
-  readonly target: Target;
-  readonly afterReads: readonly EdgeUpdate[];
-
-  // Inverse dependencies
-  readonly overwritable: ExistingEdge;
-
-  constructor(hash: Buffer, overwrites: Edge, target: Target, reads: readonly ExistingEdge[], afterReads: readonly EdgeUpdate[]) {
-    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(): [Delta,string][] {
-    return this.overwrites.getDependencies()
-      .concat(this.target.getDependencies())
-      .concat(this.reads.map(r => [r.delta, "R"] as [Delta,string]))
-      .concat(this.afterReads.map(a => [a, "A"] as [Delta, string]));
-  }
-
-  serialize(): any {
-    return {
-      ...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')),
-    };
-  }
-}
-
-// 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()),
-//     };
-//   }
-// }
-
-export 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][]) {
-    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(): readonly [Delta,string][] {
-    return this.dependencies;
-  }
-
-  serialize(): any {
-    return {
-      ...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();
-    }
-  }
-}
-
-// 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.
-export function findTxDependencies(deltas: Delta[], candidates?: Set<Delta>): [Transaction, string][] {
-  const txDependencies: Map<Transaction,Set<string>> = 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<string>();
-          txDependencies.set(tx, kinds);
-          return kinds;
-        })();
-        kinds.add(kind);
-      }
-    }
-  }
-  return [...txDependencies.entries()].map(([tx, kinds]) => [tx, [...kinds.values()].join(',')]);
-}

+ 0 - 82
src/onion/delta_parser.ts

@@ -1,82 +0,0 @@
-import {PrimitiveValue} from "./types";
-import {Delta, PrimitiveDelta} from "./delta";
-
-import {
-  NodeCreation,
-  NodeDeletion,
-  EdgeUpdate,
-  Edge, NewEdge, ExistingEdge,
-  Target, TargetValue, TargetNode,
-} from "./delta";
-
-import {DeltaRegistry} from "./delta_registry";
-
-
-import {UUID} from "./types";
-
-export class DeltaParser {
-  readonly deltaRegistry: DeltaRegistry;
-
-  constructor(deltaRegistry) {
-    this.deltaRegistry = deltaRegistry;
-  }
-
-  private getDependency<T>(hash): T {
-    const result = this.deltaRegistry.deltas.get(hash);
-    if (result === undefined) throw new Error("Could not dependency: " + hash);
-    return result as T;
-  }
-
-  private loadTarget({type, ...rest}): NodeCreation | PrimitiveValue {
-    if (type === "TargetValue") {
-      const {value} = rest;
-      return value;
-    }
-    if (type === "TargetNode") {
-      const {node} = rest;
-      return this.getDependency<NodeCreation>(node);
-    }
-    throw new Error("Unknown edge target type: " + type);
-  }
-
-  private loadEdge({type, ...rest}): Edge {
-    if (type === "NewEdge") {
-      const {source, label} = rest;
-      return this.getDependency<NodeCreation>(source).createOutgoingEdge(label);
-    }
-    else if (type === "ExistingEdge") {
-      const {overwrites} = rest;
-      return this.getDependency<EdgeUpdate>(overwrites).overwrite();
-    }
-    throw new Error("Unknown edge type: " + type);
-  }
-
-  loadDelta({type, ...rest}): Delta {
-    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<NodeCreation>(node),
-        afterSrc.map(a => this.getDependency<EdgeUpdate>(a)),
-        afterTgt.map(a => this.getDependency<EdgeUpdate>(a)));
-    }
-    else if (type === "Transaction") {
-      const {type, deltas, dependencies, description} = rest;
-      return this.deltaRegistry.newTransaction(
-        deltas.map(d => this.getDependency<Delta>(d)),
-        description,
-        dependencies.map(d => this.getDependency<Delta>(d)));
-    }
-    throw new Error("Unknown delta type: " + type);
-  }
-}

+ 0 - 97
src/onion/delta_registry.ts

@@ -1,97 +0,0 @@
-import {Delta, Transaction, findTxDependencies, NodeCreation, NodeDeletion, EdgeUpdate, Edge, ExistingEdge, TargetValue, TargetNode, NewEdge, ReadAllOutgoing} from "./delta";
-import {PrimitiveValue, UUID} from "./types";
-import {createHash} from "crypto";
-import {Buffer} from "buffer";
-import {buffersXOR} from "../util/buffer_xor";
-
-function string2Hash(data: string): Buffer {
-  return createHash('sha256')
-    .update(data)
-    .digest();
-}
-
-function json2Hash(data: any): Buffer {
-  return string2Hash(JSON.stringify(data));
-}
-
-interface Serializable {
-  serialize(): any;
-}
-
-function getHash(obj: Serializable) {
-  return json2Hash(obj.serialize());
-}
-
-// Ensures that deltas with the same hash are only created once.
-export class DeltaRegistry {
-  deltas: Map<string, Delta> = new Map();
-
-  // Given the expected hash 
-  private createIdempotent<T extends Delta>(hash: Buffer, callback: () => T): T {
-    const hex = hash.toString('hex');
-    return this.deltas.get(hex) as T || (() => {
-      const delta = callback();
-      this.deltas.set(hex, delta);
-      return delta;
-    })();
-  }
-
-  newNodeCreation(id: UUID): NodeCreation {
-    const hash = 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 NodeCreation(hash, id));
-  }
-
-  newReadAllOutgoing(node: NodeCreation, after: readonly NewEdge[]): ReadAllOutgoing {
-    const hash = createHash('sha256')
-      .update('node=')
-      .update(getHash(node))
-      .update(buffersXOR(...after.map(a => string2Hash(a.edgeId))))
-      .digest();
-    return this.createIdempotent(hash, () => new ReadAllOutgoing(hash, node, after));
-  }
-
-  newNodeDeletion(creation: NodeCreation, afterSrc: readonly EdgeUpdate[], afterTgt: readonly EdgeUpdate[]): NodeDeletion {
-    const hash = createHash('sha256')
-      .update('deletes=')
-      .update(getHash(creation))
-      .update('afterSrc=')
-      .update(buffersXOR(...afterSrc.map(getHash)))
-      .update('afterTgt=')
-      .update(buffersXOR(...afterTgt.map(getHash)))
-      .digest();
-    return this.createIdempotent(hash, () => new NodeDeletion(hash, creation, afterSrc, afterTgt));
-  }
-
-  newEdgeUpdate(overwrites: Edge, target: NodeCreation | PrimitiveValue, reads: readonly ExistingEdge[] = [], afterReads: readonly EdgeUpdate[] = []): EdgeUpdate {
-    const wrappedTarget = target instanceof NodeCreation ? new TargetNode(target) : new TargetValue(target);
-    const hash = 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(buffersXOR(...reads.map(getHash)))
-      .update('afterReads=')
-      .update(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 EdgeUpdate(hash, overwrites, wrappedTarget, reads, afterReads));
-  }
-
-  newTransaction(deltas: Array<Delta>, description: string, dependencies: readonly [Transaction, string][] = findTxDependencies(deltas)): Transaction {
-    // XOR of hashes of deltas
-    const hash = createHash('sha256')
-      .update('deltas=')
-      .update(buffersXOR(...deltas.map(getHash)))
-      .update('dependencies=')
-      .update(buffersXOR(...dependencies.map(([tx]) => getHash(tx))))
-      .digest();
-    return this.createIdempotent<Transaction>(hash, () => new Transaction(hash, deltas, description, dependencies));
-  }
-}

+ 0 - 106
src/onion/graph_state.test.ts

@@ -1,106 +0,0 @@
-import {GraphState, GraphStateListener, INodeState, IValueState} from "./graph_state";
-import {mockUuid} from "../util/test_helpers";
-import {DeltaRegistry} from "./delta_registry";
-import {findTxDependencies, TargetValue} from "./delta";
-import {assert} from "../util/assert";
-
-class MyGraphStateListener implements GraphStateListener {
-  createNode(ns: INodeState) {
-    console.log("created node", ns);
-  }
-  createValue(vs: IValueState) {
-    console.log("created value", vs);
-  }
-  deleteNode(id) {
-    console.log("deleted node", id);
-  }
-  deleteValue(value) {
-    console.log("deleted value", value);
-  }
-  createLinkToNode(sourceId, label: string, targetId) {
-    console.log("created link to node", sourceId, label, targetId);
-  }
-  createLinkToValue(sourceId, label: string, targetValue) {
-    console.log("created link to value", sourceId, label, targetValue);
-  }
-  deleteLink(sourceId, label: string) {
-    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 GraphState();
-    const registry = new DeltaRegistry();
-    const getId = mockUuid();
-
-    function newTransaction(deltas, description) {
-      return registry.newTransaction(deltas, description, 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]);
-
-    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);
-
-    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);
-
-    assert(graphState.nodes.get(nodeId)!.outgoingDeltas.get("x") !== undefined, "Expected outgoing edge 'x' for node " + nodeId);
-
-    assert((graphState.nodes.get(nodeId)!.outgoing.get("x") as IValueState).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");
-  });
-});

+ 0 - 541
src/onion/graph_state.ts

@@ -1,541 +0,0 @@
-import {Delta, Transaction} from "./delta";
-
-import {
-  NodeCreation,
-  NodeDeletion,
-  EdgeUpdate,
-  NewEdge, ExistingEdge,
-  Target, TargetValue, TargetNode,
-} from "./delta";
-
-import {DeltaRegistry} from "./delta_registry";
-
-import {PrimitiveValue} from "./types";
-
-
-// This interface is used to de-couple the graph state (in our case, a data structure interpreted by the d3 library, and by the 'Graph' react component), from GraphState.
-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;
-}
-
-// A 'proxy' GraphStateListener that multicasts graph state operations to a bunch of GraphStateListeners.
-export class FanOutListener implements GraphStateListener {
-  readonly listeners: GraphStateListener[];
-
-  constructor(listeners: GraphStateListener[]) {
-    this.listeners = listeners;
-  }
-  
-  createNode(ns: NodeState) {
-    this.listeners.forEach(m => m.createNode(ns));
-  }
-  createValue(vs: ValueState) {
-    this.listeners.forEach(m => m.createValue(vs));
-  }
-  deleteNode(id: PrimitiveValue) {
-    this.listeners.forEach(m => m.deleteNode(id));
-  }
-  deleteValue(value: PrimitiveValue) {
-    this.listeners.forEach(m => m.deleteValue(value));
-  }
-  createLinkToNode(sourceId: PrimitiveValue, label: string, targetId: PrimitiveValue) {
-    this.listeners.forEach(m => m.createLinkToNode(sourceId, label, targetId));
-  }
-  createLinkToValue(sourceId: PrimitiveValue, label: string, targetValue: PrimitiveValue) {
-    this.listeners.forEach(m => m.createLinkToValue(sourceId, label, targetValue));
-  }
-  deleteLink(sourceId: PrimitiveValue, label: string) {
-    this.listeners.forEach(m => m.deleteLink(sourceId, label));
-  }
-}
-
-export class DummyListener implements GraphStateListener {
-  createNode(ns: NodeState) {}
-  createValue(vs: ValueState) {}
-  deleteNode(id: PrimitiveValue) {}
-  deleteValue(value: PrimitiveValue) {}
-  createLinkToNode(sourceId: PrimitiveValue, label: string, targetId: PrimitiveValue) {}
-  createLinkToValue(sourceId: PrimitiveValue, label: string, targetValue: PrimitiveValue) {}
-  deleteLink(sourceId: PrimitiveValue, label: string) {}
-}
-
-const DUMMY = new DummyListener();
-
-// Helper
-function removeFromArray<T>(arr: T[], cb: (elem: T) => boolean) {
-  arr.splice(arr.findIndex(cb), 1);
-}
-
-
-abstract class Common {
-  // For every currently incoming edge, the label of the edge, the delta that set the edge, and the source of the edge.
-  readonly currentlyIncoming: Array<[string, EdgeUpdate, NodeState]> = [];
-  // For every previously incoming edge, the delta that set the edge to point somewhere else.
-  readonly previouslyIncoming: Set<EdgeUpdate> = new Set();
-
-  addIncoming(label: string, delta: EdgeUpdate, srcState: NodeState, listener: GraphStateListener) {
-    this.currentlyIncoming.push([label, delta, srcState]);
-  }
-  noLongerIncoming(overwritten: EdgeUpdate, overwriter: EdgeUpdate, listener: GraphStateListener) {
-    removeFromArray(this.currentlyIncoming, ([_,d]) => d === overwritten);
-    this.previouslyIncoming.add(overwriter);
-  }
-  // only called when undoing a Delta
-  unAddIncoming(delta: EdgeUpdate, listener: GraphStateListener) {
-    removeFromArray(this.currentlyIncoming, ([_,d]) => d === delta);
-  }
-  // only called when undoing a Delta
-  incomingAgain(label: string, unOverwritten: EdgeUpdate, srcState: NodeState, unOverwriter: EdgeUpdate, listener: GraphStateListener) {
-    this.currentlyIncoming.push([label, unOverwritten, srcState]);
-    this.previouslyIncoming.delete(unOverwriter);
-  }
-
-  getIncomingEdges(): [string, INodeState][] {
-    return this.currentlyIncoming.map(([label, _, srcState]) => [label, srcState]);
-  }
-
-  // pure
-  abstract isNode(uuid: PrimitiveValue): boolean;
-  abstract isValue(value: PrimitiveValue): boolean;
-  abstract asTarget(): NodeCreation | PrimitiveValue;
-
-  // will notify 'listener'
-  abstract createLinkTo(sourceId: PrimitiveValue, label: string, listener: GraphStateListener): void;
-
-  // pure
-  abstract getDeltaForSetEdge(registry: DeltaRegistry, label: string, target: NodeCreation | PrimitiveValue, reads?: ExistingEdge[]): EdgeUpdate;
-  abstract getDeltasForDelete(registry: DeltaRegistry): (EdgeUpdate|NodeDeletion)[];
-
-  getReads(label: string): EdgeUpdate[] {
-    return [];
-  }
-
-  // idempotent - may create some new deltas but does not execute them
-  getIncomingEdgeDependenciesForDelete(registry: DeltaRegistry): [EdgeUpdate[], EdgeUpdate[]]{
-    const newDeltas = this.currentlyIncoming.map(([label, u, ns]) => {
-      return registry.newEdgeUpdate(u.overwrite(), null, [], ns.getReads(label).slice());
-    });
-
-    return [[...this.previouslyIncoming].concat(newDeltas), newDeltas];
-  }
-}
-
-// These interfaces allow us to pass around NodeState and ValueState objects while not exposing the stuff that is supposed to remain internal wrt. this module.
-
-interface ICommon {  
-  // array of (label, NodeState) pairs
-  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;
-}
-
-// 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 implements INodeState {
-  readonly type = "node";
-
-  readonly creation: NodeCreation;
-
-  // For every outgoing edge, the Delta that set this edge to its current value
-  readonly outgoingDeltas: Map<string, EdgeUpdate> = 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!
-  readonly outgoingReads: Map<string, EdgeUpdate[]> = new Map();
-
-  // All currently outgoing edges. Edges that were set to null will not be part of this mapping.
-  readonly outgoing: Map<string, IValueState | INodeState> = new Map();
-
-  isDeleted: boolean = false; // has the node been deleted?
-
-  constructor(creation: NodeCreation) {
-    super();
-    this.creation = creation;
-  }
-
-  isNode(uuid: PrimitiveValue): boolean {
-    return this.creation.id == uuid;
-  }
-
-  isValue(value: PrimitiveValue): boolean {
-    return false;
-  }
-
-  asTarget() {
-    return this.creation;
-  }
-
-  createLinkTo(sourceId: PrimitiveValue, label: string, listener: GraphStateListener) {
-    listener.createLinkToNode(sourceId, label, this.creation.id);
-  }
-
-  getReads(label: string): EdgeUpdate[] {
-    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: DeltaRegistry, label: string, target: NodeCreation | PrimitiveValue, reads: readonly ExistingEdge[] = []): EdgeUpdate {
-    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: DeltaRegistry): (EdgeUpdate|NodeDeletion)[] {
-    // 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 implements IValueState {
-  readonly type = "value";
-
-  shown: boolean = false; // does a (visible) node currently exist for this value?
-
-  readonly value: PrimitiveValue;
-
-  constructor(value: PrimitiveValue) {
-    super();
-    this.value = value;
-  }
-
-  isNode(uuid: PrimitiveValue) {
-    return false;
-  }
-
-  isValue(value: PrimitiveValue) {
-    return this.value === value;
-  }
-
-  asTarget() {
-    return this.value;
-  }
-
-  createLinkTo(sourceId: PrimitiveValue, label: string, listener: GraphStateListener) {
-    listener.createLinkToValue(sourceId, label, this.value);
-  }
-
-  addIncoming(label: string, delta: EdgeUpdate, srcState: NodeState, listener: GraphStateListener) {
-    super.addIncoming(label, delta, srcState, listener);
-    this._showOrHide(listener);
-  }
-  noLongerIncoming(overwritten: EdgeUpdate, overwriter: EdgeUpdate, listener: GraphStateListener) {
-    super.noLongerIncoming(overwritten, overwriter, listener);
-    this._showOrHide(listener);
-  }
-  // only called when undoing a Delta
-  unAddIncoming(delta: EdgeUpdate, listener: GraphStateListener) {
-    super.unAddIncoming(delta, listener);
-    this._showOrHide(listener);
-  }
-  // only called when undoing a Delta
-  incomingAgain(label: string, unOverwritten: EdgeUpdate, srcState: NodeState, unOverwriter: EdgeUpdate, listener: GraphStateListener) {
-    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'.
-  private _showOrHide(listener: GraphStateListener) {
-    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: DeltaRegistry, label: string, target: NodeCreation | PrimitiveValue, reads: ExistingEdge[] = []): EdgeUpdate {
-    // 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: DeltaRegistry): (EdgeUpdate|NodeDeletion)[] {
-    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.
-export class GraphState {
-
-  readonly nodes: Map<PrimitiveValue, NodeState> = new Map();
-  readonly values: Map<PrimitiveValue, ValueState> = new Map();
-
-  private deltasSinceCheckpoint: Array<Array<Delta>> = [];
-
-  // Deltas that are part of current state
-  readonly deltas: Set<Delta> = 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(): Array<Delta> {
-    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: Delta, listener: GraphStateListener = DUMMY) {
-    if (delta instanceof NodeCreation) {
-      this.execNodeCreation(delta, listener);
-    }
-    else if (delta instanceof NodeDeletion) {
-      this.execNodeDeletion(delta, listener);
-    }
-    else if (delta instanceof EdgeUpdate) {
-      this.execEdgeUpdate(delta, listener);
-    }
-    else if (delta instanceof Transaction) {
-      delta.deltas.forEach(d => this.exec(d, listener));
-    }
-    else {
-      throw new Error("Assertion failed: Unexpected delta type");
-    }
-    this.deltasSinceCheckpoint.at(-1)?.push(delta);
-    this.deltas.add(delta);
-  }
-  unexec(delta: Delta, listener: GraphStateListener = DUMMY) {
-    this.deltas.delete(delta);
-    if (delta instanceof NodeCreation) {
-      this.unexecNodeCreation(delta, listener);
-    }
-    else if (delta instanceof NodeDeletion) {
-      this.unexecNodeDeletion(delta, listener);
-    }
-    else if (delta instanceof EdgeUpdate) {
-      this.unexecEdgeUpdate(delta, listener);
-    }
-    else if (delta instanceof 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");
-      }
-    }
-  }
-
-  private _getEdgeTargetState(target: Target): NodeState | ValueState | undefined {
-    if (target instanceof TargetNode) {
-      return this.nodes.get(target.value.id); // may return undefined
-    }
-    else if (target instanceof TargetValue && target.value !== null) {
-      return this.getValueState(target.value);
-    }
-  }
-  private getValueState(value: PrimitiveValue): ValueState {
-    let vs = this.values.get(value);
-    if (vs === undefined) {
-      vs = new ValueState(value);
-      this.values.set(value, vs);
-    }
-    return vs;
-  }
-
-  private execNodeCreation(delta: NodeCreation, listener: GraphStateListener) {
-    // console.log("execNodeCreation", delta)
-    const nodeState = new NodeState(delta);
-    this.nodes.set(delta.id, nodeState);
-    listener.createNode(nodeState);
-  }
-  private unexecNodeCreation(delta: NodeCreation, listener: GraphStateListener) {
-    // console.log("unexecNodeCreation", delta)
-    this.nodes.delete(delta.id);
-    listener.deleteNode(delta.id);
-  }
-
-  private execNodeDeletion(delta: NodeDeletion, listener: GraphStateListener) {
-    // 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);
-  }
-  private unexecNodeDeletion(delta: NodeDeletion, listener: GraphStateListener) {
-    // 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);
-  }
-
-  private execEdgeUpdate(delta: EdgeUpdate, listener: GraphStateListener) {
-    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 NewEdge) {
-      // Nothing was overwritten
-    }
-    else if (edge instanceof 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);
-    }
-  }
-  private unexecEdgeUpdate(delta: EdgeUpdate, listener: GraphStateListener) {
-    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 NewEdge) {
-      // Nothing was overwritten
-      sourceState.outgoingDeltas.delete(label);
-      sourceState.outgoing.delete(label);
-    }
-    else if (edge instanceof 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);
-    }
-  }
-}

+ 0 - 206
src/onion/legacy/delta.ts.legacy

@@ -1,206 +0,0 @@
-import {inspect} from "util"; // NodeJS library
-
-export abstract class Delta {
-  // Get deltas that this delta depends on.
-  abstract getDependencies(): Array<Delta>;
-
-  // Get deltas that this delta depends on + the type of each dependency.
-  abstract getTypedDependencies(): Array<[Delta, string]>;
-
-  // Get deltas that are conflicting with this delta.
-  abstract getConflicts(): Array<Delta>;
-
-  // Get an ID that is unique to the VALUE (attributes and dependencies) of this Delta.
-  // Meaning: if two deltas are identical (they do exactly the same thing, and they have the exact same dependencies), they have the same ID.
-  // Returned value must be 8 bytes (256 bits). SHA-256 hashing is used internally.
-  abstract getHash(): Buffer;
-
-  // A short text string that summarizes the delta.
-  // For visualization purposes.
-  abstract getDescription(): string;
-
-  // Result must survive a JSON round-trip.
-  abstract serialize(): any;
-
-  // Get all primitive deltas that this delta consists of.
-  abstract iterPrimitiveDeltas(): Iterable<PrimitiveDelta>;
-
-  abstract getLevel(): number;
-
-  // Inverse dependency
-  partOf: Array<Transaction> = []; // append-only
-}
-
-// All non-Transaction types of Delta
-export abstract class PrimitiveDelta extends Delta {
-}
-
-export function isConflicting(a: Delta, b: Delta) {
-  return a.conflictsWith.includes(b);
-}
-
-// Yields all elements of 'it' that are conflicting with 'd'.
-export function* iterConflicts(d: Delta, it: Iterable<Delta>) {
-  for (const conflictsWith of d.conflictsWith) {
-    for (const otherDelta of it) {
-      if (conflictsWith === otherDelta) {
-        yield conflictsWith;
-      }
-    }
-  }
-}
-
-export function* iterMissingDependencies(d: Delta, it: Iterable<Delta>) {
-  for (const dep of d.getDependencies()) {
-    let found = false;
-    for (const otherDelta of it) {
-      if (dep === otherDelta) {
-        found = true;
-        break;
-      }
-    }
-    if (!found) {
-      yield dep;
-    }
-  }
-}
-
-
-export class Transaction extends Delta {
-  // Dependencies
-  deltas: Array<Delta>;
-  dependencies: Array<Transaction>;
-
-  conflicts: Array<Transaction>;
-
-  // Inverse dependencies
-  inverseDependencies: Array<Transaction> = []; // append-only
-
-  readonly hash: Buffer;
-  readonly description: string;
-  readonly lvl: number;
-
-  constructor(hash: Buffer, deltas: Array<Delta>, dependencies: Array<Transaction>, description: string) {
-    super();
-    this.hash = hash;
-    this.deltas = deltas;
-    this.dependencies = dependencies;
-    this.description = description;
-
-    // Figure out lvl
-    let maxLvl = 0;
-    for (const d of deltas) {
-      maxLvl = Math.max(maxLvl, d.getLevel());
-    }
-    this.lvl = maxLvl + 1;
-
-    // Figure out conflicts
-    this.conflicts = [];
-    for (const delta of deltas) {
-      for (const conflictingDelta of delta.conflictsWith) {
-        if (deltas.includes(conflictingDelta)) {
-          // console.log("Conflict between", conflictingDelta, "and", delta);
-          throw new Error("Cannot create a composite delta out of conflicting deltas");
-        }
-        const conflictingTxs = conflictingDelta.partOf;
-        for (const otherTx of conflictingTxs) {
-          if (!this.conflicts.includes(otherTx)) {
-            this.conflicts.push(otherTx);
-            otherTx.conflicts.push(this);
-          }
-        }
-      }
-    }
-
-    if (dependencies.some(dependency => this.conflicts.includes(dependency))) {
-      throw new Error("Assertion failed: Transaction depends on another conflicting transaction.");
-    }
-
-    // Inverse dependencies
-    for (const delta of deltas) {
-      delta.partOf.push(this);
-    }
-    for (const dep of dependencies) {
-      dep.inverseDependencies.push(this);
-    }
-  }
-
-  getDependencies(): Array<Delta> {
-    return this.dependencies;
-    // return this.deltas.concat(this.dependencies);
-  }
-
-  getTypedDependencies(): Array<[Delta, string]> {
-    return this.dependencies.map(d => [d, ""] as [Delta,string]);
-    // return this.deltas.map(d => [d, "TX"] as [Delta,string])
-    //   .concat(this.dependencies.map(d => [d, ""] as [Delta,string]));
-  }
-
-  getConflicts(): Array<Delta> {
-    return [];
-  }
-
-  getHash(): Buffer {
-    return this.hash;
-  }
-
-  getDescription(): string {
-    return this.description;
-  }
-
-  // pretty print to console under NodeJS
-  [inspect.custom](depth: number, options: object) {
-    return "Tx(" + this.deltas.map(d => d[inspect.custom](0, options)).join(',') + ")";
-  }
-
-  toString(): string {
-    return this[inspect.custom](0, {});
-  }
-
-  serialize(): any {
-    return {
-      hash: this.hash.toString('base64'),
-      type: "Transaction",
-      description: this.description,
-      deltas: this.deltas.map(d => d.getHash().toString('base64')),
-      dependencies: this.dependencies.map(d => d.getHash().toString('base64')),
-    }
-  }
-
-  *iterPrimitiveDeltas(): Iterable<Delta> {
-    for (const d of this.deltas) {
-      yield* d.iterPrimitiveDeltas();
-    }
-  }
-
-  getLevel() {
-    return this.lvl;
-  }
-}
-
-// 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.
-export function findTxDependencies(deltas: Array<Delta>, candidates?: Set<Delta>): Array<Transaction> {
-  const dependencies: Array<Transaction> = [];
-  for (const delta of deltas) {
-    for (const dependency of delta.getDependencies()) {
-      if (!deltas.includes(dependency)) {
-        const txs = dependency.partOf;
-        const filteredTxs = candidates !== undefined ?
-           txs.filter(d => candidates!.has(d)) : txs;
-        if (txs.length > 1) {
-          throw new Error("Error: One of the composite's dependencies is contained by multiple composites.");
-        }
-        if (txs.length === 0) {
-          // throw new Error("Assertion failed: delta " + delta.description + " depends on " + dependency.description + " but this dependency could not be found in a composite.");
-          continue;
-        }
-        const [tx] = filteredTxs;
-        if (!dependencies.includes(tx)) {
-          dependencies.push(tx);
-        }
-      }
-    }
-  }
-  return dependencies;
-}

+ 0 - 75
src/onion/legacy/delta_parser.ts.legacy

@@ -1,75 +0,0 @@
-import {Delta, PrimitiveDelta} from "./delta";
-
-import {
-  NodeCreation,
-  NodeDeletion,
-  EdgeCreation,
-  EdgeUpdate,
-  EdgeTargetType,
-} from "./delta";
-
-import {DeltaRegistry} from "./delta_registry";
-
-
-import {UUID} from "./types";
-
-export class DeltaParser {
-  readonly deltaRegistry: DeltaRegistry;
-
-  constructor(deltaRegistry) {
-    this.deltaRegistry = deltaRegistry;
-  }
-
-  private getDependency<T>(hash): T {
-    const result = this.deltaRegistry.deltas.get(hash);
-    if (result === undefined) throw new Error("Could not dependency: " + hash);
-    return result as T;
-  }
-
-  loadEdgeTarget({type, ...rest}): EdgeTargetType {
-    if (type === "value") {
-      const {value} = rest;
-      return value;
-    }
-    if (type === "node") {
-      const {creation} = rest;
-      return this.getDependency<NodeCreation>(creation);
-    }
-    throw new Error("Unknown edge target type: " + type);
-  }
-
-  loadDelta({type, ...rest}): Delta {
-    if (type === "NodeCreation") {
-      const {id} = rest;
-      return this.deltaRegistry.newNodeCreation(new UUID(id));
-    }
-    if (type === "EdgeCreation") {
-      const {source, label, target} = rest;
-      return this.deltaRegistry.newEdgeCreation(
-        this.getDependency<NodeCreation>(source),
-        label,
-        this.loadEdgeTarget(target));
-    }
-    if (type === "EdgeUpdate") {
-      const {overwrites, target} = rest;
-      return this.deltaRegistry.newEdgeUpdate(
-        this.getDependency<EdgeCreation|EdgeUpdate>(overwrites),
-        this.loadEdgeTarget(target));
-    }
-    if (type === "NodeDeletion") {
-      const {creation, deletedOutgoingEdges, afterIncomingEdges} = rest;
-      return this.deltaRegistry.newNodeDeletion(
-        this.getDependency<NodeCreation>(creation),
-        deletedOutgoingEdges.map(d => this.getDependency<EdgeCreation|EdgeUpdate>(d)),
-        afterIncomingEdges.map(d => this.getDependency<EdgeUpdate|NodeDeletion>(d)));
-    }
-    if (type === "Transaction") {
-      const {type, deltas, dependencies, description} = rest;
-      return this.deltaRegistry.newTransaction(
-        deltas.map(d => this.getDependency<Delta>(d)),
-        description,
-        dependencies.map(d => this.getDependency<Delta>(d)));
-    }
-    throw new Error("Unknown delta type: " + type);
-  }
-}

+ 0 - 82
src/onion/legacy/delta_registry.ts.legacy

@@ -1,82 +0,0 @@
-import {Delta, Transaction, findTxDependencies} from "./delta";
-import {NodeCreation, NodeDeletion, EdgeCreation, EdgeUpdate, EdgeTargetType} from "./delta";
-import {UUID} from "./types";
-import {createHash} from "crypto";
-import {Buffer} from "buffer";
-import {bufferXOR} from "./buffer_xor";
-
-// Ensures that deltas with the same hash are only created once.
-export class DeltaRegistry {
-  deltas: Map<string, Delta> = new Map();
-
-  // Given the expected hash 
-  private createIdempotent<T extends Delta>(hash: Buffer, callback: () => T): T {
-    const base64 = hash.toString('base64');
-    return this.deltas.get(base64) as T || (() => {
-      const delta = callback();
-      this.deltas.set(base64, delta);
-      return delta;
-    })();
-  }
-
-  newNodeCreation(id: UUID): NodeCreation {
-    const hash = createHash('sha256')
-      .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 NodeCreation(hash, id));
-  }
-
-  newNodeDeletion(creation: NodeCreation, deletedOutgoingEdges: Array<EdgeCreation|EdgeUpdate>, afterIncomingEdges: Array<EdgeUpdate|NodeDeletion>): NodeDeletion {
-    // hash will be calculated based on all EdgeCreations/EdgeUpdates/NodeDeletions that we depend on, by XOR (insensitive to array order).
-    let hash = createHash('sha256').update(creation.hash);
-    let union = Buffer.alloc(32); // all zeroes - neutral element for XOR
-    for (const deletedOutgoing of deletedOutgoingEdges) {
-      union = bufferXOR(union, deletedOutgoing.getHash());
-    }
-    for (const afterIncoming of afterIncomingEdges) {
-      union = bufferXOR(union, afterIncoming.getHash());
-    }
-    const buf = hash.update(union).digest();
-    return this.createIdempotent(buf, () => new NodeDeletion(buf, creation, deletedOutgoingEdges, afterIncomingEdges));
-  }
-
-  newEdgeCreation(source: NodeCreation, label: string, target: EdgeTargetType): EdgeCreation {
-    const hash = createHash('sha256')
-      .update(source.hash)
-      .update('create').update(label)
-      .update('target=').update(targetToHash(target))
-      .digest();
-    return this.createIdempotent(hash, () => new EdgeCreation(hash, source, label, target));
-  }
-
-  newEdgeUpdate(overwrites: EdgeCreation | EdgeUpdate, target: EdgeTargetType): EdgeUpdate {
-    const hash = createHash('sha256')
-      .update(overwrites.hash)
-      .update('target=').update(targetToHash(target))
-      .digest();
-    return this.createIdempotent(hash, () => new EdgeUpdate(hash, overwrites, target));
-  }
-
-  newTransaction(deltas: Array<Delta>, description: string, dependencies: Array<Transaction> = findTxDependencies(deltas)): Transaction {
-    // XOR of hashes of deltas
-    const xor = deltas.reduce((buf, delta) => bufferXOR(delta.getHash(), buf), Buffer.alloc(32));
-    let hash = createHash('sha256')
-      .update('tx')
-      .update(xor);
-    for (const d of dependencies) {
-      hash = hash.update(d.hash);
-    }
-    const buf = hash.digest();
-    return this.createIdempotent(buf, () => new Transaction(buf, deltas, dependencies, description));
-  }
-}
-
-function targetToHash(target: EdgeTargetType): any {
-  // just needs to return something "unique" to feed into the hash function
-  if (target instanceof NodeCreation) {
-    return target.hash;
-  }
-  else {
-    return "value" + JSON.stringify(target);
-  }
-}

+ 0 - 635
src/onion/legacy/primitive_delta.ts.legacy

@@ -1,635 +0,0 @@
-import * as _ from "lodash";
-import {inspect} from "util";
-import {UUID, PrimitiveValue} from "./types";
-import {Delta, PrimitiveDelta} from "./delta";
-
-export class NodeCreation extends Delta {
-  readonly id: UUID;
-  readonly hash: Buffer;
-  readonly description: string;
-
-  // Inverse dependency: Deletions of this node.
-  deletions: Array<NodeDeletion> = []; // append-only
-
-  // Inverse dependency: Creation outgoing edges.
-  outgoingEdges: Array<EdgeCreation> = []; // append-only
-
-  // Inverse dependency: All the times this node was the target of an edge update.
-  incomingEdges: Array<EdgeCreation | EdgeUpdate> = []; // append-only
-
-  constructor(hash: Buffer, id: UUID) {
-    super();
-    this.hash = hash;
-    this.id = id;
-    this.description = "NEW("+this.id.toString().slice(0,8)+")";
-  }
-
-  getDependencies(): [] {
-    return [];
-  }
-
-  getTypedDependencies(): [] {
-    return [];
-  }
-
-  getConflicts(): [] {
-    return [];
-  }
-
-  getHash(): Buffer {
-    return this.hash;
-  }
-
-  getDescription(): string {
-    return this.description;
-  }
-
-  // pretty print to console under NodeJS
-  [inspect.custom](depth: number, options: object) {
-    return "NodeCreation{" + inspect(this.id, options) + "}";
-  }
-
-  toString(): string {
-    return this[inspect.custom](0, {});
-  }
-
-  serialize(): any {
-    return {
-      hash: this.hash.toString('base64'),
-      type: "NodeCreation",
-      id: this.id,
-    }
-  }
-
-  *iterPrimitiveDeltas(): Iterable<NodeCreation> {
-    yield this;
-  }
-
-  getLevel() {
-    return 0;
-  }
-}
-
-export class NodeDeletion extends Delta {
-  readonly hash: Buffer;
-  readonly description: string;
-
-  // Dependency: The node being deleted.
-  readonly creation: NodeCreation;
-
-  // Dependency: All outgoing edges of the deleted node must be deleted also.
-  readonly deletedOutgoingEdges: Array<EdgeCreation | EdgeUpdate>;
-
-  // Dependency: For every time the deleted node was target of an edge, the deletion depends on the EdgeUpdate that sets this edge to have a different target.
-  readonly afterIncomingEdges: Array<EdgeUpdate | NodeDeletion>;
-
-  // Conflicts: Concurrent deletion of the same node.
-  deleteConflicts: Array<NodeDeletion> = [];
-
-  // Conflicts: Concurrent creation of an edge with as source the deleted node.
-  edgeSourceConflicts: Array<EdgeCreation> = [];
-
-  // Conflicts: Concurrent update and deletion of an edge (because its source node is deleted).
-  updateConflicts: Array<EdgeUpdate | NodeDeletion> = [];
-
-  // Conflicts: Concurrent creation/update of an edge with as target the deleted node.
-  edgeTargetConflicts: Array<EdgeCreation | EdgeUpdate> = [];
-
-  // Parameters:
-  //   deletedOutgoingEdges: For every outgoing edge of this node being deleted, must explicitly specify the most recent EdgeCreation/EdgeUpdate on this edge, to make it explicit that this deletion happens AFTER the EdgeCreation/EdgeUpdate (instead of concurrently, which is a conflict).
-  //   afterIncomingEdges: For every edge that is or was (once) incoming to this node, must explicitly specify an EdgeUpdate/NodeDeletion that makes this edge point somewhere else (no longer to this node).
-  constructor(hash: Buffer, creation: NodeCreation, deletedOutgoingEdges: Array<EdgeCreation|EdgeUpdate>, afterIncomingEdges: Array<EdgeUpdate|NodeDeletion>) {
-    super();
-    this.hash = hash;
-    this.creation = creation;
-    this.deletedOutgoingEdges = deletedOutgoingEdges;
-    this.afterIncomingEdges = afterIncomingEdges;
-
-    // Check some assertions
-    if (_.uniq(deletedOutgoingEdges).length !== deletedOutgoingEdges.length) {
-      throw new Error("Assertion failed: deletedOutgoingEdges contains duplicates.");
-    }
-    if (_.uniq(afterIncomingEdges).length !== afterIncomingEdges.length) {
-      throw new Error("Assertion failed: deletedOutgoingEdges contains duplicates.");
-    }
-    for (const supposedlyOutgoingEdge of this.deletedOutgoingEdges) {
-      if (supposedlyOutgoingEdge.getCreation().source !== this.creation) {
-        throw new Error("Assertion failed: Every element of delOutgoings must be an EdgeCreation or EdgeUpdate of an outgoing edge of the deleted node.")
-      }
-    }
-    for (const supposedlyIncomingEdge of this.afterIncomingEdges) {
-      if (supposedlyIncomingEdge instanceof NodeDeletion) {
-        // should check if the NodeDeletion deletes an edge that was *once* 
-        let isReallyIncomingEdge = false;
-        for (const deletedEdge of supposedlyIncomingEdge.deletedOutgoingEdges) {
-          let current: EdgeCreation|EdgeUpdate|undefined = deletedEdge;
-          while (current !== undefined) {
-            if (current.target.getTarget() === this.creation) {
-              isReallyIncomingEdge = true;
-              break;
-            }
-            if (current instanceof EdgeUpdate) current = current.overwrites
-            else break;
-          }
-          if (isReallyIncomingEdge) break;
-        }
-        if (!isReallyIncomingEdge) {
-          throw new Error("Assertion failed: NodeDeletion in afterIncomingEdges does not delete an incoming edge.");
-        }
-      }
-      else {      
-        if (supposedlyIncomingEdge.target.getTarget() === this.creation) {
-          throw new Error("Assertion failed: Every element in afterIncomingEdges MUST set the target of the edge to SOME PLACE ELSE.");
-        }
-        if (![...supposedlyIncomingEdge.iterUpdates()].some(e => e.target.getTarget() === this.creation)) {
-          throw new Error("Assertion failed: None of the EdgeUpdates of supposedlyIncomingEdge set the target to the node being deleted.");
-        }
-      }
-    }
-
-    this.description = "DEL("+this.creation.id.toString().slice(0,8)+")"
-
-    // Detect conflicts
-
-    // Delete/delete
-    for (const concurrentDeletion of this.creation.deletions) {
-      // Symmetric:
-      this.deleteConflicts.push(concurrentDeletion);
-      concurrentDeletion.deleteConflicts.push(this);
-    }
-
-    // Concurrently created outgoing edges of this node
-    for (const outgoingEdgeCreation of this.creation.outgoingEdges) {
-      if (!this.deletedOutgoingEdges.some(edge => edge.getCreation() === outgoingEdgeCreation)) {
-        // Conflict: The deleted node has an outgoing edge that this deletion does not depend on.
-        // Symmetric
-        this.edgeSourceConflicts.push(outgoingEdgeCreation);
-        outgoingEdgeCreation.deleteSourceConflicts.push(this);
-      }
-    }
-
-    // Related to previous conflict type: Concurrent edge updates
-    for (const deletedEdge of this.deletedOutgoingEdges) {
-      for (const concurrentEdgeUpdate of deletedEdge.overwrittenBy) {
-        if (this.afterIncomingEdges.includes(concurrentEdgeUpdate)) {
-          // This is a special case that can occur when a node with a self-edge is deleted.
-          // Not a conflict.
-        }
-        else {
-          // Conflict: Edge concurrently updated and deleted.
-          // Symmetric
-          this.updateConflicts.push(concurrentEdgeUpdate);
-          concurrentEdgeUpdate.updateConflicts.push(this);
-        }
-      }
-    }
-
-    function overwritesEdge(op: EdgeCreation | EdgeUpdate | NodeDeletion, edge: EdgeUpdate | EdgeCreation) {
-      if (op === edge) {
-        return true;
-      }
-      if (op instanceof EdgeUpdate) {
-        return overwritesEdge(op.overwrites, edge);
-      }
-      if (op instanceof NodeDeletion) {
-        return op.deletedOutgoingEdges.some(deletedEdge => overwritesEdge(deletedEdge, edge));
-      }
-      return false;
-    }
-
-    // Concurrently updated incoming edges of this node
-    for (const incomingEdge of this.creation.incomingEdges) {
-      // every incoming edge of deleted node must have been overwritten by an EdgeUpdate that is an explicit dependency:
-      if (!this.afterIncomingEdges.some(edge => overwritesEdge(edge, incomingEdge))) {
-        // Symmetric
-        this.edgeTargetConflicts.push(incomingEdge);
-        incomingEdge.target.addDeleteTargetConflict(this);
-      }
-    }
-
-
-    // Create inverse dependencies
-
-    this.creation.deletions.push(this);
-
-    for (const deletedEdge of this.deletedOutgoingEdges) {
-      // NodeDeletion acts a bit as an EdgeUpdate here
-      deletedEdge.overwrittenBy.push(this);
-    }
-  }
-
-  getDependencies(): Array<PrimitiveDelta> {
-    return Array<Delta>().concat(
-      [this.creation],
-      this.deletedOutgoingEdges,
-      this.afterIncomingEdges,
-    );
-  }
-
-  getTypedDependencies(): Array<[PrimitiveDelta, string]> {
-    return Array<[PrimitiveDelta, string]>().concat(
-      [[this.creation, "DEL"]],
-      this.deletedOutgoingEdges.map(edge => ([edge, "D"])),
-      this.afterIncomingEdges.map(edge => ([edge, "A"])),
-    );
-  }
-
-  getConflicts(): Array<PrimitiveDelta> {
-    return Array<PrimitiveDelta>().concat(
-      this.deleteConflicts,
-      this.edgeSourceConflicts,
-      this.edgeTargetConflicts,
-      this.updateConflicts,
-    );
-  }
-
-  getHash(): Buffer {
-    return this.hash;
-  }
-
-  getDescription(): string {
-    return this.description;
-  }
-
-  // pretty print to console under NodeJS
-  [inspect.custom](depth: number, options: object) {
-    return "NodeDeletion{" + inspect(this.creation.id, options) + ",delEdges=" + this.deletedOutgoingEdges.map(e => inspect(e, options)).join(",") + ",after=" + this.afterIncomingEdges.map(e => inspect(e, options)).join(",") + "}";
-  }
-
-  toString(): string {
-    return this[inspect.custom](0, {});
-  }
-
-  serialize(): any {
-    return {
-      hash: this.hash.toString('base64'),
-      type: "NodeDeletion",
-      creation: this.creation.hash.toString('base64'),
-      deletedOutgoingEdges: this.deletedOutgoingEdges.map(d => d.hash.toString('base64')),
-      afterIncomingEdges: this.afterIncomingEdges.map(d => d.hash.toString('base64')),
-    };
-  }
-
-  *iterPrimitiveDeltas(): Iterable<Delta> {
-    yield this;
-  }
-
-  getLevel() {
-    return 0;
-  }
-}
-
-// Target of an edge can be: another node, nothing (edge doesn't exist) or a value (i.e., string, number or boolean)
-export type EdgeTargetType = NodeCreation | null | PrimitiveValue;
-
-// Target of an edge can be either: (1) another node, (2) a value or (3) null (hides the edge - initially all edges are assumed to be null).
-export interface SetsTarget {
-  getTarget(): EdgeTargetType;
-  addDeleteTargetConflict(nodeDeletion: NodeDeletion);
-  getDeleteTargetConflicts(): ReadonlyArray<NodeDeletion>;
-  getDependencies(): ReadonlyArray<NodeCreation>;
-  getTypedDependencies(): ReadonlyArray<[NodeCreation, string]>;
-  getHash(): Buffer;
-  serialize(): any;
-}
-
-// Common functionality in EdgeCreation and EdgeUpdate: both set the target of an edge, and this can conflict with the deletion of the target.
-class SetsTargetToNode implements SetsTarget {
-  // Dependency
-  private readonly targetNode: NodeCreation;
-
-  // Conflict: Concurrent deletion of target node.
-  private deleteTargetConflicts: Array<NodeDeletion> = []; // append-only
-
-  constructor(targetNode: NodeCreation, edgeOperation: EdgeCreation|EdgeUpdate) {
-    this.targetNode = targetNode;
-
-    // Concurrent deletion of target node
-    if (this.targetNode instanceof NodeCreation) {
-      for (const targetDeletion of this.targetNode.deletions) {
-        if (targetDeletion.afterIncomingEdges.some(edge => {
-          while (true) {
-            if (edge === edgeOperation) return true;
-            if (edge instanceof EdgeUpdate && edge.overwrites instanceof EdgeUpdate) edge = edge.overwrites;
-            else return false;
-          }
-        })) {
-          // this can never happen - something is very wrong if you get this error:
-          throw new Error("Assertion failed - did not expect existing deletion to be aware of a new edge update");
-        }
-        // Symmetric
-        this.deleteTargetConflicts.push(targetDeletion);
-        targetDeletion.edgeTargetConflicts.push(edgeOperation);
-      }
-
-      // Create inverse dependency
-      this.targetNode.incomingEdges.push(edgeOperation);
-    }
-  }
-
-  getTarget(): EdgeTargetType {
-    return this.targetNode;
-  }
-  addDeleteTargetConflict(nodeDeletion: NodeDeletion) {
-    this.deleteTargetConflicts.push(nodeDeletion);
-  }
-  getDeleteTargetConflicts(): ReadonlyArray<NodeDeletion> {
-    return this.deleteTargetConflicts;
-  }
-  getDependencies(): ReadonlyArray<NodeCreation> {
-    return [this.targetNode];
-  }
-  getTypedDependencies(): ReadonlyArray<[NodeCreation, string]> {
-    return [[this.targetNode, "TGT"]];
-  }
-  getHash(): Buffer {
-    return this.targetNode.hash;
-  }
-  [inspect.custom](depth: number, options: object) {
-    return inspect(this.targetNode.id, options);
-  }
-  serialize(): any {
-    return {
-      type: "node",
-      creation: this.targetNode.hash.toString('base64'),
-    }
-  }
-}
-class SetsTargetToValue implements SetsTarget {
-  readonly value: PrimitiveValue | null;
-
-  constructor(value: PrimitiveValue | null) {
-    this.value = value;
-  }
-
-  getTarget(): EdgeTargetType {
-    return this.value;
-  }
-  addDeleteTargetConflict(nodeDeletion: NodeDeletion) {
-    throw new Error("Assertion error: SetsTargetToValue cannot be involved in conflict with NodeDeletion");
-  }
-  getDeleteTargetConflicts(): ReadonlyArray<NodeDeletion> {
-    return [];
-  }
-  getDependencies(): ReadonlyArray<NodeCreation> {
-    return [];
-  }
-  getTypedDependencies(): ReadonlyArray<[NodeCreation, string]> {
-    return [];
-  }
-  getHash(): Buffer {
-    return Buffer.from(JSON.stringify(this.value));
-  }
-  [inspect.custom](depth: number, options: object) {
-    return inspect(this.value, options);
-  }
-  serialize(): any {
-    return {
-      type: "value",
-      value: this.value,
-    }
-  }
-}
-
-function makeSetsTarget(target: EdgeTargetType, edgeOperation: EdgeCreation|EdgeUpdate) {
-  if (target instanceof NodeCreation) {
-    return new SetsTargetToNode(target, edgeOperation);
-  } else {
-    return new SetsTargetToValue(target);
-  }
-}
-
-class ReadDependencies {
-  // Dependencies
-  readonly deps: Array<EdgeCreation | EdgeUpdate>;
-
-  // Conflicts: Concurrent read-write.
-  updateConficts: Array<EdgeUpdate> = []; // append-only
-
-  constructor(deps: EdgeCreation | EdgeUpdate) {
-    this.deps = deps;
-
-    for (const d of deps) {
-      for (const o of d.overwrittenBy) {
-        if (d === o) {
-          continue; // a delta cannot conflict with itself
-        }
-        if (o.readDependencies)
-      }
-    }
-  }
-}
-
-export class EdgeCreation extends Delta {
-  // Dependencies
-  readonly source: NodeCreation;
-  readonly label: string;
-  readonly target: SetsTarget;
-
-  readonly hash: Buffer;
-  readonly description: string;
-
-  // Inverse dependency
-  // NodeDeletion if source of edge is deleted.
-  overwrittenBy: Array<EdgeUpdate | NodeDeletion> = []; // append-only
-
-  // Conflicts: Concurrent creations of the same edge.
-  createConflicts: Array<EdgeCreation> = []; // append-only
-
-  // Conflicts: Concurrent deletions of source node.
-  deleteSourceConflicts: Array<NodeDeletion> = []; // append-only
-
-  constructor(hash: Buffer, source: NodeCreation, label: string, target: EdgeTargetType, readDependencies: Array<EdgeCreation | EdgeUpdate>) {
-    super();
-    this.hash = hash;
-    this.source = source;
-    this.label = label;
-    this.target = makeSetsTarget(target, this);
-
-    this.description = "U("+this.label+")";
-
-    // Detect conflicts
-
-    // Create/create
-    for (const outgoingEdge of this.source.outgoingEdges) {
-      if (outgoingEdge.label === this.label) {
-        // Symmetric:
-        this.createConflicts.push(outgoingEdge);
-        outgoingEdge.createConflicts.push(this);
-      }
-    }
-
-    // Concurrent deletions of source node
-    for (const sourceDeletion of this.source.deletions) {
-      if (sourceDeletion.deletedOutgoingEdges.some(edge => edge.getCreation() === this)) {
-        // this can never happen - something is very wrong if you get this error:
-        throw new Error("Assertion failed - did not expect existing deletion to be aware of a new edge creation");
-      }
-      // Symmetric
-      this.deleteSourceConflicts.push(sourceDeletion);
-      sourceDeletion.edgeSourceConflicts.push(this);
-    }
-
-    // Create inverse dependency
-    this.source.outgoingEdges.push(this);
-  }
-
-  // Helper
-  getCreation(): EdgeCreation {
-    return this;
-  }
-
-  getDependencies(): Array<NodeCreation> {
-    return [this.source, ...this.target.getDependencies()];
-  }
-
-  getTypedDependencies(): Array<[NodeCreation, string]> {
-    return [[this.source, "SRC"], ...this.target.getTypedDependencies()];
-  }
-
-  getConflicts(): Array<PrimitiveDelta> {
-    return Array<Delta>().concat(
-      this.createConflicts,
-      this.deleteSourceConflicts,
-      this.target.getDeleteTargetConflicts(),
-    );
-  }
-
-  getHash(): Buffer {
-    return this.hash;
-  }
-
-  getDescription(): string {
-    return this.description;
-  }
-
-  // pretty print to console under NodeJS
-  [inspect.custom](depth: number, options: object) {
-    return "EdgeCreation{src=" + inspect(this.source.id, options) + ",tgt=" + inspect(this.target, options) + ",label=" + this.label + "}";
-  }
-
-  toString(): string {
-    return this[inspect.custom](0, {});
-  }
-
-  serialize(): any {
-    return {
-      hash: this.hash.toString('base64'),
-      type: "EdgeCreation",
-      source: this.source.hash.toString('base64'),
-      label: this.label,
-      target: this.target.serialize(),
-    };
-  }
-
-  *iterPrimitiveDeltas(): Iterable<PrimitiveDelta> {
-    yield this;
-  }
-
-  getLevel() {
-    return 0;
-  }
-}
-
-export class EdgeUpdate extends Delta {
-  // Dependencies
-  readonly overwrites: EdgeCreation | EdgeUpdate;
-  readonly target: SetsTarget;
-
-  readonly hash: Buffer;
-  readonly description: string;
-
-  // Inverse dependency
-  // NodeDeletion if source of edge is deleted.
-  overwrittenBy: Array<EdgeUpdate | NodeDeletion> = []; // append-only
-
-  // Conflicts: Concurrent updates
-  updateConflicts: Array<EdgeUpdate | NodeDeletion> = []; // append-only
-
-  constructor(hash: Buffer, overwrites: EdgeCreation | EdgeUpdate, newTarget: EdgeTargetType) {
-    super();
-    this.hash = hash;
-    this.overwrites = overwrites;
-    this.target = makeSetsTarget(newTarget, this);
-
-    this.description = this.getCreation().description;
-
-    // Detect conflicts
-
-    // Concurrent updates (by EdgeUpdate or NodeDeletion)
-    for (const concurrentUpdate of this.overwrites.overwrittenBy) {
-      // Symmetric
-      this.updateConflicts.push(concurrentUpdate);
-      concurrentUpdate.updateConflicts.push(this);
-    }
-
-    // Create inverse dependency
-    this.overwrites.overwrittenBy.push(this);
-  }
-
-  // Helper
-  getCreation(): EdgeCreation {
-    return this.overwrites.getCreation();
-  }
-
-  getDependencies(): Array<Delta> {
-    return [this.overwrites, ...this.target.getDependencies()];
-  }
-
-  getTypedDependencies(): Array<[Delta, string]> {
-    return [[this.overwrites, "U"], ...this.target.getTypedDependencies()];
-  }
-
-  getConflicts(): Array<Delta> {
-    return Array<Delta>().concat(
-      this.updateConflicts,
-      this.target.getDeleteTargetConflicts(),
-    );
-  }
-
-  getHash(): Buffer {
-    return this.hash;
-  }
-
-  getDescription(): string {
-    return this.description;
-  }
-
-  // pretty print to console under NodeJS
-  [inspect.custom](depth: number, options: object) {
-    return "EdgeUpdate{upd=" + inspect(this.overwrites, options) + ",tgt=" + inspect(this.target, options) + "}";
-  }
-
-  toString(): string {
-    return this[inspect.custom](0, {});
-  }
-
-  serialize(): any {
-    return {
-      hash: this.hash.toString('base64'),
-      type: "EdgeUpdate",
-      overwrites: this.overwrites.hash.toString('base64'),
-      target: this.target.serialize(),
-    };
-  }
-
-  *iterUpdates() {
-    let current: EdgeUpdate | EdgeCreation = this;
-    while (true) {
-      yield current;
-      if (current instanceof EdgeUpdate) {
-        current = current.overwrites;
-      } else {
-        return;
-      }
-    }
-  }
-
-  *iterPrimitiveDeltas(): Iterable<EdgeUpdate> {
-    yield this;
-  }
-
-  getLevel() {
-    return 0;
-  }
-}

+ 0 - 19
src/onion/types.ts

@@ -1,19 +0,0 @@
-import {inspect} from "util"; // NodeJS library 
-
-export type PrimitiveValue = string | number | boolean | null;
-
-export type UUID = string | number | boolean;
-
-// // 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) + "}"
-//   }
-// }

+ 0 - 395
src/onion/version.test.ts

@@ -1,395 +0,0 @@
-import * as _ from "lodash";
-
-import {
-  Version,
-  VersionRegistry,
-  // embed,
-  // overrideDeltas,
-} from "./version";
-
-import {
-  Delta,
-} from "./delta";
-
-import {
-  NodeCreation,
-  NodeDeletion,
-  EdgeUpdate,
-} from "./delta";
-
-import {DeltaRegistry} from "./delta_registry";
-
-import {
-  mockUuid,
-} from "../util/test_helpers";
-
-import {
-  assert,
-  assertThrows,
-} from "../util/assert";
-
-describe("Version", () => {
-
-  it("Get deltas", () => {
-    const deltaRegistry = new DeltaRegistry();
-    const getId = mockUuid();
-    const registry = new VersionRegistry();
-
-    const nodeCreation = deltaRegistry.newNodeCreation(getId());
-    const nodeDeletion = deltaRegistry.newNodeDeletion(nodeCreation, [], []);
-
-    const version1 = registry.createVersion(registry.initialVersion, nodeCreation);
-    const version2 = registry.createVersion(version1, nodeDeletion);
-
-    assert(_.isEqual([... registry.initialVersion], []), "expected initialVersion to be empty");
-    assert(_.isEqual([... version1], [nodeCreation]), "expected version1 to contain creation");
-    assert(_.isEqual([... version2], [nodeDeletion, nodeCreation]), "expected version2 to contain creation and deletion");
-  });
-
-  it("Commutating operations yield equal versions", () => {
-    const deltaRegistry = new DeltaRegistry();
-    const getId = mockUuid();
-    const registry = new 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);
-
-    assert(versionAB === versionBA, "expected versions to be equal");
-  });
-
-  it("Intersection", () => {
-    const deltaRegistry = new DeltaRegistry();
-    const getId = mockUuid();
-    const registry = new 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]);
-    assert(intersection0 === registry.quickVersion([B]), "expected intersection of v1 and v2 to be B.");
-
-    const intersection1 = registry.getIntersection([v1, v1]);
-    assert(intersection1 === v1, "expected intersection of v1 with itself to be v1");
-
-    const intersection2 = registry.getIntersection([v1]);
-    assert(intersection2 === v1, "expected intersection of v1 with itself to be v1");
-
-    const intersection3 = registry.getIntersection([]);
-    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);
-      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 VersionRegistry();
-      const merged = registry.merge([]);
-      assert(merged.length === 1 && merged[0] === registry.initialVersion, "expected intial version");
-
-      mergeAgain(registry, merged);
-    })
-
-    it("Merge non-conflicting versions", () => {
-      const deltaRegistry = new DeltaRegistry();
-      const getId = mockUuid();
-      const registry = new 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)!);
-      assert(merged.length === 1, "expected 1 merged version");
-
-      const deltas = [... merged[0]];
-      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 DeltaRegistry();
-      const getId = mockUuid();
-      const registry = new 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
-      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
-
-      assert(_.isEqual(D.conflictsWith, [[C, 'U/D']]), "Expected D to conflict with C");
-
-      const nameMap: Map<Delta, string> = new Map<Delta,string>([
-        [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)!);
-      assert(merged.length === 3, "expected three maximal versions");
-      assert(merged.includes(registry.quickVersion([A,C,X,Y,Z])), "expected [X,Y,Z,A,C] to be a maximal version");
-      assert(merged.includes(registry.quickVersion([BB,B,C,X,Y,Z])), "expected [X,Y,Z,B,C] to be a maximal version");
-      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 DeltaRegistry();
-      const getId = mockUuid();
-      const registry = new VersionRegistry();
-
-      // Bunch of non-conflicting deltas:
-      const deltas: Array<Delta> = [];
-      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)!);
-      assert(merged.length === 1, "only one merged version should result");
-
-      const mergedAgain = registry.merge(merged, d => nameMap.get(d)!);
-      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 DeltaRegistry();
-      const getId = mockUuid();
-      const registry = new VersionRegistry();
-
-      const HOW_MANY = 3;
-
-      const creations: Array<Delta> = [];
-      const deletions: Array<Delta> = [];
-      const edges: Array<Delta> = [];
-      const versions: Array<Version> = [];
-
-      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)!);
-      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 DeltaRegistry();
-      const getId = mockUuid();
-      const registry = new 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], "");
-
-      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 DeltaRegistry();
-      const getId = mockUuid();
-      const registry = new 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<Delta,string>([
-        [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)!);
-      assert(mergedHosts.length === 1, "expected no host merge conflict");
-
-      console.log("Merging guests...");
-      const mergedGuests = registry.merge([guestA, guestB], d => debugNames.get(d)!);
-      assert(mergedGuests.length === 1, "expected no guest merge conflict");
-
-      const [guestAB] = mergedGuests;
-      const [hostABCD] = mergedHosts;
-
-      const {version: mergedGuest, overridings: mergedOverridings} = hostABCD.getEmbedding("guest");
-      assert(mergedGuest === guestAB, "merged host should embed merged guest");
-
-      assert(mergedOverridings.size === 0, "expected no overridings in merged embedding");
-    });
-
-    it("Multi-level embedding", () => {
-      const deltaRegistry = new DeltaRegistry();
-      const getId = mockUuid();
-      const registry = new 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 = vABCDEF.embeddings.get("L1")?.version;
-
-      assert(vABDE !== undefined,     "No L1 merged version");
-      assert([...vABDE!].length === 2, "L1 merge result unexpected number of deltas: " + [...vABDE!].length);
-
-      const vAD = vABDE!.embeddings.get("L0")?.version;
-
-      assert(vAD !== undefined, "No L0 merged version");
-      assert([...vAD!].length === 2, "L1 merge result unexpected number of deltas: " + [...vAD!].length);
-    });
-
-    it("Self-embedding", () => {
-      const deltaRegistry = new DeltaRegistry();
-      const getId = mockUuid();
-      const registry = new 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]);
-
-      assert(vAB.embeddings.get("self") !== undefined, "Expected merge result to embed itself");
-    })
-  });
-});

+ 0 - 599
src/onion/version.ts

@@ -1,599 +0,0 @@
-import {inspect} from "util"; // NodeJS library
-import { Buffer } from "buffer"; // NodeJS library
-import {findDFS} from "../util/dfs";
-import {permutations} from "../util/permutations";
-import {bufferXOR} from "../util/buffer_xor";
-import {PrimitiveDelta, Transaction} from "./delta";
-
-// import * as _ from "lodash";
-
-import {
-  Delta,
-  // isConflicting,
-  // iterMissingDependencies,
-  // iterConflicts,
-} from "./delta";
-
-// The difference between two consecutive versions.
-type DiffType = Delta;
-
-type OverridingsMap = Map<Delta,Delta>;
-
-export interface Embedding {
-  version: Version; // the embedded ("guest") version
-  overridings: OverridingsMap; // overridden deltas: mapping from guest delta to host delta.
-}
-export type Embeddings = Map<string, Embedding>; // key = a simple identifier for the kind of embedding, e.g., "cs", "as", ...
-
-export type ParentLink = [Version, DiffType];
-export type ChildLink = [Version, DiffType];
-
-// A typed link from one version to another.
-// There are two types: 'p' (parent) and 'c' (child)
-// The link also has a 'value', which is a Delta.
-type PathLink = [('p'|'c'), DiffType, Version];
-
-// not exported -> use VersionRegistry to create versions
-export class Version {
-  readonly parents: Array<ParentLink>;
-  readonly children: Array<ChildLink> = []; // reverse parents
-
-  readonly embeddings: Embeddings; // (guest) versions embedded in this (host) version.
-  readonly reverseEmbeddings: Map<string, Version[]> = new Map(); // (host) versions in which this (guest) version is embedded.
-
-  // Unique ID of the version - XOR of all delta hashes - guarantees that Versions with equal (unordered) sets of Deltas have the same ID.
-  readonly hash: Buffer;
-
-  // Number of deltas that version consists of.
-  readonly size: number;
-
-  // DO NOT USE constructor directly - instead use VersionRegistry.createVersion.
-  constructor(parents: Array<ParentLink>, hash: Buffer, size: number, embeddings: (Version) => Embeddings) {
-    this.parents = parents;
-    this.hash = hash;
-    this.size = size;
-    this.embeddings = embeddings(this);
-  }
-
-  implicitSelfEmbedding: Embedding = {
-    version: this, // every version embeds itself for all undefined embeddings.
-    overridings: new Map(), // no overrides
-  };
-
-  // 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](): Iterator<DiffType> {
-    let current: Version = 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(): Iterable<PrimitiveDelta> {
-    const executionOrder = [...this].reverse();
-    for (const d of executionOrder) {
-      yield* d.iterPrimitiveDeltas();
-    }
-  }
-
-  [inspect.custom](depth: number, options: object): string {
-    return "Version{" + [...this].map(d => inspect(d, options)).join(",") + "}";
-  }
-
-  isSubSetOf(otherVersion: Version): boolean {
-    // current implementation is probably quite slow
-    for (const delta of this) {
-      if (!otherVersion.contains(delta)) {
-        return false;
-      }
-    }
-    return true;
-  }
-
-  contains(delta: Delta): boolean {
-    for (const d of this) {
-      if (d === delta) {
-        return true;
-      }
-    }
-    return false;
-  }
-
-  containsPrimitive(delta: PrimitiveDelta): boolean {
-    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: Version): Array<PathLink> | undefined {
-    const getNeighbors = (v: Version) => {
-      const parentVersions: [PathLink,Version][] = v.parents.map(([parentVersion, delta]) => [['p', delta, parentVersion], parentVersion]);
-      const childVersions: [PathLink,Version][] = 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 = findDFS<Version,PathLink>(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: Version): Array<PathLink> | undefined {
-    const getNeighbors = (v: Version) => {
-      const result: [PathLink,Version][] = v.children.map(([childVersion, delta]) =>
-        [['c', delta, childVersion], childVersion]);
-      return result;
-    };
-    return findDFS<Version,PathLink>(this, otherVersion, getNeighbors);
-  }
-
-  getEmbedding(key: string): Embedding {
-    return this.embeddings.get(key) || this.implicitSelfEmbedding;
-  }
-
-  getReverseEmbeddings(key: string): Version[] {
-    return this.reverseEmbeddings.get(key) || [this];
-  }
-
-  // Serialize a path of Deltas from a Version in alreadyHave, to fully reconstruct this version.
-  serialize(alreadyHave: Set<Version> = new Set()) {
-    const deltas = [];
-    const versions = [];
-    this.serializeInternal(new Set(alreadyHave), new Set<Delta>(), deltas, versions);
-    return {
-      externalDependencies: [...alreadyHave].map(v => v.hash.toString('hex')),
-      deltas,
-      versions,
-    };
-  }
-
-  private serializeInternal(alreadyHaveVersions: Set<Version>, alreadyHaveDeltas: Set<Delta>, deltas, versions) {
-    if (alreadyHaveVersions.has(this)) {
-      return;
-    }
-    alreadyHaveVersions.add(this);
-    const embeddings = new Array<{guestId: string, v: string, ovr: object}>();
-    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,
-      })
-    }
-  }
-}
-
-const initialHash = Buffer.alloc(32); // all zeros
-
-function isConflicting(deltaA: Delta, deltaB: Delta) {
-  // 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);
-  }
-}
-
-export class VersionRegistry {
-  readonly initialVersion: Version = new Version([], initialHash, 0, () => new Map());
-
-  // Maps version ID (as string, because a Buffer cannot be a map key) to Version
-  readonly versionMap: Map<string, Version> = new Map([
-    [initialHash.toString('hex'), this.initialVersion], // the initial version, always already there
-  ]);
-
-  lookupOptional(hash: Buffer): Version | undefined {
-    return this.versionMap.get(hash.toString('hex'));
-  }
-
-  lookup(hash: Buffer): Version {
-    const hex = hash.toString('hex');
-    const version = this.versionMap.get(hex);
-    if (version === undefined) {
-      throw new Error("no such version: " + hex);
-    }
-    return version;
-  }
-
-  private putVersion(hash: Buffer, version: 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: Version, delta: DiffType, embeddings: (Version) => Embeddings = () => new Map()): Version {
-    // 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: Version, delta: DiffType, embeddings: (Version) => Embeddings = () => new Map()): Version {
-    const newHash = 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: Array<DiffType>): Version {
-    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: Array<Version>): Version {
-    // 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: Array<DiffType> = [];
-    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);
-  }
-
-  private getLCAInternal(versionA: Version, versionB: Version): Version {
-    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: Array<Version>): Version {
-    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: Array<Version>, debugNames?: (Delta) => string): Array<Version> {
-
-    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);
-    // printDebug("lca:", ...[...lca].map(d => d.description));
-
-    // Tuple
-    //    Delta: a delta that is at least one of 'versions' but not in 'lca'
-    //    Map: a mapping from all guest-IDs to 'diffs':
-    //          the Delta of the guest that corresponds to the Delta in the host.
-    type DeltaWithEmbedding = [Delta, Map<string, [DeltaWithEmbedding|null, OverridingsMap]>];
-
-    // ATTENTION: Constructing 'diff' must be made recursive (guest could also be a host)
-
-    function visitEmbeddings(currentHost: Version, nextHost: Version) {
-      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: Array<DeltaWithEmbedding> = [];
-    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: Array<Version> = [];
-
-    // 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: Version, candidates: Array<DeltaWithEmbedding>, depth: number) => {
-      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<string>();
-          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: Array<Version>, debugNames?: (Delta) => string): Array<Version> {
-    let result;
-    for (const v of permutations(versions)) {
-      // whatever the permutation, result will always be the same:
-      result = this.merge(v, debugNames);
-    }
-    return result;
-  }
-}

+ 0 - 55
src/onion/version_parser.ts

@@ -1,55 +0,0 @@
-import {Buffer} from "buffer"; // NodeJS library
-
-import {Delta} from "./delta";
-import {VersionRegistry} from "./version";
-import {DeltaParser} from "./delta_parser";
-
-export class VersionParser {
-  readonly deltaParser: DeltaParser;
-  readonly versionRegistry: VersionRegistry;
-
-  constructor(deltaParser: DeltaParser, versionRegistry: VersionRegistry) {
-    this.deltaParser = deltaParser;
-    this.versionRegistry = versionRegistry;
-  }
-
-  load({externalDependencies, deltas, versions}, onLoadDelta: (Delta) => void, onLoadVersion: (Version) => void) {
-    for (const e of externalDependencies) {
-      if (this.versionRegistry.lookupOptional(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.from(parent, 'hex'));
-      const parentDelta = this.deltaParser.deltaRegistry.deltas.get(delta)!;
-      const theEmbeddings = new Map();
-      const selfEmbeddingKeys = new Set<string>();
-      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.from(v, 'hex'));
-          for (const [key,val] of Object.entries(ovr)) {
-            const guestDelta = this.deltaParser.deltaRegistry.deltas.get(key as string);
-            const hostDelta = this.deltaParser.deltaRegistry.deltas.get(val as string);
-            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);
-    }
-  }
-}

+ 1 - 1
src/parser/parser.ts

@@ -1,5 +1,5 @@
 // import {Version} from "../onion/version";
-import {Delta, PrimitiveDelta} from "onion/delta";
+import {Delta, PrimitiveDelta} from "onion-core";
 
 export interface ParseOrRenderResult {
   corrDeltas: PrimitiveDelta[];

+ 0 - 15
src/parser/rountangle_parser.test.ts

@@ -1,18 +1,3 @@
-import {RountangleParser} from "./rountangle_parser";
-
-import {
-  VersionRegistry,
-} from "../onion/version";
-
-import {
-  NodeCreation,
-  NodeDeletion,
-  EdgeUpdate,
-} from "../onion/delta";
-
-import {DeltaRegistry} from "../onion/delta_registry";
-
-import {assert} from "../util/assert";
 
 // describe("Trivial Parser", () => {
 //   it("Parse CS creation and deletion", () => {

+ 25 - 6
src/parser/rountangle_parser.ts

@@ -1,19 +1,18 @@
-import {Delta, ExistingEdge, PrimitiveDelta} from "../onion/delta";
-import {PrimitiveValue, UUID} from "../onion/types";
-import {visitPartialOrdering} from "../util/partial_ordering";
+import {Delta, ExistingEdge, PrimitiveDelta} from "onion-core";
+import {PrimitiveValue, UUID} from "onion-core";
 
 import {
   NodeCreation,
   NodeDeletion,
   EdgeUpdate,
-} from "../onion/delta";
+} from "onion-core";
 
-import {DeltaRegistry} from "../onion/delta_registry";
+import {DeltaRegistry} from "onion-core";
 
 import {
   GraphState,
   INodeState,
-} from "../onion/graph_state";
+} from "onion-core";
 
 export class ParseError extends Error {}
 
@@ -26,6 +25,26 @@ export interface Geometry2DRect {
 
 const geometryLabels = ["x", "y", "width", "height"];
 
+
+export function visitPartialOrdering<T>(elements: T[], smallerThan: (T) => T[], visitCallback: (T) => void) {
+  const visitable = new Set(elements);
+  const remaining = new Set(elements);
+  while (remaining.size > 0) {
+    let found = false;
+    for (const elem of remaining) {
+      if (smallerThan(elem).every(e => !visitable.has(e) || !remaining.has(e))) {
+        visitCallback(elem);
+        remaining.delete(elem);
+        found = true;
+        break;
+      }
+    }
+    if (!found) {
+      throw new Error("Could not find a smallest element - not a partial ordering?");
+    }
+  }
+}
+
 // Whether a is inside of b
 export const isInside = (a: Geometry2DRect, b: Geometry2DRect): boolean => 
     a.x > b.x && a.y > b.y && a.x+a.w < b.x+b.w && a.y+a.h < b.y+b.h;

+ 0 - 25
src/util/assert.ts

@@ -1,25 +0,0 @@
-// this function can be used to ensure there are no missing cases in the handling of a union type
-export function assertNever(x: never): never {
-    throw new Error(`Unexpected object: ${x}`);
-}
-
-
-export function assert(expression: boolean, msg: string) {
-  if (!expression) {
-    throw new Error(msg);
-  }
-}
-
-export function assertThrows(callback, msg) {
-  let threw = false;
-  try {
-    callback()
-  }
-  catch (e) {
-    threw = true;
-  }
-
-  if (!threw) {
-    throw new Error(msg);
-  }
-}

+ 0 - 18
src/util/buffer_xor.ts

@@ -1,18 +0,0 @@
-import {Buffer} from "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.
-export function bufferXOR(a: Buffer, b: Buffer): Buffer {
-  const result = 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;
-}
-
-export function buffersXOR(...args: Buffer[]): Buffer {
-  return args.reduce((a,b) => bufferXOR(a,b), Buffer.alloc(32));
-}

+ 0 - 49
src/util/dfs.test.ts

@@ -1,49 +0,0 @@
-import {
-  findDFS,
-} from "./dfs";
-
-import {
-  assert,
-} from "./assert";
-
-import * as _ from "lodash";
-
-const graph = new Map([
-  [0, new Map([['a', 1], ['b', 2]])], // meaning: node 0 has two outgoing links: --'a'--> node 1 and --'b'--> node 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 = findDFS(0, 1, getNeighbors);
-    assert(_.isEqual(path, ['a']), "Expected node to be found.");
-  })
-
-  it("Find node two hops", () => {
-    const path = 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:
-    assert(_.isEqual(path, ['b', 'd']), "Expected node to be found.");
-  });
-
-  it("Find node zero hops", () => {
-    const path = findDFS(0, 0, getNeighbors);
-    assert(_.isEqual(path, []), "Expected node to be found.");
-  })
-
-  it("Find unreachable node", () => {
-    const path = findDFS(3, 0, getNeighbors);
-    assert(path === undefined, "Expected node to be unreachable.");
-  });
-});

+ 0 - 35
src/util/dfs.ts

@@ -1,35 +0,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.
-export function findDFS<T,L>(start: T, searchFor: T, getNeighbors: (T)=>Array<[L,T]>): L[] | undefined {
-  const alreadyVisited = new Set<T>([start]); // only visit every 'thing' once
-
-  let recursiveInvocations = 0;
-
-  function findDFSRecursive(current: T, currentPath: L[]): L[] | undefined {
-    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;
-}

src/onion/mock_node_util.ts → src/util/mock_node_util.ts


+ 0 - 19
src/util/partial_ordering.ts

@@ -1,19 +0,0 @@
-
-export function visitPartialOrdering<T>(elements: T[], smallerThan: (T) => T[], visitCallback: (T) => void) {
-  const visitable = new Set(elements);
-  const remaining = new Set(elements);
-  while (remaining.size > 0) {
-    let found = false;
-    for (const elem of remaining) {
-      if (smallerThan(elem).every(e => !visitable.has(e) || !remaining.has(e))) {
-        visitCallback(elem);
-        remaining.delete(elem);
-        found = true;
-        break;
-      }
-    }
-    if (!found) {
-      throw new Error("Could not find a smallest element - not a partial ordering?");
-    }
-  }
-}

+ 0 - 12
src/util/permutations.ts

@@ -1,12 +0,0 @@
-export function* permutations<T>(arr: Array<T>, m: Array<T> = []) {
-  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<T>(curr.slice(), m.concat(next))
-   }
- }
-}

+ 0 - 7
src/util/test_helpers.ts

@@ -1,7 +0,0 @@
-
-export function mockUuid() {
-  let nextId = 0;
-  return function() {
-    return nextId++;
-  }
-}

+ 1 - 4
tsconfig.json

@@ -3,9 +3,6 @@
     "types": ["mocha", "node", "react"],
     "target": "es6",
     "jsx": "react",
-    "paths": {
-      "onion/*": ["./src/onion/*"],
-    },
     "sourceMap": true,
     "noImplicitThis": true,
     "strictBindCallApply": true,
@@ -14,6 +11,6 @@
     "strictPropertyInitialization": true,
     "alwaysStrict": true,
     "moduleResolution": "node",
-    "module": "commonjs",
+    "module": "commonjs"
   },
 }

+ 1 - 1
webpack.config.cjs

@@ -37,7 +37,7 @@ module.exports = {
       // allow using absolute paths when referring to 'onion' library.
       onion: path.resolve(__dirname, 'src', 'onion'),
 
-      util: path.resolve(__dirname, 'src', 'onion', 'mock_node_util.ts'),
+      util: path.resolve(__dirname, 'src', 'util', 'mock_node_util.ts'),
 
       // The following are needed to make NodeJS' 'crypto' module work in the browser:
       // Should probably migrate to the SubtleCrypto Web API