|
@@ -1,77 +1,68 @@
|
|
|
-// NodeJS libraries
|
|
|
-import {inspect} from "util";
|
|
|
+import {NodeId, PrimitiveType, UUID, nodeIdsEqual} from "./types";
|
|
|
|
|
|
-type PrimitiveType = string | number | boolean;
|
|
|
+// import {
|
|
|
+// Genesis,
|
|
|
+// NodeCreation,
|
|
|
+// NodeDeletion,
|
|
|
+// EdgeCreation,
|
|
|
+// EdgeUpdate,
|
|
|
+// EdgeDeletion,
|
|
|
+// } from "./delta";
|
|
|
|
|
|
-// This class is here to distinguish UUIDs from ordinary strings
|
|
|
-export class UUID {
|
|
|
- uuid: PrimitiveType;
|
|
|
-
|
|
|
- constructor(uuid: PrimitiveType) {
|
|
|
- this.uuid = uuid;
|
|
|
- }
|
|
|
-
|
|
|
- [inspect.custom](depth: number, options: object) {
|
|
|
- return "UUID{" + inspect(this.uuid, options) + "}"
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-type NodeId = PrimitiveType | UUID;
|
|
|
-
|
|
|
-function nodeIdsEqual(a: NodeId, b: NodeId) {
|
|
|
- if (a === b) return true;
|
|
|
- if (a instanceof UUID && b instanceof UUID) {
|
|
|
- return a.uuid === b.uuid;
|
|
|
- }
|
|
|
-}
|
|
|
|
|
|
// In- and outgoing edges of a node.
|
|
|
// This is the only place where edges are recorded.
|
|
|
// Every edge corresponds to one entry in the source's 'outgoing', and one entry in the target's 'incoming'.
|
|
|
class Node {
|
|
|
- outgoing: Map<string, NodeId>;
|
|
|
+ // creation: NodeCreation;
|
|
|
+ outgoing: Map<string, NodeId>; // key: edge label, value: target id.
|
|
|
incoming: Array<{label: string, srcId: NodeId}>;
|
|
|
|
|
|
- constructor() {
|
|
|
+ constructor(/*creation: NodeCreation*/) {
|
|
|
+ //this.creation = creation;
|
|
|
this.outgoing = new Map();
|
|
|
this.incoming = [];
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-// Helper class. Stores all nodes.
|
|
|
+// Helper class.
|
|
|
+// Abstracts away the fact that we use 2 maps for our nodes: one for our 'ordinary' nodes, and one for our value nodes.
|
|
|
class NodeMap {
|
|
|
- // ordinary nodes: they are created and deleted
|
|
|
- nodes: Map<PrimitiveType, Node>;
|
|
|
+ // ordinary nodes: they are created and deleted, and identified by a UUID
|
|
|
+ ordinary: Map<PrimitiveType, Node>;
|
|
|
|
|
|
- // value nodes: we pretend that they always already exist
|
|
|
+ // value nodes: we pretend that they always already exist, and are identified by a PrimitiveType
|
|
|
values: Map<PrimitiveType, Node>;
|
|
|
|
|
|
constructor() {
|
|
|
- this.nodes = new Map();
|
|
|
+ this.ordinary = new Map();
|
|
|
this.values = new Map();
|
|
|
}
|
|
|
|
|
|
+ // get a node by its ID. if node doesn't exist, returns undefined.
|
|
|
getOptional(id: NodeId): Node | undefined {
|
|
|
if (id instanceof UUID) {
|
|
|
// ordinary node: can only get it if it actually exists,
|
|
|
// i.e., it has already been created and has not yet been deleted.
|
|
|
- return this.nodes.get(id.uuid); // may return undefined
|
|
|
+ return this.ordinary.get(id.value); // may return undefined
|
|
|
}
|
|
|
-
|
|
|
- // value node: implicitly create it if it doesn't exist yet,
|
|
|
- // pretending that it's "always already there"
|
|
|
- const v = this.values.get(id);
|
|
|
- if (v !== undefined) {
|
|
|
- return v;
|
|
|
+ else {
|
|
|
+ // value node: implicitly create it if it doesn't exist yet,
|
|
|
+ // pretending that it's "always already there"
|
|
|
+ const valueNode = this.values.get(id);
|
|
|
+ if (valueNode !== undefined) {
|
|
|
+ return valueNode;
|
|
|
+ } else {
|
|
|
+ // auto-construct non-existing value node
|
|
|
+ const valueNode = new Node();
|
|
|
+ this.values.set(id, valueNode);
|
|
|
+ return valueNode;
|
|
|
+ }
|
|
|
}
|
|
|
- // auto-construct non-existing value node
|
|
|
- const node = new Node();
|
|
|
- this.values.set(id, node);
|
|
|
- return node;
|
|
|
}
|
|
|
|
|
|
// same as get, but raises error when not found
|
|
|
- get(id: NodeId): Node {
|
|
|
+ getOrThrow(id: NodeId): Node {
|
|
|
const node = this.getOptional(id);
|
|
|
if (node === undefined) {
|
|
|
throw Error("node not found");
|
|
@@ -82,14 +73,14 @@ class NodeMap {
|
|
|
// create a new ordinary node
|
|
|
create(id: UUID): Node {
|
|
|
const node = new Node();
|
|
|
- this.nodes.set(id.uuid, node);
|
|
|
+ this.ordinary.set(id.value, node);
|
|
|
return node;
|
|
|
}
|
|
|
|
|
|
// delete an ordinary node
|
|
|
// Idempotent.
|
|
|
delete(id: UUID) {
|
|
|
- this.nodes.delete(id.uuid);
|
|
|
+ this.ordinary.delete(id.value);
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -99,6 +90,8 @@ export class GraphState {
|
|
|
nodes: NodeMap;
|
|
|
uuidCallback: UUIDCallbackType;
|
|
|
|
|
|
+ // creations: Map<PrimitiveType, NodeCreation>;
|
|
|
+
|
|
|
constructor(uuidCallback: UUIDCallbackType) {
|
|
|
this.nodes = new NodeMap();
|
|
|
this.uuidCallback = uuidCallback;
|
|
@@ -108,25 +101,27 @@ export class GraphState {
|
|
|
createNode(): UUID {
|
|
|
const uuid = this.uuidCallback();
|
|
|
this.nodes.create(uuid);
|
|
|
+ // const creation = new NodeCreation(uuid);
|
|
|
+ // this.creations.set(uuid.value, creation);
|
|
|
return uuid;
|
|
|
}
|
|
|
|
|
|
// Delete node and delete all of its outgoing + incoming edges.
|
|
|
- // Does nothing when given uuid does not exist.
|
|
|
+ // Does nothing when given uuid does not exist./
|
|
|
// Idempotent.
|
|
|
deleteNode(uuid: UUID) {
|
|
|
const node = this.nodes.getOptional(uuid);
|
|
|
if (node !== undefined) {
|
|
|
// delete outgoing edges
|
|
|
- for (const [label, targetId] of node.outgoing.entries()) {
|
|
|
- const targetNode = this.nodes.get(targetId);
|
|
|
- // remove edge from targetNode.incoming
|
|
|
- const i = this.lookupIncoming(targetNode, label, uuid);
|
|
|
- targetNode.incoming.splice(i, 1);
|
|
|
+ for (const [label, tgtId] of node.outgoing.entries()) {
|
|
|
+ const tgtNode = this.nodes.getOrThrow(tgtId);
|
|
|
+ // remove edge from tgtNode.incoming
|
|
|
+ const i = this.lookupIncoming(tgtNode, label, uuid);
|
|
|
+ tgtNode.incoming.splice(i, 1);
|
|
|
}
|
|
|
// delete incoming edges
|
|
|
for (const {label, srcId} of node.incoming) {
|
|
|
- const srcNode = this.nodes.get(srcId);
|
|
|
+ const srcNode = this.nodes.getOrThrow(srcId);
|
|
|
// remove edge from srcNode.outgoing
|
|
|
srcNode.outgoing.delete(label);
|
|
|
}
|
|
@@ -137,26 +132,26 @@ export class GraphState {
|
|
|
|
|
|
// Create or update a node's outgoing edge to point to a node
|
|
|
// Idempotent.
|
|
|
- setEdge(srcId: NodeId, label: string, targetId: NodeId) {
|
|
|
+ setEdge(srcId: NodeId, label: string, tgtId: NodeId) {
|
|
|
// gotta remove the existing edge first, if it exists
|
|
|
this.deleteEdge(srcId, label);
|
|
|
|
|
|
- const srcNode = this.nodes.get(srcId);
|
|
|
- srcNode.outgoing.set(label, targetId);
|
|
|
- const targetNode = this.nodes.get(targetId);
|
|
|
- targetNode.incoming.push({label, srcId});
|
|
|
+ const srcNode = this.nodes.getOrThrow(srcId);
|
|
|
+ srcNode.outgoing.set(label, tgtId);
|
|
|
+ const tgtNode = this.nodes.getOrThrow(tgtId);
|
|
|
+ tgtNode.incoming.push({label, srcId});
|
|
|
}
|
|
|
|
|
|
// Delete an edge.
|
|
|
// Idempotent.
|
|
|
deleteEdge(srcId: NodeId, label: string) {
|
|
|
- const srcNode = this.nodes.get(srcId);
|
|
|
- const existingTargetId = srcNode.outgoing.get(label);
|
|
|
- if (existingTargetId !== undefined) {
|
|
|
- // remove the respective entry in the existingTargetNode's 'incoming' array:
|
|
|
- const existingTargetNode = this.nodes.get(existingTargetId);
|
|
|
- const i = this.lookupIncoming(existingTargetNode, label, srcId);
|
|
|
- existingTargetNode.incoming.splice(i, 1); // remove from array
|
|
|
+ const srcNode = this.nodes.getOrThrow(srcId);
|
|
|
+ const existingTgtId = srcNode.outgoing.get(label);
|
|
|
+ if (existingTgtId !== undefined) {
|
|
|
+ // remove the respective entry in the existingTgtNode's 'incoming' array:
|
|
|
+ const existingTgtNode = this.nodes.getOrThrow(existingTgtId);
|
|
|
+ const i = this.lookupIncoming(existingTgtNode, label, srcId);
|
|
|
+ existingTgtNode.incoming.splice(i, 1); // remove from array
|
|
|
}
|
|
|
srcNode.outgoing.delete(label);
|
|
|
}
|
|
@@ -184,15 +179,15 @@ export class GraphState {
|
|
|
getEdges(): Array<[NodeId, NodeId, string]> {
|
|
|
const result: Array<[NodeId, NodeId, string]> = [];
|
|
|
// get all outgoing edges of ordinary nodes
|
|
|
- for (const [srcId, srcNode] of this.nodes.nodes.entries()) {
|
|
|
- for (const [label, targetId] of srcNode.outgoing) {
|
|
|
- result.push([new UUID(srcId), targetId, label]);
|
|
|
+ for (const [srcId, srcNode] of this.nodes.ordinary.entries()) {
|
|
|
+ for (const [label, tgtId] of srcNode.outgoing) {
|
|
|
+ result.push([new UUID(srcId), tgtId, label]);
|
|
|
}
|
|
|
}
|
|
|
// get all outgoing edges of value nodes
|
|
|
for (const [srcId, srcNode] of this.nodes.values.entries()) {
|
|
|
- for (const [label, targetId] of srcNode.outgoing) {
|
|
|
- result.push([srcId, targetId, label]);
|
|
|
+ for (const [label, tgtId] of srcNode.outgoing) {
|
|
|
+ result.push([srcId, tgtId, label]);
|
|
|
}
|
|
|
}
|
|
|
return result;
|