@vuer-ai/vuer-rtc 0.7.0 → 0.8.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (172) hide show
  1. package/CLAUDE.md +3 -2
  2. package/dist/client/EditBuffer.d.ts +4 -4
  3. package/dist/client/EditBuffer.d.ts.map +1 -1
  4. package/dist/client/EditBuffer.js +26 -25
  5. package/dist/client/EditBuffer.js.map +1 -1
  6. package/dist/client/actions.d.ts +3 -3
  7. package/dist/client/actions.d.ts.map +1 -1
  8. package/dist/client/actions.js +71 -70
  9. package/dist/client/actions.js.map +1 -1
  10. package/dist/client/coalesceGraphOps.d.ts +4 -4
  11. package/dist/client/coalesceGraphOps.js +4 -4
  12. package/dist/client/coalesceTextOperations.d.ts.map +1 -1
  13. package/dist/client/coalesceTextOperations.js +23 -20
  14. package/dist/client/coalesceTextOperations.js.map +1 -1
  15. package/dist/client/coalescence/lwwOperations.js +3 -3
  16. package/dist/client/coalescence/lwwOperations.js.map +1 -1
  17. package/dist/client/coalescence/numberOperations.js +2 -2
  18. package/dist/client/coalescence/numberOperations.js.map +1 -1
  19. package/dist/client/coalescence/registry.d.ts +3 -3
  20. package/dist/client/coalescence/registry.d.ts.map +1 -1
  21. package/dist/client/coalescence/registry.js +11 -11
  22. package/dist/client/coalescence/registry.js.map +1 -1
  23. package/dist/client/coalescence/textDeletes.d.ts +8 -7
  24. package/dist/client/coalescence/textDeletes.d.ts.map +1 -1
  25. package/dist/client/coalescence/textDeletes.js +11 -11
  26. package/dist/client/coalescence/textDeletes.js.map +1 -1
  27. package/dist/client/coalescence/textInserts.d.ts +8 -5
  28. package/dist/client/coalescence/textInserts.d.ts.map +1 -1
  29. package/dist/client/coalescence/textInserts.js +32 -12
  30. package/dist/client/coalescence/textInserts.js.map +1 -1
  31. package/dist/client/coalescence/utils.d.ts +3 -9
  32. package/dist/client/coalescence/utils.d.ts.map +1 -1
  33. package/dist/client/coalescence/utils.js +10 -8
  34. package/dist/client/coalescence/utils.js.map +1 -1
  35. package/dist/client/coalescence/vector3Operations.js +2 -2
  36. package/dist/client/coalescence/vector3Operations.js.map +1 -1
  37. package/dist/client/createGraph.d.ts +2 -2
  38. package/dist/client/createGraph.js +4 -4
  39. package/dist/client/createGraph.js.map +1 -1
  40. package/dist/client/createTextDocument.d.ts +1 -1
  41. package/dist/client/createTextDocument.js +3 -3
  42. package/dist/client/createTextDocument.js.map +1 -1
  43. package/dist/client/hooks.d.ts +3 -3
  44. package/dist/client/hooks.d.ts.map +1 -1
  45. package/dist/client/hooks.js +4 -4
  46. package/dist/client/hooks.js.map +1 -1
  47. package/dist/client/textActions.d.ts +2 -2
  48. package/dist/client/textActions.d.ts.map +1 -1
  49. package/dist/client/textActions.js +47 -47
  50. package/dist/client/textActions.js.map +1 -1
  51. package/dist/client/textTypes.d.ts +8 -8
  52. package/dist/client/textTypes.d.ts.map +1 -1
  53. package/dist/client/types.d.ts +4 -4
  54. package/dist/client/types.d.ts.map +1 -1
  55. package/dist/crdt/GraphTextCRDT.d.ts +2 -2
  56. package/dist/crdt/GraphTextCRDT.d.ts.map +1 -1
  57. package/dist/crdt/GraphTextCRDT.js +6 -6
  58. package/dist/crdt/GraphTextCRDT.js.map +1 -1
  59. package/dist/crdt/Rope.d.ts +13 -14
  60. package/dist/crdt/Rope.d.ts.map +1 -1
  61. package/dist/crdt/Rope.js +130 -59
  62. package/dist/crdt/Rope.js.map +1 -1
  63. package/dist/crdt/index.d.ts +1 -1
  64. package/dist/crdt/index.d.ts.map +1 -1
  65. package/dist/crdt/index.js +1 -1
  66. package/dist/crdt/index.js.map +1 -1
  67. package/dist/index.d.ts +1 -1
  68. package/dist/index.d.ts.map +1 -1
  69. package/dist/index.js +1 -1
  70. package/dist/index.js.map +1 -1
  71. package/dist/operations/OperationTypes.d.ts +45 -48
  72. package/dist/operations/OperationTypes.d.ts.map +1 -1
  73. package/dist/operations/OperationValidator.js +11 -11
  74. package/dist/operations/OperationValidator.js.map +1 -1
  75. package/dist/operations/apply/node.js +3 -3
  76. package/dist/operations/apply/node.js.map +1 -1
  77. package/dist/operations/apply/text.d.ts.map +1 -1
  78. package/dist/operations/apply/text.js +35 -32
  79. package/dist/operations/apply/text.js.map +1 -1
  80. package/dist/operations/apply/types.d.ts +4 -4
  81. package/dist/operations/apply/types.d.ts.map +1 -1
  82. package/dist/operations/apply/types.js +8 -8
  83. package/dist/operations/apply/types.js.map +1 -1
  84. package/dist/operations/dispatcher.d.ts.map +1 -1
  85. package/dist/operations/dispatcher.js +52 -13
  86. package/dist/operations/dispatcher.js.map +1 -1
  87. package/dist/serdes.d.ts +1 -1
  88. package/dist/serdes.d.ts.map +1 -1
  89. package/dist/state/ConflictResolver.d.ts +9 -9
  90. package/dist/state/ConflictResolver.d.ts.map +1 -1
  91. package/dist/state/ConflictResolver.js +20 -20
  92. package/dist/state/ConflictResolver.js.map +1 -1
  93. package/dist/state/DType.d.ts +2 -2
  94. package/dist/state/DType.d.ts.map +1 -1
  95. package/dist/state/DType.js +14 -14
  96. package/dist/state/DType.js.map +1 -1
  97. package/dist/state/VectorClock.d.ts +6 -6
  98. package/dist/state/VectorClock.d.ts.map +1 -1
  99. package/dist/state/VectorClock.js +14 -14
  100. package/dist/state/VectorClock.js.map +1 -1
  101. package/dist/state/index.d.ts +1 -1
  102. package/dist/state/index.js +1 -1
  103. package/examples/01-basic-usage.ts +16 -16
  104. package/examples/02-concurrent-edits.ts +29 -29
  105. package/examples/03-scene-building.ts +28 -28
  106. package/examples/04-conflict-resolution.ts +56 -56
  107. package/examples/05-coalescence-usage.ts +23 -23
  108. package/examples/README.md +12 -12
  109. package/package.json +1 -1
  110. package/src/client/EditBuffer.ts +28 -27
  111. package/src/client/TEXT_DOCUMENT_API.md +9 -9
  112. package/src/client/actions.ts +74 -70
  113. package/src/client/coalesceGraphOps.ts +4 -4
  114. package/src/client/coalesceTextOperations.ts +26 -22
  115. package/src/client/coalescence/lwwOperations.ts +3 -3
  116. package/src/client/coalescence/numberOperations.ts +2 -2
  117. package/src/client/coalescence/registry.ts +13 -12
  118. package/src/client/coalescence/textDeletes.ts +22 -18
  119. package/src/client/coalescence/textInserts.ts +49 -25
  120. package/src/client/coalescence/utils.ts +14 -11
  121. package/src/client/coalescence/vector3Operations.ts +2 -2
  122. package/src/client/createGraph.ts +4 -4
  123. package/src/client/createTextDocument.ts +3 -3
  124. package/src/client/hooks.tsx +5 -5
  125. package/src/client/textActions.ts +47 -47
  126. package/src/client/textTypes.ts +8 -8
  127. package/src/client/types.ts +4 -4
  128. package/src/crdt/GraphTextCRDT.ts +6 -6
  129. package/src/crdt/Rope.ts +156 -71
  130. package/src/crdt/index.ts +2 -0
  131. package/src/index.ts +2 -0
  132. package/src/operations/OperationTypes.ts +47 -47
  133. package/src/operations/OperationValidator.ts +11 -11
  134. package/src/operations/apply/node.ts +3 -3
  135. package/src/operations/apply/text.ts +38 -32
  136. package/src/operations/apply/types.ts +11 -11
  137. package/src/operations/dispatcher.ts +57 -13
  138. package/src/serdes.ts +1 -1
  139. package/src/state/ConflictResolver.ts +23 -23
  140. package/src/state/DType.ts +16 -16
  141. package/src/state/VectorClock.ts +14 -14
  142. package/src/state/index.ts +1 -1
  143. package/tests/client/actions.test.ts +76 -76
  144. package/tests/client/coalesce-graph-operations.test.ts +84 -84
  145. package/tests/client/coalesce-text-operations.test.ts +91 -114
  146. package/tests/client/compaction.test.ts +18 -18
  147. package/tests/client/delete-coalescence-bug.test.ts +34 -34
  148. package/tests/client/edit-buffer.test.ts +27 -30
  149. package/tests/client/graph-coalescence-phase1.test.ts +66 -66
  150. package/tests/client/graph-coalescence.test.ts +50 -50
  151. package/tests/client/journal-benchmark.test.ts +5 -5
  152. package/tests/crdt/graph-text-crdt.test.ts +60 -64
  153. package/tests/crdt/rope.test.ts +9 -8
  154. package/tests/crdt/text-operations.test.ts +28 -28
  155. package/tests/fixtures/array-ops.jsonl +6 -6
  156. package/tests/fixtures/boolean-ops.jsonl +6 -6
  157. package/tests/fixtures/color-ops.jsonl +4 -4
  158. package/tests/fixtures/edit-buffer.jsonl +3 -3
  159. package/tests/fixtures/node-ops.jsonl +6 -6
  160. package/tests/fixtures/number-ops.jsonl +7 -7
  161. package/tests/fixtures/object-ops.jsonl +4 -4
  162. package/tests/fixtures/operations.jsonl +7 -7
  163. package/tests/fixtures/string-ops.jsonl +4 -4
  164. package/tests/fixtures/undo-redo.jsonl +3 -3
  165. package/tests/fixtures/vector-ops.jsonl +17 -17
  166. package/tests/operations/collections.test.ts +4 -4
  167. package/tests/operations/nodes.test.ts +5 -5
  168. package/tests/operations/operation-ordering.test.ts +406 -0
  169. package/tests/operations/primitives.test.ts +4 -4
  170. package/tests/operations/unified-schema.test.ts +27 -27
  171. package/tests/operations/vectors.test.ts +4 -4
  172. package/tests/sync/digest.test.ts +5 -5
