@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
@@ -15,10 +15,10 @@ import type { VectorClock } from '../state/VectorClock.js';
15
15
  export interface CRDTMessage {
16
16
  // === CRDT Metadata (wrapper) ===
17
17
  id: string; // Message ID (UUID)
18
- sessionId: string; // Session that created this message
18
+ client: string; // Session that created this message
19
19
  clock: VectorClock; // Vector clock for causal ordering
20
- lamportTime: number; // Lamport timestamp for total ordering
21
- timestamp: number; // Wall-clock time (seconds since epoch)
20
+ lt: number; // Lamport timestamp for total ordering
21
+ ts: number; // Wall-clock time (seconds since epoch)
22
22
 
23
23
  // === Operations (batch) ===
24
24
  ops: Operation[]; // One or more operations
@@ -29,7 +29,7 @@ export interface CRDTMessage {
29
29
  */
30
30
  export interface BaseOp {
31
31
  key: string; // Node key (human-friendly, e.g., 'cube-1', 'player', 'scene')
32
- otype: string; // dtype operation: 'number.set', 'vector3.add', etc.
32
+ ot: string; // dtype operation: 'number.set', 'vector3.add', etc.
33
33
  path: string; // Property path: 'color', 'transform.position', etc.
34
34
  }
35
35
 
@@ -39,35 +39,35 @@ export interface BaseOp {
39
39
 
40
40
  export interface NumberSetOp extends BaseOp {
41
41
  key: string;
42
- otype: 'number.set';
42
+ ot: 'number.set';
43
43
  path: string;
44
44
  value: number;
45
45
  }
46
46
 
47
47
  export interface NumberAddOp extends BaseOp {
48
48
  key: string;
49
- otype: 'number.add';
49
+ ot: 'number.add';
50
50
  path: string;
51
51
  value: number;
52
52
  }
53
53
 
54
54
  export interface NumberMultiplyOp extends BaseOp {
55
55
  key: string;
56
- otype: 'number.multiply';
56
+ ot: 'number.multiply';
57
57
  path: string;
58
58
  value: number;
59
59
  }
60
60
 
61
61
  export interface NumberMinOp extends BaseOp {
62
62
  key: string;
63
- otype: 'number.min';
63
+ ot: 'number.min';
64
64
  path: string;
65
65
  value: number;
66
66
  }
67
67
 
68
68
  export interface NumberMaxOp extends BaseOp {
69
69
  key: string;
70
- otype: 'number.max';
70
+ ot: 'number.max';
71
71
  path: string;
72
72
  value: number;
73
73
  }
@@ -78,14 +78,14 @@ export interface NumberMaxOp extends BaseOp {
78
78
 
79
79
  export interface StringSetOp extends BaseOp {
80
80
  key: string;
81
- otype: 'string.set';
81
+ ot: 'string.set';
82
82
  path: string;
83
83
  value: string;
84
84
  }
85
85
 
86
86
  export interface StringConcatOp extends BaseOp {
87
87
  key: string;
88
- otype: 'string.concat';
88
+ ot: 'string.concat';
89
89
  path: string;
90
90
  value: string;
91
91
  separator?: string;
@@ -101,7 +101,7 @@ export interface StringConcatOp extends BaseOp {
101
101
  */
102
102
  export interface TextInitOp extends BaseOp {
103
103
  key: string;
104
- otype: 'text.init';
104
+ ot: 'text.init';
105
105
  path: string;
106
106
  value?: string; // Optional initial content
107
107
  }
@@ -113,7 +113,7 @@ export interface TextInitOp extends BaseOp {
113
113
  */
114
114
  export interface TextInsertOp extends BaseOp {
115
115
  key: string;
116
- otype: 'text.insert';
116
+ ot: 'text.insert';
117
117
  path: string;
118
118
  // Position-based format (local convenience)
119
119
  position?: number;
@@ -133,13 +133,13 @@ export interface TextInsertOp extends BaseOp {
133
133
  */
134
134
  export interface TextDeleteOp extends BaseOp {
135
135
  key: string;
136
- otype: 'text.delete';
136
+ ot: 'text.delete';
137
137
  path: string;
138
138
  // Position-based format (local convenience)
139
139
  position?: number;
140
140
  length?: number;
141
- // CRDT metadata format (network sync)
142
- deletions?: Array<{ id: string; length: number }>; // ItemId string format
141
+ // CRDT metadata format (network sync) - compressed tuple format
142
+ deletions?: Array<[string, number]>; // [[itemId, length], ...]
143
143
  }
144
144
 
145
145
  /**
@@ -149,7 +149,7 @@ export interface TextDeleteOp extends BaseOp {
149
149
  */
150
150
  export interface TextReplaceOp extends BaseOp {
151
151
  key: string;
152
- otype: 'text.replace';
152
+ ot: 'text.replace';
153
153
  path: string;
154
154
  // Position-based format (local convenience)
155
155
  position?: number;
@@ -170,21 +170,21 @@ export interface TextReplaceOp extends BaseOp {
170
170
 
171
171
  export interface BooleanSetOp extends BaseOp {
172
172
  key: string;
173
- otype: 'boolean.set';
173
+ ot: 'boolean.set';
174
174
  path: string;
175
175
  value: boolean;
176
176
  }
177
177
 
178
178
  export interface BooleanOrOp extends BaseOp {
179
179
  key: string;
180
- otype: 'boolean.or';
180
+ ot: 'boolean.or';
181
181
  path: string;
182
182
  value: boolean;
183
183
  }
184
184
 
185
185
  export interface BooleanAndOp extends BaseOp {
186
186
  key: string;
187
- otype: 'boolean.and';
187
+ ot: 'boolean.and';
188
188
  path: string;
189
189
  value: boolean;
190
190
  }
@@ -195,21 +195,21 @@ export interface BooleanAndOp extends BaseOp {
195
195
 
196
196
  export interface Vector3SetOp extends BaseOp {
197
197
  key: string;
198
- otype: 'vector3.set';
198
+ ot: 'vector3.set';
199
199
  path: string;
200
200
  value: [number, number, number];
201
201
  }
202
202
 
203
203
  export interface Vector3AddOp extends BaseOp {
204
204
  key: string;
205
- otype: 'vector3.add';
205
+ ot: 'vector3.add';
206
206
  path: string;
207
207
  value: [number, number, number];
208
208
  }
209
209
 
210
210
  export interface Vector3MultiplyOp extends BaseOp {
211
211
  key: string;
212
- otype: 'vector3.multiply';
212
+ ot: 'vector3.multiply';
213
213
  path: string;
214
214
  value: [number, number, number];
215
215
  }
@@ -232,15 +232,15 @@ export type EulerOrder = 'XYZ' | 'XZY' | 'YXZ' | 'YZX' | 'ZXY' | 'ZYX';
232
232
  *
233
233
  * @example
234
234
  * // Intrinsic XYZ rotation (default): rotate around body's X, then Y, then Z
235
- * { otype: 'vector3.applyEuler', key: 'arm', path: 'direction', value: [0, Math.PI/2, 0] }
235
+ * { ot: 'vector3.applyEuler', key: 'arm', path: 'direction', value: [0, Math.PI/2, 0] }
236
236
  *
237
237
  * // Extrinsic XYZ rotation: rotate around fixed X, then Y, then Z
238
238
  * // Note: Extrinsic XYZ is equivalent to Intrinsic ZYX
239
- * { otype: 'vector3.applyEuler', key: 'arm', path: 'direction', value: [0, Math.PI/2, 0], intrinsic: false }
239
+ * { ot: 'vector3.applyEuler', key: 'arm', path: 'direction', value: [0, Math.PI/2, 0], intrinsic: false }
240
240
  */
241
241
  export interface Vector3ApplyEulerOp extends BaseOp {
242
242
  key: string;
243
- otype: 'vector3.applyEuler';
243
+ ot: 'vector3.applyEuler';
244
244
  path: string;
245
245
  value: [number, number, number];
246
246
  order?: EulerOrder;
@@ -249,7 +249,7 @@ export interface Vector3ApplyEulerOp extends BaseOp {
249
249
 
250
250
  export interface Vector3ApplyQuaternionOp extends BaseOp {
251
251
  key: string;
252
- otype: 'vector3.applyQuaternion';
252
+ ot: 'vector3.applyQuaternion';
253
253
  path: string;
254
254
  value: [number, number, number, number]; // [x, y, z, w]
255
255
  }
@@ -260,14 +260,14 @@ export interface Vector3ApplyQuaternionOp extends BaseOp {
260
260
 
261
261
  export interface EulerSetOp extends BaseOp {
262
262
  key: string;
263
- otype: 'euler.set';
263
+ ot: 'euler.set';
264
264
  path: string;
265
265
  value: [number, number, number]; // [x, y, z] in radians
266
266
  }
267
267
 
268
268
  export interface EulerAddOp extends BaseOp {
269
269
  key: string;
270
- otype: 'euler.add';
270
+ ot: 'euler.add';
271
271
  path: string;
272
272
  value: [number, number, number]; // [x, y, z] in radians
273
273
  }
@@ -278,14 +278,14 @@ export interface EulerAddOp extends BaseOp {
278
278
 
279
279
  export interface QuaternionSetOp extends BaseOp {
280
280
  key: string;
281
- otype: 'quaternion.set';
281
+ ot: 'quaternion.set';
282
282
  path: string;
283
283
  value: [number, number, number, number];
284
284
  }
285
285
 
286
286
  export interface QuaternionMultiplyOp extends BaseOp {
287
287
  key: string;
288
- otype: 'quaternion.multiply';
288
+ ot: 'quaternion.multiply';
289
289
  path: string;
290
290
  value: [number, number, number, number];
291
291
  }
@@ -296,14 +296,14 @@ export interface QuaternionMultiplyOp extends BaseOp {
296
296
 
297
297
  export interface ColorSetOp extends BaseOp {
298
298
  key: string;
299
- otype: 'color.set';
299
+ ot: 'color.set';
300
300
  path: string;
301
301
  value: string; // Hex color
302
302
  }
303
303
 
304
304
  export interface ColorBlendOp extends BaseOp {
305
305
  key: string;
306
- otype: 'color.blend';
306
+ ot: 'color.blend';
307
307
  path: string;
308
308
  value: string;
309
309
  }
@@ -314,28 +314,28 @@ export interface ColorBlendOp extends BaseOp {
314
314
 
315
315
  export interface ArraySetOp extends BaseOp {
316
316
  key: string;
317
- otype: 'array.set';
317
+ ot: 'array.set';
318
318
  path: string;
319
319
  value: any[];
320
320
  }
321
321
 
322
322
  export interface ArrayPushOp extends BaseOp {
323
323
  key: string;
324
- otype: 'array.push';
324
+ ot: 'array.push';
325
325
  path: string;
326
326
  value: any;
327
327
  }
328
328
 
329
329
  export interface ArrayUnionOp extends BaseOp {
330
330
  key: string;
331
- otype: 'array.union';
331
+ ot: 'array.union';
332
332
  path: string;
333
333
  value: any[];
334
334
  }
335
335
 
336
336
  export interface ArrayRemoveOp extends BaseOp {
337
337
  key: string;
338
- otype: 'array.remove';
338
+ ot: 'array.remove';
339
339
  path: string;
340
340
  value: any;
341
341
  }
@@ -346,14 +346,14 @@ export interface ArrayRemoveOp extends BaseOp {
346
346
 
347
347
  export interface ObjectSetOp extends BaseOp {
348
348
  key: string;
349
- otype: 'object.set';
349
+ ot: 'object.set';
350
350
  path: string;
351
351
  value: Record<string, any>;
352
352
  }
353
353
 
354
354
  export interface ObjectMergeOp extends BaseOp {
355
355
  key: string;
356
- otype: 'object.merge';
356
+ ot: 'object.merge';
357
357
  path: string;
358
358
  value: Record<string, any>;
359
359
  }
@@ -367,7 +367,7 @@ export interface ObjectMergeOp extends BaseOp {
367
367
  */
368
368
  export interface NodeInsertOp extends BaseOp {
369
369
  key: string; // Parent node's key
370
- otype: 'node.insert';
370
+ ot: 'node.insert';
371
371
  path: 'children';
372
372
  value: {
373
373
  key: string; // New node's key (also used as id)
@@ -382,7 +382,7 @@ export interface NodeInsertOp extends BaseOp {
382
382
  */
383
383
  export interface NodeRemoveOp extends BaseOp {
384
384
  key: string; // Parent node's key
385
- otype: 'node.remove';
385
+ ot: 'node.remove';
386
386
  path: 'children';
387
387
  value: string; // Node key to remove
388
388
  }
@@ -392,7 +392,7 @@ export interface NodeRemoveOp extends BaseOp {
392
392
  */
393
393
  export interface NodeMoveOp extends BaseOp {
394
394
  key: string; // Old parent node's key
395
- otype: 'node.move';
395
+ ot: 'node.move';
396
396
  path: 'children';
397
397
  value: {
398
398
  nodeKey: string; // Node key to move
@@ -409,7 +409,7 @@ export interface NodeMoveOp extends BaseOp {
409
409
  */
410
410
  export interface MetaUndoOp {
411
411
  key: '_meta';
412
- otype: 'meta.undo';
412
+ ot: 'meta.undo';
413
413
  path: '_meta';
414
414
  targetMsgId: string; // Message ID to undo
415
415
  }
@@ -419,7 +419,7 @@ export interface MetaUndoOp {
419
419
  */
420
420
  export interface MetaRedoOp {
421
421
  key: '_meta';
422
- otype: 'meta.redo';
422
+ ot: 'meta.redo';
423
423
  path: '_meta';
424
424
  targetMsgId: string; // Message ID to redo
425
425
  }
@@ -450,8 +450,8 @@ export type Operation =
450
450
  * Per-property LWW metadata
451
451
  */
452
452
  export interface PropertyLWW {
453
- lamportTime: number;
454
- sessionId: string;
453
+ lt: number;
454
+ client: string;
455
455
  }
456
456
 
457
457
  /**
@@ -484,6 +484,6 @@ export interface SceneNode {
484
484
  export interface SceneGraph {
485
485
  nodes: Record<string, SceneNode>; // Flattened map by key
486
486
  rootKey: string; // Root node key
487
- lww: Record<string, PropertyLWW>; // Global LWW map: "nodeKey.path" → {lamportTime, sessionId}
487
+ lww: Record<string, PropertyLWW>; // Global LWW map: "nodeKey.path" → {lamportTime, client}
488
488
  tombstones: Record<string, number>; // Deleted nodes: nodeKey → deletedAt timestamp
489
489
  }
@@ -25,20 +25,20 @@ export class OperationValidator {
25
25
  errors.push('id is required and must be a string');
26
26
  }
27
27
 
28
- if (!msg.sessionId || typeof msg.sessionId !== 'string') {
29
- errors.push('sessionId is required and must be a string');
28
+ if (!msg.client || typeof msg.client !== 'string') {
29
+ errors.push('client is required and must be a string');
30
30
  }
31
31
 
32
32
  if (!msg.clock || typeof msg.clock !== 'object') {
33
33
  errors.push('clock must be an object');
34
34
  }
35
35
 
36
- if (typeof msg.lamportTime !== 'number') {
37
- errors.push('lamportTime must be a number');
36
+ if (typeof msg.lt !== 'number') {
37
+ errors.push('lt must be a number');
38
38
  }
39
39
 
40
- if (typeof msg.timestamp !== 'number') {
41
- errors.push('timestamp must be a number');
40
+ if (typeof msg.ts !== 'number') {
41
+ errors.push('ts must be a number');
42
42
  }
43
43
 
44
44
  if (!Array.isArray(msg.ops)) {
@@ -72,17 +72,17 @@ export class OperationValidator {
72
72
  errors.push('key must be a string');
73
73
  }
74
74
 
75
- if (!op.otype || typeof op.otype !== 'string') {
76
- errors.push('otype is required and must be a string');
75
+ if (!op.ot || typeof op.ot !== 'string') {
76
+ errors.push('ot is required and must be a string');
77
77
  }
78
78
 
79
79
  if (!op.path || typeof op.path !== 'string') {
80
80
  errors.push('path is required and must be a string');
81
81
  }
82
82
 
83
- // Validate based on otype
84
- if (op.otype) {
85
- const [dtype, operation] = op.otype.split('.');
83
+ // Validate based on ot
84
+ if (op.ot) {
85
+ const [dtype, operation] = op.ot.split('.');
86
86
 
87
87
  switch (dtype) {
88
88
  case 'number':
@@ -50,8 +50,8 @@ export function NodeInsert(
50
50
  // Register in LWW map
51
51
  const lwwKey = `${nodeKey}.${propKey}`;
52
52
  draft.lww[lwwKey] = {
53
- lamportTime: meta.lamportTime,
54
- sessionId: meta.sessionId,
53
+ lt: meta.lt,
54
+ client: meta.client,
55
55
  };
56
56
  }
57
57
  }
@@ -89,7 +89,7 @@ export function NodeRemove(
89
89
  }
90
90
 
91
91
  // Tombstone: mark as deleted at root level
92
- draft.tombstones[nodeKey] = meta.timestamp;
92
+ draft.tombstones[nodeKey] = meta.ts;
93
93
 
94
94
  // Remove from parent's children
95
95
  const parentNode = draft.nodes[op.key];
@@ -101,7 +101,7 @@ export function TextInit(
101
101
  const node = draft.nodes[op.key];
102
102
  if (!node) return;
103
103
 
104
- const rope = getOrCreateRope(node, op.path, meta.sessionId);
104
+ const rope = getOrCreateRope(node, op.path, meta.client);
105
105
 
106
106
  if (op.value && op.value.length > 0) {
107
107
  insert(rope, 0, op.value);
@@ -121,25 +121,26 @@ export function TextInsert(
121
121
  const node = draft.nodes[op.key];
122
122
  if (!node) return;
123
123
 
124
- const rope = getOrCreateRope(node, op.path, meta.sessionId);
124
+ const rope = getOrCreateRope(node, op.path, meta.client);
125
125
 
126
- if (op.id !== undefined && op.content !== undefined && op.seq !== undefined) {
126
+ const value = (op as any).value;
127
+
128
+ if (op.id !== undefined && value !== undefined && op.seq !== undefined) {
127
129
  // CRDT metadata format (replay from journal)
130
+ // Value is already [anchor, content] tuple
128
131
  apply(rope, {
129
- otype: 'insert',
132
+ ot: 'insert',
130
133
  id: op.id,
131
- content: op.content,
132
- parentId: op.parentId ?? null,
134
+ value, // Pass tuple as-is
133
135
  seq: op.seq,
134
136
  ts: op.ts ?? Date.now() / 1000,
135
137
  });
136
138
  } else if (op.position !== undefined && op.value !== undefined) {
137
139
  // Position-based format (local edit) → convert to CRDT metadata in-place
138
- // so the op is order-independent when replayed from a different journal order.
139
- const crdtOp = insertWithSplit(rope, op.position, op.value, meta.sessionId);
140
+ const crdtOp = insertWithSplit(rope, op.position, op.value, meta.client);
140
141
  op.id = crdtOp.id;
141
- op.content = crdtOp.content;
142
- op.parentId = crdtOp.parentId;
142
+ // Pack tuple: [anchor, content] - crdtOp.value is already [anchor, content]
143
+ (op as any).value = crdtOp.value;
143
144
  op.seq = crdtOp.seq;
144
145
  op.ts = crdtOp.ts;
145
146
  }
@@ -161,13 +162,17 @@ export function TextDelete(
161
162
  const rope = getRope(node, op.path);
162
163
  if (!rope) return;
163
164
 
164
- if (op.deletions !== undefined) {
165
+ const deletionsArray = (op as any).rm;
166
+
167
+ if (deletionsArray !== undefined) {
165
168
  // CRDT metadata format (replay from journal)
166
- applyDelete(rope, { otype: 'delete', deletions: op.deletions });
169
+ // rm is already in tuple format: [[id, len], ...]
170
+ applyDelete(rope, { ot: 'delete', rm: deletionsArray });
167
171
  } else if (op.position !== undefined && op.length !== undefined) {
168
172
  // Position-based format (local edit) → convert to CRDT metadata in-place
169
173
  const crdtOp = remove(rope, op.position, op.length);
170
- op.deletions = crdtOp.deletions;
174
+ // rm is already in tuple format from remove()
175
+ (op as any).rm = crdtOp.rm;
171
176
  }
172
177
 
173
178
  // node[path] is the mutated TextRope
@@ -184,31 +189,32 @@ export function TextReplace(
184
189
  const node = draft.nodes[op.key];
185
190
  if (!node) return;
186
191
 
187
- const rope = getOrCreateRope(node, op.path, meta.sessionId);
192
+ const rope = getOrCreateRope(node, op.path, meta.client);
193
+
194
+ const deletionsArray = (op as any).rm;
195
+ const value = (op as any).value;
188
196
 
189
- if (op.deletions !== undefined && op.id !== undefined && op.seq !== undefined) {
197
+ if (deletionsArray !== undefined && op.id !== undefined && op.seq !== undefined) {
190
198
  // CRDT metadata format (replay from journal)
199
+ // Value is already [anchor, content] tuple
191
200
  applyReplace(rope, {
192
- otype: 'replace',
193
- delete: { otype: 'delete', deletions: op.deletions },
194
- insert: {
195
- otype: 'insert',
196
- id: op.id,
197
- content: op.content ?? '',
198
- parentId: op.parentId ?? null,
199
- seq: op.seq,
200
- ts: op.ts ?? Date.now() / 1000,
201
- },
201
+ ot: 'replace',
202
+ rm: deletionsArray,
203
+ id: op.id,
204
+ value: value ?? [null, ''], // Pass tuple as-is
205
+ seq: op.seq,
206
+ ts: op.ts ?? Date.now() / 1000,
202
207
  });
203
208
  } else if (op.position !== undefined && op.length !== undefined && op.value !== undefined) {
204
209
  // Position-based format (local edit) → convert to CRDT metadata in-place
205
- const crdtOp = replace(rope, op.position, op.length, op.value, meta.sessionId);
206
- op.deletions = crdtOp.delete.deletions;
207
- op.id = crdtOp.insert.id;
208
- op.content = crdtOp.insert.content;
209
- op.parentId = crdtOp.insert.parentId;
210
- op.seq = crdtOp.insert.seq;
211
- op.ts = crdtOp.insert.ts;
210
+ const crdtOp = replace(rope, op.position, op.length, op.value, meta.client);
211
+ // rm is already in tuple format from replace()
212
+ (op as any).rm = crdtOp.rm;
213
+ op.id = crdtOp.id;
214
+ // Pack tuple: [anchor, content] - crdtOp.value is already [anchor, content]
215
+ (op as any).value = crdtOp.value;
216
+ op.seq = crdtOp.seq;
217
+ op.ts = crdtOp.ts;
212
218
  }
213
219
 
214
220
  // node[path] is the mutated TextRope
@@ -9,10 +9,10 @@ import type { SceneGraph, SceneNode } from '../OperationTypes.js';
9
9
  * Operation metadata from the CRDTMessage envelope
10
10
  */
11
11
  export interface OpMeta {
12
- sessionId: string;
12
+ client: string;
13
13
  clock: VectorClock;
14
- lamportTime: number;
15
- timestamp: number;
14
+ lt: number;
15
+ ts: number;
16
16
  }
17
17
 
18
18
  /**
@@ -59,8 +59,8 @@ function getLWW(graph: SceneGraph, nodeKey: string, path: string): PropertyLWW |
59
59
  function setLWW(graph: SceneGraph, nodeKey: string, path: string, meta: OpMeta): void {
60
60
  const key = `${nodeKey}.${path}`;
61
61
  graph.lww[key] = {
62
- lamportTime: meta.lamportTime,
63
- sessionId: meta.sessionId,
62
+ lt: meta.lt,
63
+ client: meta.client,
64
64
  };
65
65
  }
66
66
 
@@ -69,7 +69,7 @@ function setLWW(graph: SceneGraph, nodeKey: string, path: string, meta: OpMeta):
69
69
  *
70
70
  * Resolution is **per-property**: concurrent writes to different paths
71
71
  * on the same node never conflict. For two writes to the same path,
72
- * higher lamportTime wins. On tie, higher sessionId wins (lexicographic).
72
+ * higher lamportTime wins. On tie, higher client ID wins (lexicographic).
73
73
  * This ensures deterministic conflict resolution regardless of
74
74
  * message arrival order.
75
75
  */
@@ -89,16 +89,16 @@ export function setNodePropertyLWW(
89
89
  return;
90
90
  }
91
91
 
92
- if (meta.lamportTime > current.lamportTime) {
92
+ if (meta.lt > current.lt) {
93
93
  // Strictly higher — always wins
94
94
  node[path] = value;
95
95
  setLWW(graph, node.key, path, meta);
96
- } else if (meta.lamportTime === current.lamportTime) {
97
- // Tie: higher sessionId wins, or same session (last-write-wins within message)
98
- if (meta.sessionId >= current.sessionId) {
96
+ } else if (meta.lt === current.lt) {
97
+ // Tie: higher client wins, or same client (last-write-wins within message)
98
+ if (meta.client >= current.client) {
99
99
  node[path] = value;
100
100
  setLWW(graph, node.key, path, meta);
101
101
  }
102
102
  }
103
- // meta.lamportTime < current.lamportTime → skip (stale write)
103
+ // meta.lt < current.lt → skip (stale write)
104
104
  }