@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
@@ -16,13 +16,13 @@ console.log('🎬 Example 04: Conflict Resolution\n');
16
16
 
17
17
  let graph = applyMessage(createEmptyGraph(), {
18
18
  id: 'setup',
19
- sessionId: 'server',
19
+ client: 'server',
20
20
  clock: { server: 1 },
21
- lamportTime: 0,
22
- timestamp: Date.now() / 1000,
21
+ lt: 0,
22
+ ts: Date.now() / 1000,
23
23
  ops: [{
24
24
  key: 'cube',
25
- otype: 'node.insert',
25
+ ot: 'node.insert',
26
26
  path: 'cube',
27
27
  value: {
28
28
  id: 'uuid-cube',
@@ -49,22 +49,22 @@ console.log('Alice sets color to red (lamport: 10) - arrives AFTER Bob\n');
49
49
  // Bob's message arrives first (higher lamport)
50
50
  graph = applyMessage(graph, {
51
51
  id: 'bob-color',
52
- sessionId: 'bob',
52
+ client: 'bob',
53
53
  clock: { bob: 1 },
54
- lamportTime: 11,
55
- timestamp: Date.now() / 1000,
56
- ops: [{ key: 'cube', otype: 'color.set', path: 'color', value: '#0000ff' }],
54
+ lt: 11,
55
+ ts: Date.now() / 1000,
56
+ ops: [{ key: 'cube', ot: 'color.set', path: 'color', value: '#0000ff' }],
57
57
  });
58
58
  console.log('After Bob (lamport 11):', graph.nodes['cube'].color);
59
59
 
60
60
  // Alice's message arrives later (lower lamport - should be ignored)
61
61
  graph = applyMessage(graph, {
62
62
  id: 'alice-color',
63
- sessionId: 'alice',
63
+ client: 'alice',
64
64
  clock: { alice: 1 },
65
- lamportTime: 10,
66
- timestamp: Date.now() / 1000,
67
- ops: [{ key: 'cube', otype: 'color.set', path: 'color', value: '#ff0000' }],
65
+ lt: 10,
66
+ ts: Date.now() / 1000,
67
+ ops: [{ key: 'cube', ot: 'color.set', path: 'color', value: '#ff0000' }],
68
68
  });
69
69
  console.log('After Alice (lamport 10):', graph.nodes['cube'].color);
70
70
 
@@ -81,41 +81,41 @@ console.log('Messages can arrive in any order, result is the same.\n');
81
81
  // Reset position
82
82
  graph = applyMessage(graph, {
83
83
  id: 'reset',
84
- sessionId: 'server',
84
+ client: 'server',
85
85
  clock: { server: 2 },
86
- lamportTime: 20,
87
- timestamp: Date.now() / 1000,
88
- ops: [{ key: 'cube', otype: 'vector3.set', path: 'transform.position', value: [0, 0, 0] }],
86
+ lt: 20,
87
+ ts: Date.now() / 1000,
88
+ ops: [{ key: 'cube', ot: 'vector3.set', path: 'transform.position', value: [0, 0, 0] }],
89
89
  });
90
90
 
91
91
  console.log('Position reset to [0, 0, 0]');
92
92
 
93
93
  // Apply in order: Alice, Bob, Charlie
94
94
  let graph1 = applyMessage(graph, {
95
- id: 'alice-move', sessionId: 'alice', clock: { alice: 2 }, lamportTime: 21, timestamp: Date.now() / 1000,
96
- ops: [{ key: 'cube', otype: 'vector3.add', path: 'transform.position', value: [1, 0, 0] }],
95
+ id: 'alice-move', client: 'alice', clock: { alice: 2 }, lt: 21, ts: Date.now() / 1000,
96
+ ops: [{ key: 'cube', ot: 'vector3.add', path: 'transform.position', value: [1, 0, 0] }],
97
97
  });
98
98
  graph1 = applyMessage(graph1, {
99
- id: 'bob-move', sessionId: 'bob', clock: { bob: 2 }, lamportTime: 22, timestamp: Date.now() / 1000,
100
- ops: [{ key: 'cube', otype: 'vector3.add', path: 'transform.position', value: [0, 2, 0] }],
99
+ id: 'bob-move', client: 'bob', clock: { bob: 2 }, lt: 22, ts: Date.now() / 1000,
100
+ ops: [{ key: 'cube', ot: 'vector3.add', path: 'transform.position', value: [0, 2, 0] }],
101
101
  });
102
102
  graph1 = applyMessage(graph1, {
103
- id: 'charlie-move', sessionId: 'charlie', clock: { charlie: 1 }, lamportTime: 23, timestamp: Date.now() / 1000,
104
- ops: [{ key: 'cube', otype: 'vector3.add', path: 'transform.position', value: [0, 0, 3] }],
103
+ id: 'charlie-move', client: 'charlie', clock: { charlie: 1 }, lt: 23, ts: Date.now() / 1000,
104
+ ops: [{ key: 'cube', ot: 'vector3.add', path: 'transform.position', value: [0, 0, 3] }],
105
105
  });
106
106
 
107
107
  // Apply in reverse order: Charlie, Bob, Alice
108
108
  let graph2 = applyMessage(graph, {
109
- id: 'charlie-move', sessionId: 'charlie', clock: { charlie: 1 }, lamportTime: 23, timestamp: Date.now() / 1000,
110
- ops: [{ key: 'cube', otype: 'vector3.add', path: 'transform.position', value: [0, 0, 3] }],
109
+ id: 'charlie-move', client: 'charlie', clock: { charlie: 1 }, lt: 23, ts: Date.now() / 1000,
110
+ ops: [{ key: 'cube', ot: 'vector3.add', path: 'transform.position', value: [0, 0, 3] }],
111
111
  });
112
112
  graph2 = applyMessage(graph2, {
113
- id: 'bob-move', sessionId: 'bob', clock: { bob: 2 }, lamportTime: 22, timestamp: Date.now() / 1000,
114
- ops: [{ key: 'cube', otype: 'vector3.add', path: 'transform.position', value: [0, 2, 0] }],
113
+ id: 'bob-move', client: 'bob', clock: { bob: 2 }, lt: 22, ts: Date.now() / 1000,
114
+ ops: [{ key: 'cube', ot: 'vector3.add', path: 'transform.position', value: [0, 2, 0] }],
115
115
  });
116
116
  graph2 = applyMessage(graph2, {
117
- id: 'alice-move', sessionId: 'alice', clock: { alice: 2 }, lamportTime: 21, timestamp: Date.now() / 1000,
118
- ops: [{ key: 'cube', otype: 'vector3.add', path: 'transform.position', value: [1, 0, 0] }],
117
+ id: 'alice-move', client: 'alice', clock: { alice: 2 }, lt: 21, ts: Date.now() / 1000,
118
+ ops: [{ key: 'cube', ot: 'vector3.add', path: 'transform.position', value: [1, 0, 0] }],
119
119
  });
120
120
 
121
121
  console.log('Order 1 (Alice → Bob → Charlie):', graph1.nodes['cube']['transform.position']);
@@ -135,13 +135,13 @@ console.log('Simulating two users editing simultaneously...\n');
135
135
  // Start fresh
136
136
  graph = applyMessage(createEmptyGraph(), {
137
137
  id: 'init',
138
- sessionId: 'server',
138
+ client: 'server',
139
139
  clock: { server: 1 },
140
- lamportTime: 0,
141
- timestamp: Date.now() / 1000,
140
+ lt: 0,
141
+ ts: Date.now() / 1000,
142
142
  ops: [{
143
143
  key: 'box',
144
- otype: 'node.insert',
144
+ ot: 'node.insert',
145
145
  path: 'box',
146
146
  value: {
147
147
  id: 'uuid-box',
@@ -158,26 +158,26 @@ console.log('Initial: color=#888888, position=[0,0,0]');
158
158
  // Alice drags and changes color
159
159
  const aliceMsg: CRDTMessage = {
160
160
  id: 'alice-edit',
161
- sessionId: 'alice',
161
+ client: 'alice',
162
162
  clock: { alice: 1 },
163
- lamportTime: 5,
164
- timestamp: Date.now() / 1000,
163
+ lt: 5,
164
+ ts: Date.now() / 1000,
165
165
  ops: [
166
- { key: 'box', otype: 'vector3.add', path: 'transform.position', value: [10, 0, 0] },
167
- { key: 'box', otype: 'color.set', path: 'color', value: '#ff0000' },
166
+ { key: 'box', ot: 'vector3.add', path: 'transform.position', value: [10, 0, 0] },
167
+ { key: 'box', ot: 'color.set', path: 'color', value: '#ff0000' },
168
168
  ],
169
169
  };
170
170
 
171
171
  // Bob drags and changes color (higher lamport)
172
172
  const bobMsg: CRDTMessage = {
173
173
  id: 'bob-edit',
174
- sessionId: 'bob',
174
+ client: 'bob',
175
175
  clock: { bob: 1 },
176
- lamportTime: 6,
177
- timestamp: Date.now() / 1000,
176
+ lt: 6,
177
+ ts: Date.now() / 1000,
178
178
  ops: [
179
- { key: 'box', otype: 'vector3.add', path: 'transform.position', value: [0, 5, 0] },
180
- { key: 'box', otype: 'color.set', path: 'color', value: '#00ff00' },
179
+ { key: 'box', ot: 'vector3.add', path: 'transform.position', value: [0, 5, 0] },
180
+ { key: 'box', ot: 'color.set', path: 'color', value: '#00ff00' },
181
181
  ],
182
182
  };
183
183
 
@@ -251,8 +251,8 @@ function processMessage(s: State, msg: CRDTMessage): State {
251
251
 
252
252
  // Init
253
253
  state.graph = applyMessage(state.graph, {
254
- id: 'init', sessionId: 'server', clock: { server: 1 }, lamportTime: 0, timestamp: Date.now() / 1000,
255
- ops: [{ key: 'obj', otype: 'node.insert', path: 'obj', value: { key: 'uuid', tag: 'Mesh', name: 'Object', color: '#ffffff' }}],
254
+ id: 'init', client: 'server', clock: { server: 1 }, lt: 0, ts: Date.now() / 1000,
255
+ ops: [{ key: 'obj', ot: 'node.insert', path: 'obj', value: { key: 'uuid', tag: 'Mesh', name: 'Object', color: '#ffffff' }}],
256
256
  });
257
257
 
258
258
  console.log('\n📖 State = { graph, journal }');
@@ -266,18 +266,18 @@ for (let i = 0; i < messages.length; i++) {
266
266
 
267
267
  const msg: CRDTMessage = {
268
268
  id: `msg-${m.lamport}`,
269
- sessionId: m.name,
269
+ client: m.name,
270
270
  clock: { [m.name]: 1 },
271
- lamportTime: m.lamport,
272
- timestamp: Date.now() / 1000,
273
- ops: [{ key: 'obj', otype: 'color.set', path: 'color', value: m.color }],
271
+ lt: m.lamport,
272
+ ts: Date.now() / 1000,
273
+ ops: [{ key: 'obj', ot: 'color.set', path: 'color', value: m.color }],
274
274
  };
275
275
 
276
276
  // Process: append to journal + apply to graph
277
277
  Object.assign(state, processMessage(state, msg));
278
278
 
279
279
  const applied = state.graph.nodes['obj'].color !== prevColor;
280
- const maxLamport = state.graph.nodes['obj']._crdt.lamportTime;
280
+ const maxLamport = state.graph.nodes['obj']._crdt.lt;
281
281
  const appliedStr = applied ? '✓ applied' : '✗ ignored';
282
282
 
283
283
  console.log(` ${i + 1} ${m.name.padEnd(16)} ${String(m.lamport).padStart(3)} ${String(maxLamport).padStart(3)} ${state.graph.nodes['obj'].color} ${appliedStr}`);
@@ -285,7 +285,7 @@ for (let i = 0; i < messages.length; i++) {
285
285
 
286
286
  console.log('─'.repeat(65));
287
287
  console.log(`\n state.journal: ${state.journal.length} messages`);
288
- console.log(` state.graph: color = ${state.graph.nodes['obj'].color} (maxLamport = ${state.graph.nodes['obj']._crdt.lamportTime})`);
288
+ console.log(` state.graph: color = ${state.graph.nodes['obj'].color} (maxLamport = ${state.graph.nodes['obj']._crdt.lt})`);
289
289
 
290
290
  const isCorrect = state.graph.nodes['obj'].color === '#ffff00';
291
291
  console.log(`\n${isCorrect ? '✅' : '❌'} Result: ${isCorrect ? 'CORRECT' : 'WRONG'}`);
@@ -304,19 +304,19 @@ const results: string[] = [];
304
304
 
305
305
  for (let i = 0; i < orders.length; i++) {
306
306
  let g = applyMessage(createEmptyGraph(), {
307
- id: 'init', sessionId: 'server', clock: { server: 1 }, lamportTime: 0, timestamp: Date.now() / 1000,
308
- ops: [{ key: 'obj', otype: 'node.insert', path: 'obj', value: { key: 'uuid', tag: 'Mesh', name: 'Object', color: '#ffffff' }}],
307
+ id: 'init', client: 'server', clock: { server: 1 }, lt: 0, ts: Date.now() / 1000,
308
+ ops: [{ key: 'obj', ot: 'node.insert', path: 'obj', value: { key: 'uuid', tag: 'Mesh', name: 'Object', color: '#ffffff' }}],
309
309
  });
310
310
 
311
311
  for (const idx of orders[i]) {
312
312
  const msg = messages[idx];
313
313
  g = applyMessage(g, {
314
314
  id: `msg-${msg.lamport}`,
315
- sessionId: msg.name,
315
+ client: msg.name,
316
316
  clock: { [msg.name]: 1 },
317
- lamportTime: msg.lamport,
318
- timestamp: Date.now() / 1000,
319
- ops: [{ key: 'obj', otype: 'color.set', path: 'color', value: msg.color }],
317
+ lt: msg.lamport,
318
+ ts: Date.now() / 1000,
319
+ ops: [{ key: 'obj', ot: 'color.set', path: 'color', value: msg.color }],
320
320
  });
321
321
  }
322
322
 
@@ -26,17 +26,17 @@ console.log('--- Example 1: No Coalescence ---\n');
26
26
 
27
27
  const messages1: CRDTMessage[] = [];
28
28
  const store1 = createGraph({
29
- sessionId: 'session-1',
29
+ client: 'session-1',
30
30
  coalescingEnabled: false, // No auto-commit
31
31
  onSend: (msg) => messages1.push(msg),
32
32
  });
33
33
 
34
34
  // User types "hello"
35
- store1.edit({ otype: 'text.insert', key: 'doc', path: 'content', position: 0, value: 'h' });
36
- store1.edit({ otype: 'text.insert', key: 'doc', path: 'content', position: 1, value: 'e' });
37
- store1.edit({ otype: 'text.insert', key: 'doc', path: 'content', position: 2, value: 'l' });
38
- store1.edit({ otype: 'text.insert', key: 'doc', path: 'content', position: 3, value: 'l' });
39
- store1.edit({ otype: 'text.insert', key: 'doc', path: 'content', position: 4, value: 'o' });
35
+ store1.edit({ ot: 'text.insert', key: 'doc', path: 'content', position: 0, value: 'h' });
36
+ store1.edit({ ot: 'text.insert', key: 'doc', path: 'content', position: 1, value: 'e' });
37
+ store1.edit({ ot: 'text.insert', key: 'doc', path: 'content', position: 2, value: 'l' });
38
+ store1.edit({ ot: 'text.insert', key: 'doc', path: 'content', position: 3, value: 'l' });
39
+ store1.edit({ ot: 'text.insert', key: 'doc', path: 'content', position: 4, value: 'o' });
40
40
 
41
41
  // Manual commit (no coalescence since coalescingEnabled = false)
42
42
  store1.commit();
@@ -53,7 +53,7 @@ console.log('--- Example 2: Batching Only ---\n');
53
53
 
54
54
  const messages2: CRDTMessage[] = [];
55
55
  const store2 = createGraph({
56
- sessionId: 'session-2',
56
+ client: 'session-2',
57
57
  coalescingEnabled: true, // Enable auto-commit
58
58
  coalescingDelayMs: 100, // Wait 100ms before sending
59
59
  coalescingThresholdMs: 0, // Don't merge operations (0 threshold)
@@ -61,11 +61,11 @@ const store2 = createGraph({
61
61
  });
62
62
 
63
63
  // User types "hello" quickly
64
- store2.edit({ otype: 'text.insert', key: 'doc', path: 'content', position: 0, value: 'h' });
65
- store2.edit({ otype: 'text.insert', key: 'doc', path: 'content', position: 1, value: 'e' });
66
- store2.edit({ otype: 'text.insert', key: 'doc', path: 'content', position: 2, value: 'l' });
67
- store2.edit({ otype: 'text.insert', key: 'doc', path: 'content', position: 3, value: 'l' });
68
- store2.edit({ otype: 'text.insert', key: 'doc', path: 'content', position: 4, value: 'o' });
64
+ store2.edit({ ot: 'text.insert', key: 'doc', path: 'content', position: 0, value: 'h' });
65
+ store2.edit({ ot: 'text.insert', key: 'doc', path: 'content', position: 1, value: 'e' });
66
+ store2.edit({ ot: 'text.insert', key: 'doc', path: 'content', position: 2, value: 'l' });
67
+ store2.edit({ ot: 'text.insert', key: 'doc', path: 'content', position: 3, value: 'l' });
68
+ store2.edit({ ot: 'text.insert', key: 'doc', path: 'content', position: 4, value: 'o' });
69
69
 
70
70
  // Wait for auto-commit
71
71
  await new Promise(resolve => setTimeout(resolve, 150));
@@ -82,7 +82,7 @@ console.log('--- Example 3: Full Coalescence ---\n');
82
82
 
83
83
  const messages3: CRDTMessage[] = [];
84
84
  const store3 = createGraph({
85
- sessionId: 'session-3',
85
+ client: 'session-3',
86
86
  coalescingEnabled: true, // Enable auto-commit
87
87
  coalescingDelayMs: 100, // Wait 100ms before sending
88
88
  coalescingThresholdMs: 1000, // Merge ops within 1 second
@@ -91,15 +91,15 @@ const store3 = createGraph({
91
91
 
92
92
  // User types "hello" quickly
93
93
  const startTime = Date.now();
94
- store3.edit({ otype: 'text.insert', key: 'doc', path: 'content', position: 0, value: 'h' });
94
+ store3.edit({ ot: 'text.insert', key: 'doc', path: 'content', position: 0, value: 'h' });
95
95
  await sleep(10);
96
- store3.edit({ otype: 'text.insert', key: 'doc', path: 'content', position: 1, value: 'e' });
96
+ store3.edit({ ot: 'text.insert', key: 'doc', path: 'content', position: 1, value: 'e' });
97
97
  await sleep(10);
98
- store3.edit({ otype: 'text.insert', key: 'doc', path: 'content', position: 2, value: 'l' });
98
+ store3.edit({ ot: 'text.insert', key: 'doc', path: 'content', position: 2, value: 'l' });
99
99
  await sleep(10);
100
- store3.edit({ otype: 'text.insert', key: 'doc', path: 'content', position: 3, value: 'l' });
100
+ store3.edit({ ot: 'text.insert', key: 'doc', path: 'content', position: 3, value: 'l' });
101
101
  await sleep(10);
102
- store3.edit({ otype: 'text.insert', key: 'doc', path: 'content', position: 4, value: 'o' });
102
+ store3.edit({ ot: 'text.insert', key: 'doc', path: 'content', position: 4, value: 'o' });
103
103
  const endTime = Date.now();
104
104
  const typingDuration = endTime - startTime;
105
105
 
@@ -109,7 +109,7 @@ await new Promise(resolve => setTimeout(resolve, 150));
109
109
  console.log(`Typing duration: ${typingDuration}ms (< 1000ms threshold)`);
110
110
  console.log(`Messages sent: ${messages3.length}`);
111
111
  console.log(`Operations in message: ${messages3[0].ops.length}`);
112
- if (messages3[0].ops[0].otype === 'text.insert') {
112
+ if (messages3[0].ops[0].ot === 'text.insert') {
113
113
  const op = messages3[0].ops[0] as any;
114
114
  console.log(`Operation content: "${op.content}"`);
115
115
  console.log('Result: 1 message with 1 operation (fully coalesced!)\n');
@@ -123,7 +123,7 @@ console.log('--- Example 4: Dynamic Configuration ---\n');
123
123
 
124
124
  const messages4: CRDTMessage[] = [];
125
125
  const store4 = createGraph({
126
- sessionId: 'session-4',
126
+ client: 'session-4',
127
127
  coalescingEnabled: false,
128
128
  onSend: (msg) => messages4.push(msg),
129
129
  });
@@ -144,15 +144,15 @@ console.log(` Delay: ${store4.getCoalescingDelay()}ms`);
144
144
  console.log(` Threshold: ${store4.getCoalescingThreshold()}ms\n`);
145
145
 
146
146
  // Type with new settings
147
- store4.edit({ otype: 'text.insert', key: 'doc', path: 'content', position: 0, value: 'h' });
147
+ store4.edit({ ot: 'text.insert', key: 'doc', path: 'content', position: 0, value: 'h' });
148
148
  await sleep(10);
149
- store4.edit({ otype: 'text.insert', key: 'doc', path: 'content', position: 1, value: 'i' });
149
+ store4.edit({ ot: 'text.insert', key: 'doc', path: 'content', position: 1, value: 'i' });
150
150
 
151
151
  // Wait for auto-commit
152
152
  await sleep(150);
153
153
 
154
154
  console.log(`Messages sent: ${messages4.length}`);
155
- if (messages4.length > 0 && messages4[0].ops[0].otype === 'text.insert') {
155
+ if (messages4.length > 0 && messages4[0].ops[0].ot === 'text.insert') {
156
156
  const op = messages4[0].ops[0] as any;
157
157
  console.log(`Coalesced content: "${op.content}"`);
158
158
  }
@@ -45,19 +45,19 @@ Demonstrates conflict resolution:
45
45
 
46
46
  ```typescript
47
47
  interface CRDTMessage {
48
- id: string; // Message ID
49
- sessionId: string; // Who sent this
50
- clock: VectorClock; // For causal ordering
51
- lamportTime: number; // For total ordering (LWW)
52
- timestamp: number; // Wall-clock time
53
- ops: Operation[]; // Batch of operations
48
+ id: string; // Message ID
49
+ client: string; // Who sent this
50
+ clock: VectorClock;// For causal ordering
51
+ lt: number; // Lamport time for total ordering (LWW)
52
+ ts: number; // Wall-clock timestamp
53
+ ops: Operation[]; // Batch of operations
54
54
  }
55
55
  ```
56
56
 
57
57
  ### Operation Types
58
58
 
59
- | otype | Merge Behavior | Use Case |
60
- |-------|---------------|----------|
59
+ | ot | Merge Behavior | Use Case |
60
+ |----|---------------|----------|
61
61
  | `node.insert` | Idempotent | Create node (use `parent` field to auto-add to parent's children) |
62
62
  | `node.remove` | Tombstone | Delete node |
63
63
  | `node.move` | Reparent | Move node to new parent |
@@ -83,9 +83,9 @@ Bob: position += [0, 3, 0]
83
83
  Result: position += [5, 3, 0] ✅
84
84
  ```
85
85
 
86
- **LWW** (`*.set`): Higher lamportTime wins
86
+ **LWW** (`*.set`): Higher lt wins
87
87
  ```
88
- Alice: color = red (lamport: 10)
89
- Bob: color = blue (lamport: 11)
90
- Result: color = blue ✅ (Bob's lamport is higher)
88
+ Alice: color = red (lt: 10)
89
+ Bob: color = blue (lt: 11)
90
+ Result: color = blue ✅ (Bob's lt is higher)
91
91
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vuer-ai/vuer-rtc",
3
- "version": "0.7.0",
3
+ "version": "0.8.2",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "CRDT-based real-time collaborative data structures",
@@ -21,30 +21,30 @@ import type { Operation } from '../operations/OperationTypes.js';
21
21
  * separately (coalesced later in commitEdits).
22
22
  */
23
23
  export function opDedupKey(op: Operation): string {
24
- if (op.otype === 'node.insert') return `${op.key}:${op.path}:insert:${(op as any).value.key}`;
25
- if (op.otype === 'node.remove') return `${op.key}:${op.path}:remove:${(op as any).value}`;
26
- if (op.otype === 'node.move') return `${op.key}:${op.path}:move:${(op as any).value.nodeKey}`;
24
+ if (op.ot === 'node.insert') return `${op.key}:${op.path}:insert:${(op as any).value.key}`;
25
+ if (op.ot === 'node.remove') return `${op.key}:${op.path}:remove:${(op as any).value}`;
26
+ if (op.ot === 'node.move') return `${op.key}:${op.path}:move:${(op as any).value.nodeKey}`;
27
27
  // Text operations: include position so consecutive inserts aren't deduped
28
- if (op.otype === 'text.insert') return `${op.key}:${op.path}:text.insert:${(op as any).position}`;
29
- if (op.otype === 'text.delete') return `${op.key}:${op.path}:text.delete:${(op as any).position}:${(op as any).length}`;
28
+ if (op.ot === 'text.insert') return `${op.key}:${op.path}:text.insert:${(op as any).position}`;
29
+ if (op.ot === 'text.delete') return `${op.key}:${op.path}:text.delete:${(op as any).position}:${(op as any).length}`;
30
30
  return `${op.key}:${op.path}`;
31
31
  }
32
32
 
33
33
  /**
34
34
  * Check if an operation type is additive (can be merged)
35
35
  */
36
- export function isAdditiveOp(otype: string): boolean {
37
- return otype === 'vector3.add' ||
38
- otype === 'number.add' ||
39
- otype === 'number.multiply' ||
40
- otype === 'quaternion.multiply';
36
+ export function isAdditiveOp(ot: string): boolean {
37
+ return ot === 'vector3.add' ||
38
+ ot === 'number.add' ||
39
+ ot === 'number.multiply' ||
40
+ ot === 'quaternion.multiply';
41
41
  }
42
42
 
43
43
  /**
44
44
  * Type for position-based text insert (used locally before CRDT conversion)
45
45
  */
46
46
  interface PositionTextInsertOp {
47
- otype: 'text.insert';
47
+ ot: 'text.insert';
48
48
  key: string;
49
49
  path: string;
50
50
  position: number;
@@ -55,7 +55,7 @@ interface PositionTextInsertOp {
55
55
  * Check if an operation is a position-based text insert
56
56
  */
57
57
  export function isPositionTextInsertOp(op: Operation): op is PositionTextInsertOp {
58
- return op.otype === 'text.insert' &&
58
+ return op.ot === 'text.insert' &&
59
59
  typeof (op as any).position === 'number' &&
60
60
  typeof (op as any).value === 'string';
61
61
  }
@@ -64,7 +64,7 @@ export function isPositionTextInsertOp(op: Operation): op is PositionTextInsertO
64
64
  * Type for position-based text delete
65
65
  */
66
66
  interface PositionTextDeleteOp {
67
- otype: 'text.delete';
67
+ ot: 'text.delete';
68
68
  key: string;
69
69
  path: string;
70
70
  position: number;
@@ -75,7 +75,7 @@ interface PositionTextDeleteOp {
75
75
  * Check if an operation is a text delete with position
76
76
  */
77
77
  export function isPositionTextDeleteOp(op: Operation): op is PositionTextDeleteOp {
78
- return op.otype === 'text.delete' &&
78
+ return op.ot === 'text.delete' &&
79
79
  typeof (op as any).position === 'number' &&
80
80
  typeof (op as any).length === 'number';
81
81
  }
@@ -144,7 +144,7 @@ export function coalesceTextOps(ops: Operation[]): Operation[] {
144
144
  if (pendingInsert === null) {
145
145
  // Start new pending insert
146
146
  pendingInsert = {
147
- otype: 'text.insert',
147
+ ot: 'text.insert',
148
148
  key: op.key,
149
149
  path: op.path,
150
150
  position: op.position,
@@ -157,7 +157,7 @@ export function coalesceTextOps(ops: Operation[]): Operation[] {
157
157
  ) {
158
158
  // Sequential insert - merge values
159
159
  pendingInsert = {
160
- otype: 'text.insert',
160
+ ot: 'text.insert',
161
161
  key: pendingInsert.key,
162
162
  path: pendingInsert.path,
163
163
  position: pendingInsert.position,
@@ -167,7 +167,7 @@ export function coalesceTextOps(ops: Operation[]): Operation[] {
167
167
  // Different target or non-sequential - flush pending and start new
168
168
  result.push(pendingInsert as unknown as Operation);
169
169
  pendingInsert = {
170
- otype: 'text.insert',
170
+ ot: 'text.insert',
171
171
  key: op.key,
172
172
  path: op.path,
173
173
  position: op.position,
@@ -176,7 +176,8 @@ export function coalesceTextOps(ops: Operation[]): Operation[] {
176
176
  }
177
177
  } else if (isPositionTextDeleteOp(op)) {
178
178
  // Check if operation has CRDT metadata - if so, don't coalesce it
179
- const hasCRDTMetadata = (op as any).deletions !== undefined;
179
+ // Support both old (deletions) and new (value) field names
180
+ const hasCRDTMetadata = (op as any).deletions !== undefined || (op as any).value !== undefined;
180
181
 
181
182
  if (hasCRDTMetadata) {
182
183
  // Flush any pending operations
@@ -202,7 +203,7 @@ export function coalesceTextOps(ops: Operation[]): Operation[] {
202
203
  // Coalesce deletes (only for position-based operations without CRDT metadata)
203
204
  if (pendingDelete === null) {
204
205
  pendingDelete = {
205
- otype: 'text.delete',
206
+ ot: 'text.delete',
206
207
  key: op.key,
207
208
  path: op.path,
208
209
  position: op.position,
@@ -219,7 +220,7 @@ export function coalesceTextOps(ops: Operation[]): Operation[] {
219
220
  if (isForwardDelete) {
220
221
  // Forward delete: accumulate lengths at same position
221
222
  pendingDelete = {
222
- otype: 'text.delete',
223
+ ot: 'text.delete',
223
224
  key: pendingDelete.key,
224
225
  path: pendingDelete.path,
225
226
  position: pendingDelete.position,
@@ -228,7 +229,7 @@ export function coalesceTextOps(ops: Operation[]): Operation[] {
228
229
  } else if (isBackwardDelete) {
229
230
  // Backward delete: position moves left, accumulate lengths
230
231
  pendingDelete = {
231
- otype: 'text.delete',
232
+ ot: 'text.delete',
232
233
  key: pendingDelete.key,
233
234
  path: pendingDelete.path,
234
235
  position: op.position, // use new (leftmost) position
@@ -238,7 +239,7 @@ export function coalesceTextOps(ops: Operation[]): Operation[] {
238
239
  // Non-consecutive - flush and start new
239
240
  result.push(pendingDelete as unknown as Operation);
240
241
  pendingDelete = {
241
- otype: 'text.delete',
242
+ ot: 'text.delete',
242
243
  key: op.key,
243
244
  path: op.path,
244
245
  position: op.position,
@@ -249,7 +250,7 @@ export function coalesceTextOps(ops: Operation[]): Operation[] {
249
250
  // Different key/path - flush and start new
250
251
  result.push(pendingDelete as unknown as Operation);
251
252
  pendingDelete = {
252
- otype: 'text.delete',
253
+ ot: 'text.delete',
253
254
  key: op.key,
254
255
  path: op.path,
255
256
  position: op.position,
@@ -284,8 +285,8 @@ export function coalesceTextOps(ops: Operation[]): Operation[] {
284
285
  /**
285
286
  * Merge two values for additive operations
286
287
  */
287
- export function mergeValues(otype: string, a: unknown, b: unknown): unknown {
288
- switch (otype) {
288
+ export function mergeValues(ot: string, a: unknown, b: unknown): unknown {
289
+ switch (ot) {
289
290
  case 'vector3.add': {
290
291
  const va = a as [number, number, number];
291
292
  const vb = b as [number, number, number];
@@ -325,9 +326,9 @@ export class EditBufferImpl {
325
326
  const key = opDedupKey(op);
326
327
  const existing = this.opsMap.get(key);
327
328
 
328
- if (existing && existing.otype === op.otype && isAdditiveOp(op.otype)) {
329
+ if (existing && existing.ot === op.ot && isAdditiveOp(op.ot)) {
329
330
  // Merge additive ops
330
- const mergedValue = mergeValues(op.otype, (existing as any).value, (op as any).value);
331
+ const mergedValue = mergeValues(op.ot, (existing as any).value, (op as any).value);
331
332
  this.opsMap.set(key, { ...existing, value: mergedValue } as Operation);
332
333
  } else {
333
334
  // New op or LWW replacement
@@ -18,7 +18,7 @@ import { createTextDocument } from '@vuer-ai/vuer-rtc';
18
18
 
19
19
  // Create a text document
20
20
  const doc = createTextDocument({
21
- sessionId: 'alice-123',
21
+ client: 'alice-123',
22
22
  onSend: (msg) => websocket.send(JSON.stringify(msg)),
23
23
  });
24
24
 
@@ -64,7 +64,7 @@ function createTextDocument(options: CreateTextDocumentOptions): TextDocumentSto
64
64
 
65
65
  | Option | Type | Required | Default | Description |
66
66
  |--------|------|----------|---------|-------------|
67
- | `sessionId` | `string` | ✓ | - | Unique session identifier |
67
+ | `client` | `string` | ✓ | - | Unique client identifier |
68
68
  | `initialSnapshot` | `TextSnapshot` | - | `undefined` | Initialize from server snapshot |
69
69
  | `onSend` | `(msg: TextMessage) => void` | - | `undefined` | Callback when messages are ready to send |
70
70
  | `onStateChange` | `(state: TextDocumentState) => void` | - | `undefined` | Callback on every state change |
@@ -78,7 +78,7 @@ function createTextDocument(options: CreateTextDocumentOptions): TextDocumentSto
78
78
 
79
79
  ```typescript
80
80
  const doc = createTextDocument({
81
- sessionId: 'user-123',
81
+ client: 'user-123',
82
82
  coalescingEnabled: true,
83
83
  coalescingDelayMs: 500,
84
84
  onSend: (msg) => {
@@ -116,7 +116,7 @@ function createTextDocumentFromServer(
116
116
  const { snapshot, journal } = await fetch('/api/doc/123').then(r => r.json());
117
117
 
118
118
  const doc = createTextDocumentFromServer({
119
- sessionId: 'user-123',
119
+ client: 'user-123',
120
120
  snapshot,
121
121
  journal,
122
122
  onSend: (msg) => ws.send(JSON.stringify(msg)),
@@ -507,7 +507,7 @@ interface TextDocumentState {
507
507
  snapshot: TextSnapshot; // Checkpoint
508
508
  lamportTime: number; // Logical clock
509
509
  vectorClock: VectorClock; // Causality tracking
510
- sessionId: string; // This session's ID
510
+ client: string; // This client's ID
511
511
  }
512
512
  ```
513
513
 
@@ -518,7 +518,7 @@ A message containing text operations.
518
518
  ```typescript
519
519
  interface TextMessage {
520
520
  msgId: string; // Unique message ID
521
- sessionId: string; // Sender's session ID
521
+ client: string; // Sender's client ID
522
522
  operations: TextOperation[]; // Operations in this message
523
523
  vectorClock: VectorClock; // Causal ordering
524
524
  lamportTime: number; // Total ordering
@@ -579,7 +579,7 @@ interface TextSnapshot {
579
579
  import { createTextDocument } from '@vuer-ai/vuer-rtc';
580
580
 
581
581
  const doc = createTextDocument({
582
- sessionId: 'local-user',
582
+ client: 'local-user',
583
583
  });
584
584
 
585
585
  // Edit
@@ -602,7 +602,7 @@ import { createTextDocument } from '@vuer-ai/vuer-rtc';
602
602
 
603
603
  const ws = new WebSocket('ws://server.com/doc/123');
604
604
  const doc = createTextDocument({
605
- sessionId: generateSessionId(),
605
+ client: generateClientId(),
606
606
  coalescingEnabled: true,
607
607
  coalescingDelayMs: 300,
608
608
  onSend: (msg) => {
@@ -652,7 +652,7 @@ function useTextDocument(docId: string) {
652
652
  useEffect(() => {
653
653
  const ws = new WebSocket(`ws://server/doc/${docId}`);
654
654
  const doc = createTextDocument({
655
- sessionId: generateSessionId(),
655
+ client: generateClientId(),
656
656
  coalescingEnabled: true,
657
657
  onSend: (msg) => ws.send(JSON.stringify(msg)),
658
658
  });