@@ -4,14 +4,18 @@
4
4
  * Merges consecutive text delete operations.
5
5
  */
6
6
 
7
- import type { Operation } from '../../operations/OperationTypes.js';
8
7
  import { optimizeDeletions, parseItemId } from './utils.js';
9
8
 
9
+ /**
10
+ * Text delete operation for graph coalescence.
11
+ * This is the wire format for text.delete operations (with key/path context).
12
+ * Uses compressed schema with tuple-based fields.
13
+ */
10
14
  export interface TextDeleteOp {
11
- otype: 'text.delete';
12
- key: string;
13
- path: string;
14
- deletions: Array<{ id: string; length: number }>; // Array of deletion ranges
15
+ ot: 'text.delete'; // Graph operation type
16
+ key: string; // Node key
17
+ path: string; // Property path
18
+ rm: Array<[string, number]>; // [[id, length], ...]
15
19
  // NOTE: TextDeleteOp does NOT have seq/ts fields (those are only on TextInsertOp)
16
20
  // Time-based coalescence is not available for delete operations
17
21
  }
@@ -25,13 +29,13 @@ export interface CoalesceOptions {
25
29
  * Sort and optimize deletions array.
26
30
  * Deletions must be sorted in ascending order for optimizeDeletions to work correctly.
27
31
  */
28
- function sortAndOptimizeDeletions(deletions: Array<{ id: string; length: number }>): Array<{ id: string; length: number }> {
32
+ function sortAndOptimizeDeletions(deletions: Array<[string, number]>): Array<[string, number]> {
29
33
  if (deletions.length === 0) return deletions;
30
34
 
31
35
  // Sort deletions by agent, then by sequence number (ascending)
32
36
  const sorted = [...deletions].sort((a, b) => {
33
- const aId = parseItemId(a.id);
34
- const bId = parseItemId(b.id);
37
+ const aId = parseItemId(a[0]); // id is at [0]
38
+ const bId = parseItemId(b[0]); // id is at [0]
35
39
  if (aId.agent !== bId.agent) {
36
40
  return aId.agent.localeCompare(bId.agent);
37
41
  }
@@ -44,11 +48,11 @@ function sortAndOptimizeDeletions(deletions: Array<{ id: string; length: number
44
48
  /**
45
49
  * Check if an operation is a text delete with CRDT metadata
46
50
  */
47
- export function isTextDeleteOp(op: Operation): op is TextDeleteOp {
51
+ export function isTextDeleteOp(op: any): op is TextDeleteOp {
48
52
  return (
49
- op.otype === 'text.delete' &&
50
- Array.isArray((op as any).deletions) &&
51
- (op as any).deletions.length > 0
53
+ op.ot === 'text.delete' &&
54
+ Array.isArray(op.rm) &&
55
+ op.rm.length > 0
52
56
  );
53
57
  }
54
58
 
@@ -77,7 +81,7 @@ export function coalesceTextDeletes(
77
81
  for (const op of ops) {
78
82
  if (pending === null) {
79
83
  // Start new pending delete
80
- pending = { ...op, deletions: [...op.deletions] };
84
+ pending = { ...op, rm: [...op.rm] };
81
85
  } else {
82
86
  // Check merge conditions
83
87
  const sameTarget = pending.key === op.key && pending.path === op.path;
@@ -87,16 +91,16 @@ export function coalesceTextDeletes(
87
91
  if (sameTarget) {
88
92
  // Merge operations - combine deletion lists
89
93
  const merged: TextDeleteOp = {
90
- otype: 'text.delete',
94
+ ot: 'text.delete',
91
95
  key: pending.key,
92
96
  path: pending.path,
93
- deletions: [...pending.deletions, ...op.deletions],
97
+ rm: [...pending.rm, ...op.rm],
94
98
  };
95
99
  pending = merged;
96
100
  } else {
97
101
  // Can't merge - flush pending and start new
98
102
  result.push(pending);
99
- pending = { ...op, deletions: [...op.deletions] };
103
+ pending = { ...op, rm: [...op.rm] };
100
104
  }
101
105
  }
102
106
  }
@@ -104,13 +108,13 @@ export function coalesceTextDeletes(
104
108
  // Flush any remaining pending delete
105
109
  if (pending !== null) {
106
110
  // Sort and optimize deletions array before pushing
107
- pending.deletions = sortAndOptimizeDeletions(pending.deletions);
111
+ pending.rm = sortAndOptimizeDeletions(pending.rm);
108
112
  result.push(pending);
109
113
  }
110
114
 
111
115
  // Also sort and optimize deletions in all previously pushed operations
112
116
  for (const op of result) {
113
- op.deletions = sortAndOptimizeDeletions(op.deletions);
117
+ op.rm = sortAndOptimizeDeletions(op.rm);
114
118
  }
115
119
 
116
120
  return result;
@@ -12,18 +12,38 @@
12
12
  * 5. Compatible YATA structure (forms a chain)
13
13
  */
14
14
 
15
- import type { Operation } from '../../operations/OperationTypes.js';
16
15
  import { parseItemId } from '../../crdt/Rope.js';
17
16
 
17
+ /**
18
+ * Text insert operation for graph coalescence.
19
+ * This is the wire format for text.insert operations (with key/path context).
20
+ * Uses compressed schema with tuple-based fields.
21
+ */
18
22
  export interface TextInsertOp {
19
- otype: 'text.insert';
20
- key: string;
21
- path: string;
22
- id: string;
23
- content: string;
24
- parentId: string | null;
25
- seq: number;
26
- ts: number;
23
+ ot: 'text.insert'; // Graph operation type
24
+ key: string; // Node key
25
+ path: string; // Property path
26
+ id: string; // CRDT item ID
27
+ value: [string | null, string]; // [anchor, content]
28
+ seq: number; // Lamport timestamp
29
+ ts: number; // Wall-clock time
30
+ }
31
+
32
+ /**
33
+ * Calculate the last character ID for an insert operation.
34
+ * If the operation has _lastCharId metadata, use it. Otherwise calculate from id + content length.
35
+ */
36
+ function getLastCharId(op: TextInsertOp): string {
37
+ // Check if operation has _lastCharId metadata (from previous coalescence)
38
+ const lastCharId = (op as any)._lastCharId;
39
+ if (lastCharId) return lastCharId;
40
+
41
+ // Calculate from id + content length
42
+ const content = op.value[1];
43
+ if (content.length === 0) return op.id;
44
+
45
+ const parsed = parseItemId(op.id);
46
+ return `${parsed.agent}:${parsed.seq + content.length - 1}`;
27
47
  }
28
48
 
29
49
  export interface CoalesceOptions {
@@ -34,13 +54,14 @@ export interface CoalesceOptions {
34
54
  /**
35
55
  * Check if an operation is a text insert with CRDT metadata
36
56
  */
37
- export function isTextInsertOp(op: Operation): op is TextInsertOp {
57
+ export function isTextInsertOp(op: any): op is TextInsertOp {
38
58
  return (
39
- op.otype === 'text.insert' &&
40
- typeof (op as any).id === 'string' &&
41
- typeof (op as any).content === 'string' &&
42
- typeof (op as any).seq === 'number' &&
43
- typeof (op as any).ts === 'number'
59
+ op.ot === 'text.insert' &&
60
+ typeof op.id === 'string' &&
61
+ Array.isArray(op.value) &&
62
+ op.value.length === 2 &&
63
+ typeof op.seq === 'number' &&
64
+ typeof op.ts === 'number'
44
65
  );
45
66
  }
46
67
 
@@ -79,9 +100,10 @@ export function coalesceTextInserts(
79
100
  const sameAgent = prevId.agent === currId.agent;
80
101
  const sameTarget = pending.key === op.key && pending.path === op.path;
81
102
 
82
- // IDs must be sequential: next ID = prev ID + prev content length
83
- // Example: prev="alice:5" content="hel"(3 chars) → next="alice:8"
84
- const sequentialIds = currId.seq === prevId.seq + pending.content.length;
103
+ // IDs must be sequential: next ID = prev ID + prev value/content length
104
+ // Example: prev="alice:5" value="hel"(3 chars) → next="alice:8"
105
+ const pendingContent = pending.value[1]; // content is value[1]
106
+ const sequentialIds = currId.seq === prevId.seq + pendingContent.length;
85
107
 
86
108
  // Time threshold: operations must be close in time (ts is in seconds)
87
109
  const timeDiffMs = (op.ts - pending.ts) * 1000;
@@ -91,24 +113,26 @@ export function coalesceTextInserts(
91
113
  // Current op's parent should be the last character ID in the merged content
92
114
  // (not the first ID, which is what pending.id contains after merging)
93
115
  // OR both should have the same parent (inserting at same position)
94
- const prevLastId = (pending as any)._lastCharId || pending.id;
116
+ const prevLastId = getLastCharId(pending);
117
+ const opAnchor = op.value[0]; // anchor is value[0] (can be null)
118
+ const pendingAnchor = pending.value[0]; // anchor is value[0] (can be null)
95
119
  const formsChain =
96
- op.parentId === prevLastId ||
97
- (pending.parentId === op.parentId && pending.parentId !== null);
120
+ opAnchor === prevLastId ||
121
+ (pendingAnchor === opAnchor && pendingAnchor !== null);
98
122
 
99
123
  if (sameAgent && sameTarget && sequentialIds && withinThreshold && formsChain) {
100
124
  // Merge operations
125
+ const opContent = op.value[1]; // content is value[1]
101
126
  const merged: TextInsertOp = {
102
- otype: 'text.insert',
127
+ ot: 'text.insert',
103
128
  key: pending.key,
104
129
  path: pending.path,
105
130
  id: pending.id, // Keep first ID (anchor point)
106
- content: pending.content + op.content, // Concatenate content
107
- parentId: pending.parentId, // Keep first parentId
131
+ value: [pendingAnchor, pendingContent + opContent], // [anchor, merged content]
108
132
  seq: Math.max(pending.seq, op.seq), // Use max Lamport clock for ordering
109
133
  ts: pending.ts, // Keep first timestamp (when sequence started)
110
134
  // Track the last character ID for chain validation in next merge
111
- _lastCharId: op.id,
135
+ _lastCharId: getLastCharId(op),
112
136
  } as any;
113
137
  pending = merged;
114
138
  } else {
@@ -23,33 +23,36 @@ export function parseItemId(id: ItemId): { agent: string; seq: number } {
23
23
  * Optimize a deletions array by merging consecutive deletions from the same agent.
24
24
  *
25
25
  * Example:
26
- * Input: [{id: "alice:1", length: 1}, {id: "alice:2", length: 1}, {id: "alice:3", length: 1}]
27
- * Output: [{id: "alice:1", length: 3}]
26
+ * Input: [["alice:1", 1], ["alice:2", 1], ["alice:3", 1]]
27
+ * Output: [["alice:1", 3]]
28
28
  *
29
29
  * This reduces the size of deletion arrays when consecutive items are deleted.
30
30
  */
31
31
  export function optimizeDeletions(
32
- deletions: Array<{ id: ItemId; length: number }>
33
- ): Array<{ id: ItemId; length: number }> {
32
+ deletions: Array<[ItemId, number]>
33
+ ): Array<[ItemId, number]> {
34
34
  if (deletions.length === 0) return deletions;
35
35
 
36
- const result: Array<{ id: ItemId; length: number }> = [];
37
- let current = { ...deletions[0] };
36
+ const result: Array<[ItemId, number]> = [];
37
+ let current: [ItemId, number] = [...deletions[0]]; // Clone the tuple
38
38
 
39
39
  for (let i = 1; i < deletions.length; i++) {
40
40
  const deletion = deletions[i];
41
- const currParsed = parseItemId(current.id);
42
- const delParsed = parseItemId(deletion.id);
41
+ const currentId = current[0]; // id is at [0]
42
+ const deletionId = deletion[0]; // id is at [0]
43
+
44
+ const currParsed = parseItemId(currentId);
45
+ const delParsed = parseItemId(deletionId);
43
46
 
44
47
  // Can merge if: same agent AND current deletion ends where next starts
45
48
  if (currParsed.agent === delParsed.agent &&
46
- currParsed.seq + current.length === delParsed.seq) {
49
+ currParsed.seq + current[1] === delParsed.seq) {
47
50
  // Merge: extend current deletion
48
- current.length += deletion.length;
51
+ current[1] += deletion[1]; // length is at [1]
49
52
  } else {
50
53
  // Cannot merge: push current and start new
51
54
  result.push(current);
52
- current = { ...deletion };
55
+ current = [...deletion]; // Clone the tuple
53
56
  }
54
57
  }
55
58
 
@@ -16,7 +16,7 @@ export type { Vector3AddOp };
16
16
  */
17
17
  export function isVector3AddOp(op: Operation): op is Vector3AddOp {
18
18
  return (
19
- op.otype === 'vector3.add' &&
19
+ op.ot === 'vector3.add' &&
20
20
  Array.isArray((op as any).value) &&
21
21
  (op as any).value.length === 3
22
22
  );
@@ -59,7 +59,7 @@ export function coalesceVector3Adds(
59
59
  if (sameTarget) {
60
60
  // Merge operations by summing vectors component-wise
61
61
  pending = {
62
- otype: 'vector3.add',
62
+ ot: 'vector3.add',
63
63
  key: pending.key,
64
64
  path: pending.path,
65
65
  value: [
@@ -4,11 +4,11 @@
4
4
  * Usage:
5
5
  * ```typescript
6
6
  * const store = createGraph({
7
- * sessionId: 'my-session',
7
+ * client: 'my-session',
8
8
  * onSend: (msg) => websocket.send(msg),
9
9
  * });
10
10
  *
11
- * store.edit({ otype: 'vector3.add', key: 'cube', path: 'position', value: [1, 0, 0] });
11
+ * store.edit({ ot: 'vector3.add', key: 'cube', path: 'position', value: [1, 0, 0] });
12
12
  * store.commit('Move cube');
13
13
  * store.undo();
14
14
  * ```
@@ -35,7 +35,7 @@ import type { VectorClock } from '../state/VectorClock.js';
35
35
  * Create a graph store
36
36
  */
37
37
  export function createGraph(options: CreateGraphOptions): GraphStore {
38
- let state = createInitialState(options.sessionId, options.initialSnapshot);
38
+ let state = createInitialState(options.client, options.initialSnapshot);
39
39
  const listeners = new Set<() => void>();
40
40
  let coalescingTimer: ReturnType<typeof setTimeout> | null = null;
41
41
  let coalescingEnabled = options.coalescingEnabled ?? false;
@@ -136,7 +136,7 @@ export function createGraph(options: CreateGraphOptions): GraphStore {
136
136
  },
137
137
 
138
138
  loadServerState: (snapshot: Snapshot, journal: CRDTMessage[]) => {
139
- dispatch(() => initFromServer(options.sessionId, snapshot, journal));
139
+ dispatch(() => initFromServer(options.client, snapshot, journal));
140
140
  },
141
141
 
142
142
  // Undo/redo
@@ -4,7 +4,7 @@
4
4
  * Usage:
5
5
  * ```typescript
6
6
  * const store = createTextDocument({
7
- * sessionId: 'my-session',
7
+ * client: 'my-session',
8
8
  * onSend: (msg) => websocket.send(msg),
9
9
  * });
10
10
  *
@@ -38,7 +38,7 @@ import {
38
38
  * Create a text document store
39
39
  */
40
40
  export function createTextDocument(options: CreateTextDocumentOptions): TextDocumentStore {
41
- let state = createInitialTextState(options.sessionId, options.initialSnapshot);
41
+ let state = createInitialTextState(options.client, options.initialSnapshot);
42
42
  const listeners = new Set<() => void>();
43
43
  let coalescingTimer: ReturnType<typeof setTimeout> | null = null;
44
44
  let coalescingEnabled = options.coalescingEnabled ?? false;
@@ -161,7 +161,7 @@ export function createTextDocument(options: CreateTextDocumentOptions): TextDocu
161
161
  },
162
162
 
163
163
  loadServerState: (snapshot: TextSnapshot, journal: TextMessage[]) => {
164
- dispatch(() => initTextFromServer(options.sessionId, snapshot, journal));
164
+ dispatch(() => initTextFromServer(options.client, snapshot, journal));
165
165
  },
166
166
 
167
167
  // Undo/redo
@@ -5,7 +5,7 @@
5
5
  * ```tsx
6
6
  * function App() {
7
7
  * return (
8
- * <GraphProvider sessionId="my-session" onSend={sendToServer}>
8
+ * <GraphProvider client="my-session" onSend={sendToServer}>
9
9
  * <Scene />
10
10
  * </GraphProvider>
11
11
  * );
@@ -42,21 +42,21 @@ const GraphContext = createContext<GraphStore | null>(null);
42
42
  // ===========================================
43
43
 
44
44
  export interface GraphProviderProps {
45
- sessionId: string;
45
+ client: string;
46
46
  initialSnapshot?: Snapshot;
47
47
  onSend?: (msg: CRDTMessage) => void;
48
48
  children: ReactNode;
49
49
  }
50
50
 
51
51
  export function GraphProvider({
52
- sessionId,
52
+ client,
53
53
  initialSnapshot,
54
54
  onSend,
55
55
  children,
56
56
  }: GraphProviderProps): React.ReactElement {
57
57
  const [store] = useState(() =>
58
58
  createGraph({
59
- sessionId,
59
+ client,
60
60
  initialSnapshot,
61
61
  onSend,
62
62
  })
@@ -205,7 +205,7 @@ export function useDrag(nodeKey: string, path: string) {
205
205
  const onDrag = useCallback(
206
206
  (delta: [number, number, number]) => {
207
207
  edit({
208
- otype: 'vector3.add',
208
+ ot: 'vector3.add',
209
209
  key: nodeKey,
210
210
  path,
211
211
  value: delta,
@@ -32,7 +32,7 @@ const clockManager = new VectorClockManager();
32
32
  * Create initial text document state
33
33
  */
34
34
  export function createInitialTextState(
35
- sessionId: string,
35
+ client: string,
36
36
  initialSnapshot?: TextSnapshot
37
37
  ): TextDocumentState {
38
38
  if (initialSnapshot) {
@@ -41,26 +41,26 @@ export function createInitialTextState(
41
41
  journal: [],
42
42
  edits: { ops: [], baseText: null },
43
43
  snapshot: initialSnapshot,
44
- lamportTime: initialSnapshot.lamportTime,
44
+ lt: initialSnapshot.lt,
45
45
  vectorClock: { ...initialSnapshot.vectorClock },
46
- sessionId,
46
+ client: client,
47
47
  };
48
48
  }
49
49
 
50
- const rope = createRope(sessionId);
50
+ const rope = createRope(client);
51
51
  return {
52
52
  rope,
53
53
  journal: [],
54
54
  edits: { ops: [], baseText: null },
55
55
  snapshot: {
56
56
  rope,
57
- vectorClock: { [sessionId]: 0 },
58
- lamportTime: 0,
57
+ vectorClock: { [client]: 0 },
58
+ lt: 0,
59
59
  journalIndex: 0,
60
60
  },
61
- lamportTime: 0,
62
- vectorClock: { [sessionId]: 0 },
63
- sessionId,
61
+ lt: 0,
62
+ vectorClock: { [client]: 0 },
63
+ client: client,
64
64
  };
65
65
  }
66
66
 
@@ -82,9 +82,9 @@ export function onTextEdit(
82
82
 
83
83
  // Clone rope and apply operation (rope operations mutate in place)
84
84
  const rope = cloneRope(state.rope);
85
- if (op.otype === 'insert') {
85
+ if (op.ot === 'insert') {
86
86
  ropeApply(rope, op);
87
- } else if (op.otype === 'delete') {
87
+ } else if (op.ot === 'delete') {
88
88
  ropeApplyDelete(rope, op);
89
89
  }
90
90
 
@@ -108,17 +108,17 @@ export function commitTextEdits(
108
108
  ? coalesceTextOperations(state.edits.ops, { thresholdMs: coalescingThresholdMs })
109
109
  : state.edits.ops;
110
110
 
111
- const msgId = `${state.sessionId}-${Date.now()}-${Math.random().toString(36).slice(2)}`;
112
- const vectorClock = clockManager.increment(state.vectorClock, state.sessionId);
113
- const lamportTime = state.lamportTime + 1;
111
+ const msgId = `${state.client}-${Date.now()}-${Math.random().toString(36).slice(2)}`;
112
+ const vectorClock = clockManager.increment(state.vectorClock, state.client);
113
+ const lamportTime = state.lt + 1;
114
114
 
115
115
  const msg: TextMessage = {
116
116
  msgId,
117
- sessionId: state.sessionId,
117
+ client: state.client,
118
118
  operations,
119
119
  vectorClock,
120
- lamportTime,
121
- timestamp: Date.now(),
120
+ lt: lamportTime,
121
+ ts: Date.now(),
122
122
  description,
123
123
  };
124
124
 
@@ -133,7 +133,7 @@ export function commitTextEdits(
133
133
  journal: [...state.journal, entry],
134
134
  edits: { ops: [], baseText: null },
135
135
  vectorClock,
136
- lamportTime,
136
+ lt: lamportTime,
137
137
  },
138
138
  msg,
139
139
  };
@@ -153,9 +153,9 @@ export function cancelTextEdits(state: TextDocumentState): TextDocumentState {
153
153
  for (const entry of state.journal) {
154
154
  if (entry.deletedAt) continue; // Skip undone entries
155
155
  for (const op of entry.msg.operations) {
156
- if (op.otype === 'insert') {
156
+ if (op.ot === 'insert') {
157
157
  ropeApply(rope, op);
158
- } else if (op.otype === 'delete') {
158
+ } else if (op.ot === 'delete') {
159
159
  ropeApplyDelete(rope, op);
160
160
  }
161
161
  }
@@ -196,9 +196,9 @@ export function onTextRemoteMessage(
196
196
  // Clone rope and apply remote operations
197
197
  const rope = cloneRope(state.rope);
198
198
  for (const op of msg.operations) {
199
- if (op.otype === 'insert') {
199
+ if (op.ot === 'insert') {
200
200
  ropeApply(rope, op);
201
- } else if (op.otype === 'delete') {
201
+ } else if (op.ot === 'delete') {
202
202
  ropeApplyDelete(rope, op);
203
203
  }
204
204
  }
@@ -210,14 +210,14 @@ export function onTextRemoteMessage(
210
210
 
211
211
  // Merge vector clocks
212
212
  const vectorClock = clockManager.merge(state.vectorClock, msg.vectorClock);
213
- const lamportTime = Math.max(state.lamportTime, msg.lamportTime) + 1;
213
+ const lamportTime = Math.max(state.lt, msg.lt) + 1;
214
214
 
215
215
  return {
216
216
  ...state,
217
217
  rope,
218
218
  journal: [...state.journal, entry],
219
219
  vectorClock,
220
- lamportTime,
220
+ lt: lamportTime,
221
221
  };
222
222
  }
223
223
 
@@ -225,11 +225,11 @@ export function onTextRemoteMessage(
225
225
  * Initialize from server state
226
226
  */
227
227
  export function initTextFromServer(
228
- sessionId: string,
228
+ client: string,
229
229
  snapshot: TextSnapshot,
230
230
  journal: TextMessage[]
231
231
  ): TextDocumentState {
232
- let state = createInitialTextState(sessionId, snapshot);
232
+ let state = createInitialTextState(client, snapshot);
233
233
 
234
234
  // Apply journal entries
235
235
  for (const msg of journal) {
@@ -249,7 +249,7 @@ export function undoText(
249
249
  let lastIdx = -1;
250
250
  for (let i = state.journal.length - 1; i >= 0; i--) {
251
251
  if (
252
- state.journal[i].msg.sessionId === state.sessionId &&
252
+ state.journal[i].msg.client === state.client &&
253
253
  !state.journal[i].deletedAt
254
254
  ) {
255
255
  lastIdx = i;
@@ -271,24 +271,24 @@ export function undoText(
271
271
  for (const entry of journal) {
272
272
  if (entry.deletedAt) continue;
273
273
  for (const op of entry.msg.operations) {
274
- if (op.otype === 'insert') {
274
+ if (op.ot === 'insert') {
275
275
  ropeApply(rope, op);
276
- } else if (op.otype === 'delete') {
276
+ } else if (op.ot === 'delete') {
277
277
  ropeApplyDelete(rope, op);
278
278
  }
279
279
  }
280
280
  }
281
281
 
282
282
  // Create undo message
283
- const vectorClock = clockManager.increment(state.vectorClock, state.sessionId);
284
- const lamportTime = state.lamportTime + 1;
283
+ const vectorClock = clockManager.increment(state.vectorClock, state.client);
284
+ const lamportTime = state.lt + 1;
285
285
  const msg: TextMessage = {
286
- msgId: `${state.sessionId}-undo-${Date.now()}`,
287
- sessionId: state.sessionId,
288
- operations: [{ otype: 'delete', deletions: [] }], // Placeholder for undo marker
286
+ msgId: `${state.client}-undo-${Date.now()}`,
287
+ client: state.client,
288
+ operations: [{ ot: 'delete', rm: [] }], // Placeholder for undo marker
289
289
  vectorClock,
290
- lamportTime,
291
- timestamp: Date.now(),
290
+ lt: lamportTime,
291
+ ts: Date.now(),
292
292
  description: 'undo',
293
293
  };
294
294
 
@@ -298,7 +298,7 @@ export function undoText(
298
298
  rope,
299
299
  journal,
300
300
  vectorClock,
301
- lamportTime,
301
+ lt: lamportTime,
302
302
  },
303
303
  msg,
304
304
  };
@@ -314,7 +314,7 @@ export function redoText(
314
314
  let lastIdx = -1;
315
315
  for (let i = state.journal.length - 1; i >= 0; i--) {
316
316
  if (
317
- state.journal[i].msg.sessionId === state.sessionId &&
317
+ state.journal[i].msg.client === state.client &&
318
318
  state.journal[i].deletedAt
319
319
  ) {
320
320
  lastIdx = i;
@@ -337,24 +337,24 @@ export function redoText(
337
337
  for (const e of journal) {
338
338
  if (e.deletedAt) continue;
339
339
  for (const op of e.msg.operations) {
340
- if (op.otype === 'insert') {
340
+ if (op.ot === 'insert') {
341
341
  ropeApply(rope, op);
342
- } else if (op.otype === 'delete') {
342
+ } else if (op.ot === 'delete') {
343
343
  ropeApplyDelete(rope, op);
344
344
  }
345
345
  }
346
346
  }
347
347
 
348
348
  // Create redo message
349
- const vectorClock = clockManager.increment(state.vectorClock, state.sessionId);
350
- const lamportTime = state.lamportTime + 1;
349
+ const vectorClock = clockManager.increment(state.vectorClock, state.client);
350
+ const lamportTime = state.lt + 1;
351
351
  const msg: TextMessage = {
352
- msgId: `${state.sessionId}-redo-${Date.now()}`,
353
- sessionId: state.sessionId,
352
+ msgId: `${state.client}-redo-${Date.now()}`,
353
+ client: state.client,
354
354
  operations: entry.msg.operations,
355
355
  vectorClock,
356
- lamportTime,
357
- timestamp: Date.now(),
356
+ lt: lamportTime,
357
+ ts: Date.now(),
358
358
  description: 'redo',
359
359
  };
360
360
 
@@ -364,7 +364,7 @@ export function redoText(
364
364
  rope,
365
365
  journal,
366
366
  vectorClock,
367
- lamportTime,
367
+ lt: lamportTime,
368
368
  },
369
369
  msg,
370
370
  };