delta.ts 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  1. import {NodeId, PrimitiveType, UUID, nodeIdsEqual} from "./types";
  2. type Conflict = [MicroOperation, MicroOperation];
  3. interface MicroOperation {
  4. // get conflicts between MicroOperations that depend on this MicroOperation.
  5. getConflicts(): Array<Conflict>;
  6. }
  7. export class NodeCreation implements MicroOperation {
  8. readonly id: NodeId;
  9. // Inverse dependency: Deletions of this node.
  10. deletions: Array<NodeDeletion>; // append-only
  11. // Inverse dependency: Creation of incoming and outgoing edges.
  12. edges: Array<EdgeCreation>; // append-only
  13. constructor(id: NodeId) {
  14. this.id = id;
  15. this.deletions = [];
  16. this.edges = [];
  17. }
  18. // There are 2 types of conflicts that can occur whose most nearby common ancestor is a NodeCreation: {DELETE,DELETE} and {DELETE,REQUIRE}.
  19. // Whenever a node is deleted more than once, concurrently, there's a {DELETE,DELETE}-conflict
  20. getDeleteDeleteConflicts(): Array<[NodeDeletion, NodeDeletion]> {
  21. // Basically get all pairs of 'deletions'
  22. const result = new Array(this.deletions.length*(this.deletions.length-1));
  23. let i = 0;
  24. for (const del1 of this.deletions) {
  25. for (const del2 of this.deletions) {
  26. if (del1 !== del2) {
  27. result[i] = [del1, del2]; // conflict: this node is concurrently deleted more than once
  28. i++;
  29. }
  30. }
  31. }
  32. return result;
  33. }
  34. // Whenever a node is deleted, and the source/target of a concurrent edge creation, there's a {DELETE,REQUIRE}-conflict.
  35. getDeleteRequireConflicts(): Array<[NodeDeletion, EdgeCreation]> {
  36. const result: Array<[NodeDeletion, EdgeCreation]> = [];
  37. for (const del of this.deletions) {
  38. for (const edgeCreation of this.edges) {
  39. let edgeDeleted = false;
  40. for (const edgeDel of del.edgeDeletions) {
  41. if (edgeDel.getCreation() === edgeCreation) {
  42. edgeDeleted = true;
  43. break;
  44. }
  45. }
  46. if (!edgeDeleted) {
  47. result.push([del, edgeCreation]); // conflict: this node is deleted, and at the same time it is the source or target of an edge
  48. }
  49. }
  50. }
  51. return result;
  52. }
  53. // Whenever two edges with the same source and the same label are concurrently created
  54. getEdgeCreationConflicts(): Array<[EdgeCreation, EdgeCreation]> {
  55. const result: Array<[EdgeCreation, EdgeCreation]> = [];
  56. for (const c1 of this.edges) {
  57. for (const c2 of this.edges) {
  58. if (c1 !== c2) {
  59. if (c1.label === c2.label) {
  60. result.push([c1, c2]); // conflict: same edge created concurrently
  61. }
  62. }
  63. }
  64. }
  65. return result;
  66. }
  67. getConflicts(): Array<Conflict> {
  68. return (this.getDeleteDeleteConflicts() as Array<Conflict>) // downcast :)
  69. .concat(this.getDeleteRequireConflicts() as Array<Conflict>) // downcast
  70. .concat(this.getEdgeCreationConflicts() as Array<Conflict>); // downcast
  71. }
  72. }
  73. export class NodeDeletion implements MicroOperation {
  74. // Dependency: The node being deleted.
  75. readonly deletes: NodeCreation;
  76. // Dependency: Deletion of a node depends on deletion of its incoming and outgoing edges.
  77. readonly edgeDeletions: Array<EdgeDeletion>;
  78. constructor(deletes: NodeCreation, edgeDeletions: Array<EdgeDeletion>) {
  79. this.deletes = deletes;
  80. this.edgeDeletions = edgeDeletions;
  81. // Create inverse dependency
  82. this.deletes.deletions.push(this);
  83. }
  84. getConflicts(): Array<Conflict> {
  85. return [];
  86. }
  87. }
  88. export interface EdgeOperation extends MicroOperation {
  89. // Edge operations always form a tree (or sequence, if non-conflicting) where the path from root to leaf is as follows: create, update, update, ..., update, delete
  90. // Therefore, any 'node' in this tree traces back to a 'root', the creation.
  91. getCreation(): EdgeCreation;
  92. }
  93. type EdgeUpdateOrDeletion = EdgeUpdate | EdgeDeletion;
  94. abstract class EdgeCreationOrUpdate {
  95. // Dependency: (new) target node
  96. readonly target: NodeCreation;
  97. // Inverse dependency: Next operation on the edge
  98. nextOperations: Array<EdgeUpdate | EdgeDeletion>; // append-only
  99. constructor(target: NodeCreation) {
  100. this.target = target;
  101. this.nextOperations = [];
  102. }
  103. // {UPDATE,UPDATE}, {UDPATE,DELETE}, {DELETE,DELETE} on two edges with the same source and label.
  104. getUpdateOrDeleteConflicts(): Array<[EdgeUpdateOrDeletion, EdgeUpdateOrDeletion]> {
  105. const result = new Array(this.nextOperations.length*(this.nextOperations.length-1));
  106. let i = 0;
  107. for (const n1 of this.nextOperations) {
  108. for (const n2 of this.nextOperations) {
  109. if (n1 !== n2) {
  110. result[i] = [n1, n2];
  111. i++;
  112. }
  113. }
  114. }
  115. return result;
  116. }
  117. getConflicts(): Array<Conflict> {
  118. return this.getUpdateOrDeleteConflicts();
  119. }
  120. abstract getCreation(): EdgeCreation;
  121. }
  122. export class EdgeCreation extends EdgeCreationOrUpdate implements EdgeOperation {
  123. // Dependency: source node
  124. readonly source: NodeCreation;
  125. readonly label: string;
  126. constructor(source: NodeCreation, label: string, target: NodeCreation) {
  127. super(target);
  128. this.source = source;
  129. this.label = label;
  130. // Create inverse dependency
  131. this.source.edges.push(this);
  132. }
  133. getCreation(): EdgeCreation {
  134. return this;
  135. }
  136. }
  137. export class EdgeUpdate extends EdgeCreationOrUpdate implements EdgeOperation {
  138. readonly edge: EdgeCreationOrUpdate; // UPD-dependency
  139. constructor(edge: EdgeCreationOrUpdate, newTarget: NodeCreation) {
  140. super(newTarget);
  141. this.edge = edge;
  142. // Create inverse dependency
  143. this.edge.nextOperations.push(this);
  144. }
  145. getCreation(): EdgeCreation {
  146. return this.edge.getCreation();
  147. }
  148. }
  149. export class EdgeDeletion implements EdgeOperation {
  150. readonly edge: EdgeCreationOrUpdate; // UPD-dependency
  151. constructor(edge: EdgeCreationOrUpdate) {
  152. this.edge = edge;
  153. // Create inverse dependency
  154. this.edge.nextOperations.push(this);
  155. }
  156. getCreation(): EdgeCreation {
  157. return this.edge.getCreation();
  158. }
  159. getConflicts(): Array<Conflict> {
  160. return [];
  161. }
  162. }
  163. // class GraphState {
  164. // nodes: Map<NodeId, NodeCreation>;
  165. // createNode(id: NodeId): NodeCreation {
  166. // nodes.set()
  167. // }
  168. // createEdge(source: NodeCreation, label: string, target: NodeCreation) {
  169. // }
  170. // }