@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
@@ -23,7 +23,7 @@ describe('Phase 1 Graph Coalescence', () => {
23
23
  describe('number.add coalescence', () => {
24
24
  it('should merge consecutive number.add operations on same target', () => {
25
25
  const store = createGraph({
26
- sessionId: 'alice',
26
+ client: 'alice',
27
27
  coalescingEnabled: true,
28
28
  coalescingDelayMs: 0,
29
29
  coalescingThresholdMs: 1000,
@@ -31,20 +31,20 @@ describe('Phase 1 Graph Coalescence', () => {
31
31
  });
32
32
 
33
33
  // Create a score counter
34
- store.edit({ otype: 'node.insert', key: 'player', value: { score: 0 } });
34
+ store.edit({ ot: 'node.insert', key: 'player', value: { score: 0 } });
35
35
  store.commit('create player');
36
36
 
37
37
  // Add to score 3 times rapidly
38
- store.edit({ otype: 'number.add', key: 'player', path: 'score', value: 10 });
39
- store.edit({ otype: 'number.add', key: 'player', path: 'score', value: 5 });
40
- store.edit({ otype: 'number.add', key: 'player', path: 'score', value: 3 });
38
+ store.edit({ ot: 'number.add', key: 'player', path: 'score', value: 10 });
39
+ store.edit({ ot: 'number.add', key: 'player', path: 'score', value: 5 });
40
+ store.edit({ ot: 'number.add', key: 'player', path: 'score', value: 3 });
41
41
  store.commit('add to score');
42
42
 
43
43
  // Should coalesce into 1 operation with sum: 10 + 5 + 3 = 18
44
44
  const scoreMsg = messages[1];
45
45
  expect(scoreMsg.ops).toHaveLength(1);
46
46
  expect(scoreMsg.ops[0]).toMatchObject({
47
- otype: 'number.add',
47
+ ot: 'number.add',
48
48
  key: 'player',
49
49
  path: 'score',
50
50
  value: 18,
@@ -53,32 +53,32 @@ describe('Phase 1 Graph Coalescence', () => {
53
53
 
54
54
  it('should NOT merge number.add operations on different targets', () => {
55
55
  const store = createGraph({
56
- sessionId: 'alice',
56
+ client: 'alice',
57
57
  coalescingEnabled: true,
58
58
  coalescingDelayMs: 0,
59
59
  coalescingThresholdMs: 1000,
60
60
  onSend: (msg) => messages.push(msg),
61
61
  });
62
62
 
63
- store.edit({ otype: 'node.insert', key: 'player1', value: { score: 0 } });
64
- store.edit({ otype: 'node.insert', key: 'player2', value: { score: 0 } });
63
+ store.edit({ ot: 'node.insert', key: 'player1', value: { score: 0 } });
64
+ store.edit({ ot: 'node.insert', key: 'player2', value: { score: 0 } });
65
65
  store.commit('create players');
66
66
 
67
67
  // Add to different players
68
- store.edit({ otype: 'number.add', key: 'player1', path: 'score', value: 10 });
69
- store.edit({ otype: 'number.add', key: 'player2', path: 'score', value: 5 });
68
+ store.edit({ ot: 'number.add', key: 'player1', path: 'score', value: 10 });
69
+ store.edit({ ot: 'number.add', key: 'player2', path: 'score', value: 5 });
70
70
  store.commit('add to scores');
71
71
 
72
72
  // Should NOT coalesce (different keys)
73
73
  const scoreMsg = messages[1];
74
74
  expect(scoreMsg.ops).toHaveLength(2);
75
75
  expect(scoreMsg.ops[0]).toMatchObject({
76
- otype: 'number.add',
76
+ ot: 'number.add',
77
77
  key: 'player1',
78
78
  value: 10,
79
79
  });
80
80
  expect(scoreMsg.ops[1]).toMatchObject({
81
- otype: 'number.add',
81
+ ot: 'number.add',
82
82
  key: 'player2',
83
83
  value: 5,
84
84
  });
@@ -86,19 +86,19 @@ describe('Phase 1 Graph Coalescence', () => {
86
86
 
87
87
  it('should NOT merge number.add operations on different paths', () => {
88
88
  const store = createGraph({
89
- sessionId: 'alice',
89
+ client: 'alice',
90
90
  coalescingEnabled: true,
91
91
  coalescingDelayMs: 0,
92
92
  coalescingThresholdMs: 1000,
93
93
  onSend: (msg) => messages.push(msg),
94
94
  });
95
95
 
96
- store.edit({ otype: 'node.insert', key: 'player', value: { score: 0, health: 100 } });
96
+ store.edit({ ot: 'node.insert', key: 'player', value: { score: 0, health: 100 } });
97
97
  store.commit('create player');
98
98
 
99
99
  // Add to different properties
100
- store.edit({ otype: 'number.add', key: 'player', path: 'score', value: 10 });
101
- store.edit({ otype: 'number.add', key: 'player', path: 'health', value: -5 });
100
+ store.edit({ ot: 'number.add', key: 'player', path: 'score', value: 10 });
101
+ store.edit({ ot: 'number.add', key: 'player', path: 'health', value: -5 });
102
102
  store.commit('update stats');
103
103
 
104
104
  // Should NOT coalesce (different paths)
@@ -112,27 +112,27 @@ describe('Phase 1 Graph Coalescence', () => {
112
112
  describe('vector3.add coalescence', () => {
113
113
  it('should merge consecutive vector3.add operations on same target', () => {
114
114
  const store = createGraph({
115
- sessionId: 'alice',
115
+ client: 'alice',
116
116
  coalescingEnabled: true,
117
117
  coalescingDelayMs: 0,
118
118
  coalescingThresholdMs: 1000,
119
119
  onSend: (msg) => messages.push(msg),
120
120
  });
121
121
 
122
- store.edit({ otype: 'node.insert', key: 'player', value: { position: [0, 0, 0] } });
122
+ store.edit({ ot: 'node.insert', key: 'player', value: { position: [0, 0, 0] } });
123
123
  store.commit('create player');
124
124
 
125
125
  // Move player in multiple steps
126
- store.edit({ otype: 'vector3.add', key: 'player', path: 'position', value: [1, 0, 0] });
127
- store.edit({ otype: 'vector3.add', key: 'player', path: 'position', value: [0, 2, 0] });
128
- store.edit({ otype: 'vector3.add', key: 'player', path: 'position', value: [0, 0, 3] });
126
+ store.edit({ ot: 'vector3.add', key: 'player', path: 'position', value: [1, 0, 0] });
127
+ store.edit({ ot: 'vector3.add', key: 'player', path: 'position', value: [0, 2, 0] });
128
+ store.edit({ ot: 'vector3.add', key: 'player', path: 'position', value: [0, 0, 3] });
129
129
  store.commit('move player');
130
130
 
131
131
  // Should coalesce into 1 operation with sum: [1+0+0, 0+2+0, 0+0+3] = [1, 2, 3]
132
132
  const moveMsg = messages[1];
133
133
  expect(moveMsg.ops).toHaveLength(1);
134
134
  expect(moveMsg.ops[0]).toMatchObject({
135
- otype: 'vector3.add',
135
+ ot: 'vector3.add',
136
136
  key: 'player',
137
137
  path: 'position',
138
138
  value: [1, 2, 3],
@@ -141,19 +141,19 @@ describe('Phase 1 Graph Coalescence', () => {
141
141
 
142
142
  it('should NOT merge vector3.add operations on different targets', () => {
143
143
  const store = createGraph({
144
- sessionId: 'alice',
144
+ client: 'alice',
145
145
  coalescingEnabled: true,
146
146
  coalescingDelayMs: 0,
147
147
  coalescingThresholdMs: 1000,
148
148
  onSend: (msg) => messages.push(msg),
149
149
  });
150
150
 
151
- store.edit({ otype: 'node.insert', key: 'player', value: { position: [0, 0, 0], velocity: [0, 0, 0] } });
151
+ store.edit({ ot: 'node.insert', key: 'player', value: { position: [0, 0, 0], velocity: [0, 0, 0] } });
152
152
  store.commit('create player');
153
153
 
154
154
  // Add to different properties
155
- store.edit({ otype: 'vector3.add', key: 'player', path: 'position', value: [1, 0, 0] });
156
- store.edit({ otype: 'vector3.add', key: 'player', path: 'velocity', value: [0, 1, 0] });
155
+ store.edit({ ot: 'vector3.add', key: 'player', path: 'position', value: [1, 0, 0] });
156
+ store.edit({ ot: 'vector3.add', key: 'player', path: 'velocity', value: [0, 1, 0] });
157
157
  store.commit('update player');
158
158
 
159
159
  // Should NOT coalesce (different paths)
@@ -167,27 +167,27 @@ describe('Phase 1 Graph Coalescence', () => {
167
167
  describe('LWW (*.set) coalescence', () => {
168
168
  it('should keep only last number.set operation on same target', () => {
169
169
  const store = createGraph({
170
- sessionId: 'alice',
170
+ client: 'alice',
171
171
  coalescingEnabled: true,
172
172
  coalescingDelayMs: 0,
173
173
  coalescingThresholdMs: 1000,
174
174
  onSend: (msg) => messages.push(msg),
175
175
  });
176
176
 
177
- store.edit({ otype: 'node.insert', key: 'player', value: { score: 0 } });
177
+ store.edit({ ot: 'node.insert', key: 'player', value: { score: 0 } });
178
178
  store.commit('create player');
179
179
 
180
180
  // Set score multiple times
181
- store.edit({ otype: 'number.set', key: 'player', path: 'score', value: 10 });
182
- store.edit({ otype: 'number.set', key: 'player', path: 'score', value: 20 });
183
- store.edit({ otype: 'number.set', key: 'player', path: 'score', value: 30 });
181
+ store.edit({ ot: 'number.set', key: 'player', path: 'score', value: 10 });
182
+ store.edit({ ot: 'number.set', key: 'player', path: 'score', value: 20 });
183
+ store.edit({ ot: 'number.set', key: 'player', path: 'score', value: 30 });
184
184
  store.commit('set score');
185
185
 
186
186
  // Should keep only last set (LWW)
187
187
  const scoreMsg = messages[1];
188
188
  expect(scoreMsg.ops).toHaveLength(1);
189
189
  expect(scoreMsg.ops[0]).toMatchObject({
190
- otype: 'number.set',
190
+ ot: 'number.set',
191
191
  key: 'player',
192
192
  path: 'score',
193
193
  value: 30, // Only last value
@@ -196,27 +196,27 @@ describe('Phase 1 Graph Coalescence', () => {
196
196
 
197
197
  it('should keep only last string.set operation on same target', () => {
198
198
  const store = createGraph({
199
- sessionId: 'alice',
199
+ client: 'alice',
200
200
  coalescingEnabled: true,
201
201
  coalescingDelayMs: 0,
202
202
  coalescingThresholdMs: 1000,
203
203
  onSend: (msg) => messages.push(msg),
204
204
  });
205
205
 
206
- store.edit({ otype: 'node.insert', key: 'player', value: { name: '' } });
206
+ store.edit({ ot: 'node.insert', key: 'player', value: { name: '' } });
207
207
  store.commit('create player');
208
208
 
209
209
  // Set name multiple times
210
- store.edit({ otype: 'string.set', key: 'player', path: 'name', value: 'Alice' });
211
- store.edit({ otype: 'string.set', key: 'player', path: 'name', value: 'Bob' });
212
- store.edit({ otype: 'string.set', key: 'player', path: 'name', value: 'Charlie' });
210
+ store.edit({ ot: 'string.set', key: 'player', path: 'name', value: 'Alice' });
211
+ store.edit({ ot: 'string.set', key: 'player', path: 'name', value: 'Bob' });
212
+ store.edit({ ot: 'string.set', key: 'player', path: 'name', value: 'Charlie' });
213
213
  store.commit('set name');
214
214
 
215
215
  // Should keep only last set (LWW)
216
216
  const nameMsg = messages[1];
217
217
  expect(nameMsg.ops).toHaveLength(1);
218
218
  expect(nameMsg.ops[0]).toMatchObject({
219
- otype: 'string.set',
219
+ ot: 'string.set',
220
220
  key: 'player',
221
221
  path: 'name',
222
222
  value: 'Charlie', // Only last value
@@ -225,27 +225,27 @@ describe('Phase 1 Graph Coalescence', () => {
225
225
 
226
226
  it('should keep only last boolean.set operation on same target', () => {
227
227
  const store = createGraph({
228
- sessionId: 'alice',
228
+ client: 'alice',
229
229
  coalescingEnabled: true,
230
230
  coalescingDelayMs: 0,
231
231
  coalescingThresholdMs: 1000,
232
232
  onSend: (msg) => messages.push(msg),
233
233
  });
234
234
 
235
- store.edit({ otype: 'node.insert', key: 'player', value: { active: false } });
235
+ store.edit({ ot: 'node.insert', key: 'player', value: { active: false } });
236
236
  store.commit('create player');
237
237
 
238
238
  // Toggle active multiple times
239
- store.edit({ otype: 'boolean.set', key: 'player', path: 'active', value: true });
240
- store.edit({ otype: 'boolean.set', key: 'player', path: 'active', value: false });
241
- store.edit({ otype: 'boolean.set', key: 'player', path: 'active', value: true });
239
+ store.edit({ ot: 'boolean.set', key: 'player', path: 'active', value: true });
240
+ store.edit({ ot: 'boolean.set', key: 'player', path: 'active', value: false });
241
+ store.edit({ ot: 'boolean.set', key: 'player', path: 'active', value: true });
242
242
  store.commit('toggle active');
243
243
 
244
244
  // Should keep only last set (LWW)
245
245
  const activeMsg = messages[1];
246
246
  expect(activeMsg.ops).toHaveLength(1);
247
247
  expect(activeMsg.ops[0]).toMatchObject({
248
- otype: 'boolean.set',
248
+ ot: 'boolean.set',
249
249
  key: 'player',
250
250
  path: 'active',
251
251
  value: true, // Only last value
@@ -254,27 +254,27 @@ describe('Phase 1 Graph Coalescence', () => {
254
254
 
255
255
  it('should keep only last vector3.set operation on same target', () => {
256
256
  const store = createGraph({
257
- sessionId: 'alice',
257
+ client: 'alice',
258
258
  coalescingEnabled: true,
259
259
  coalescingDelayMs: 0,
260
260
  coalescingThresholdMs: 1000,
261
261
  onSend: (msg) => messages.push(msg),
262
262
  });
263
263
 
264
- store.edit({ otype: 'node.insert', key: 'player', value: { position: [0, 0, 0] } });
264
+ store.edit({ ot: 'node.insert', key: 'player', value: { position: [0, 0, 0] } });
265
265
  store.commit('create player');
266
266
 
267
267
  // Set position multiple times
268
- store.edit({ otype: 'vector3.set', key: 'player', path: 'position', value: [1, 0, 0] });
269
- store.edit({ otype: 'vector3.set', key: 'player', path: 'position', value: [2, 0, 0] });
270
- store.edit({ otype: 'vector3.set', key: 'player', path: 'position', value: [3, 0, 0] });
268
+ store.edit({ ot: 'vector3.set', key: 'player', path: 'position', value: [1, 0, 0] });
269
+ store.edit({ ot: 'vector3.set', key: 'player', path: 'position', value: [2, 0, 0] });
270
+ store.edit({ ot: 'vector3.set', key: 'player', path: 'position', value: [3, 0, 0] });
271
271
  store.commit('set position');
272
272
 
273
273
  // Should keep only last set (LWW)
274
274
  const posMsg = messages[1];
275
275
  expect(posMsg.ops).toHaveLength(1);
276
276
  expect(posMsg.ops[0]).toMatchObject({
277
- otype: 'vector3.set',
277
+ ot: 'vector3.set',
278
278
  key: 'player',
279
279
  path: 'position',
280
280
  value: [3, 0, 0], // Only last value
@@ -283,19 +283,19 @@ describe('Phase 1 Graph Coalescence', () => {
283
283
 
284
284
  it('should NOT merge set operations on different paths', () => {
285
285
  const store = createGraph({
286
- sessionId: 'alice',
286
+ client: 'alice',
287
287
  coalescingEnabled: true,
288
288
  coalescingDelayMs: 0,
289
289
  coalescingThresholdMs: 1000,
290
290
  onSend: (msg) => messages.push(msg),
291
291
  });
292
292
 
293
- store.edit({ otype: 'node.insert', key: 'player', value: { score: 0, health: 100 } });
293
+ store.edit({ ot: 'node.insert', key: 'player', value: { score: 0, health: 100 } });
294
294
  store.commit('create player');
295
295
 
296
296
  // Set different properties
297
- store.edit({ otype: 'number.set', key: 'player', path: 'score', value: 10 });
298
- store.edit({ otype: 'number.set', key: 'player', path: 'health', value: 50 });
297
+ store.edit({ ot: 'number.set', key: 'player', path: 'score', value: 10 });
298
+ store.edit({ ot: 'number.set', key: 'player', path: 'health', value: 50 });
299
299
  store.commit('set stats');
300
300
 
301
301
  // Should NOT coalesce (different paths)
@@ -309,23 +309,23 @@ describe('Phase 1 Graph Coalescence', () => {
309
309
  describe('Mixed operations', () => {
310
310
  it('should coalesce operations independently by type', () => {
311
311
  const store = createGraph({
312
- sessionId: 'alice',
312
+ client: 'alice',
313
313
  coalescingEnabled: true,
314
314
  coalescingDelayMs: 0,
315
315
  coalescingThresholdMs: 1000,
316
316
  onSend: (msg) => messages.push(msg),
317
317
  });
318
318
 
319
- store.edit({ otype: 'node.insert', key: 'player', value: { score: 0, position: [0, 0, 0], name: '' } });
319
+ store.edit({ ot: 'node.insert', key: 'player', value: { score: 0, position: [0, 0, 0], name: '' } });
320
320
  store.commit('create player');
321
321
 
322
322
  // Mix of operations
323
- store.edit({ otype: 'number.add', key: 'player', path: 'score', value: 10 });
324
- store.edit({ otype: 'number.add', key: 'player', path: 'score', value: 5 });
325
- store.edit({ otype: 'vector3.add', key: 'player', path: 'position', value: [1, 0, 0] });
326
- store.edit({ otype: 'vector3.add', key: 'player', path: 'position', value: [0, 2, 0] });
327
- store.edit({ otype: 'string.set', key: 'player', path: 'name', value: 'Alice' });
328
- store.edit({ otype: 'string.set', key: 'player', path: 'name', value: 'Bob' });
323
+ store.edit({ ot: 'number.add', key: 'player', path: 'score', value: 10 });
324
+ store.edit({ ot: 'number.add', key: 'player', path: 'score', value: 5 });
325
+ store.edit({ ot: 'vector3.add', key: 'player', path: 'position', value: [1, 0, 0] });
326
+ store.edit({ ot: 'vector3.add', key: 'player', path: 'position', value: [0, 2, 0] });
327
+ store.edit({ ot: 'string.set', key: 'player', path: 'name', value: 'Alice' });
328
+ store.edit({ ot: 'string.set', key: 'player', path: 'name', value: 'Bob' });
329
329
  store.commit('update player');
330
330
 
331
331
  // Should coalesce each type independently
@@ -334,21 +334,21 @@ describe('Phase 1 Graph Coalescence', () => {
334
334
 
335
335
  // number.add should be coalesced: 10 + 5 = 15
336
336
  expect(updateMsg.ops[0]).toMatchObject({
337
- otype: 'number.add',
337
+ ot: 'number.add',
338
338
  path: 'score',
339
339
  value: 15,
340
340
  });
341
341
 
342
342
  // vector3.add should be coalesced: [1, 0, 0] + [0, 2, 0] = [1, 2, 0]
343
343
  expect(updateMsg.ops[1]).toMatchObject({
344
- otype: 'vector3.add',
344
+ ot: 'vector3.add',
345
345
  path: 'position',
346
346
  value: [1, 2, 0],
347
347
  });
348
348
 
349
349
  // string.set should keep last: 'Bob'
350
350
  expect(updateMsg.ops[2]).toMatchObject({
351
- otype: 'string.set',
351
+ ot: 'string.set',
352
352
  path: 'name',
353
353
  value: 'Bob',
354
354
  });
@@ -18,7 +18,7 @@ describe('Graph Operation Coalescence (Integration)', () => {
18
18
  const messages: CRDTMessage[] = [];
19
19
 
20
20
  const store = createGraph({
21
- sessionId: 'alice',
21
+ client: 'alice',
22
22
  coalescingEnabled: true,
23
23
  coalescingDelayMs: 0,
24
24
  coalescingThresholdMs: 1000,
@@ -26,27 +26,27 @@ describe('Graph Operation Coalescence (Integration)', () => {
26
26
  });
27
27
 
28
28
  // Initialize scene and text node
29
- store.edit({ otype: 'node.init', key: 'scene', path: '', value: { tag: 'Scene' } });
29
+ store.edit({ ot: 'node.init', key: 'scene', path: '', value: { tag: 'Scene' } });
30
30
  store.commit('init scene');
31
31
 
32
- store.edit({ otype: 'node.insert', key: 'scene', path: 'children', value: { key: 'text-doc', tag: 'Text' } });
32
+ store.edit({ ot: 'node.insert', key: 'scene', path: 'children', value: { key: 'text-doc', tag: 'Text' } });
33
33
  store.commit('add text node');
34
34
 
35
- store.edit({ otype: 'text.init', key: 'text-doc', path: 'content', value: '' } as any);
35
+ store.edit({ ot: 'text.init', key: 'text-doc', path: 'content', value: '' } as any);
36
36
  store.commit('init text');
37
37
 
38
38
  // Type "Hello" (5 operations)
39
- store.edit({ otype: 'text.insert', key: 'text-doc', path: 'content', position: 0, value: 'H' } as any);
40
- store.edit({ otype: 'text.insert', key: 'text-doc', path: 'content', position: 1, value: 'e' } as any);
41
- store.edit({ otype: 'text.insert', key: 'text-doc', path: 'content', position: 2, value: 'l' } as any);
42
- store.edit({ otype: 'text.insert', key: 'text-doc', path: 'content', position: 3, value: 'l' } as any);
43
- store.edit({ otype: 'text.insert', key: 'text-doc', path: 'content', position: 4, value: 'o' } as any);
39
+ store.edit({ ot: 'text.insert', key: 'text-doc', path: 'content', position: 0, value: 'H' } as any);
40
+ store.edit({ ot: 'text.insert', key: 'text-doc', path: 'content', position: 1, value: 'e' } as any);
41
+ store.edit({ ot: 'text.insert', key: 'text-doc', path: 'content', position: 2, value: 'l' } as any);
42
+ store.edit({ ot: 'text.insert', key: 'text-doc', path: 'content', position: 3, value: 'l' } as any);
43
+ store.edit({ ot: 'text.insert', key: 'text-doc', path: 'content', position: 4, value: 'o' } as any);
44
44
  store.commit('type Hello');
45
45
 
46
46
  messages.length = 0; // Clear previous messages
47
47
 
48
48
  // Delete all 5 characters (1 operation)
49
- store.edit({ otype: 'text.delete', key: 'text-doc', path: 'content', position: 0, length: 5 } as any);
49
+ store.edit({ ot: 'text.delete', key: 'text-doc', path: 'content', position: 0, length: 5 } as any);
50
50
  store.commit('delete all');
51
51
 
52
52
  // Verify we sent exactly 1 message
@@ -54,22 +54,22 @@ describe('Graph Operation Coalescence (Integration)', () => {
54
54
 
55
55
  const msg = messages[0];
56
56
  expect(msg.ops).toHaveLength(1);
57
- expect(msg.ops[0].otype).toBe('text.delete');
57
+ expect(msg.ops[0].ot).toBe('text.delete');
58
58
 
59
59
  const deleteOp = msg.ops[0] as any;
60
- expect(deleteOp.deletions).toBeDefined();
60
+ expect(deleteOp.rm).toBeDefined();
61
61
 
62
62
  // CRITICAL: This verifies the coalescence optimization works!
63
63
  // Without the fix, this would have many single-char deletions
64
- expect(deleteOp.deletions).toHaveLength(1);
65
- expect(deleteOp.deletions[0].length).toBe(5);
64
+ expect(deleteOp.rm).toHaveLength(1);
65
+ expect(deleteOp.rm[0][1]).toBe(5);
66
66
  });
67
67
 
68
68
  it('should coalesce multiple consecutive delete operations', () => {
69
69
  const messages: CRDTMessage[] = [];
70
70
 
71
71
  const store = createGraph({
72
- sessionId: 'alice',
72
+ client: 'alice',
73
73
  coalescingEnabled: true,
74
74
  coalescingDelayMs: 0,
75
75
  coalescingThresholdMs: 1000,
@@ -77,23 +77,23 @@ describe('Graph Operation Coalescence (Integration)', () => {
77
77
  });
78
78
 
79
79
  // Initialize
80
- store.edit({ otype: 'node.init', key: 'scene', path: '', value: { tag: 'Scene' } });
80
+ store.edit({ ot: 'node.init', key: 'scene', path: '', value: { tag: 'Scene' } });
81
81
  store.commit('init');
82
82
 
83
- store.edit({ otype: 'node.insert', key: 'scene', path: 'children', value: { key: 'text-doc', tag: 'Text' } });
83
+ store.edit({ ot: 'node.insert', key: 'scene', path: 'children', value: { key: 'text-doc', tag: 'Text' } });
84
84
  store.commit('add text');
85
85
 
86
- store.edit({ otype: 'text.init', key: 'text-doc', path: 'content', value: 'Hello' } as any);
86
+ store.edit({ ot: 'text.init', key: 'text-doc', path: 'content', value: 'Hello' } as any);
87
87
  store.commit('init text with Hello');
88
88
 
89
89
  messages.length = 0;
90
90
 
91
91
  // Simulate rapid backspace (5 separate delete operations)
92
- store.edit({ otype: 'text.delete', key: 'text-doc', path: 'content', position: 4, length: 1 } as any); // 'o'
93
- store.edit({ otype: 'text.delete', key: 'text-doc', path: 'content', position: 3, length: 1 } as any); // 'l'
94
- store.edit({ otype: 'text.delete', key: 'text-doc', path: 'content', position: 2, length: 1 } as any); // 'l'
95
- store.edit({ otype: 'text.delete', key: 'text-doc', path: 'content', position: 1, length: 1 } as any); // 'e'
96
- store.edit({ otype: 'text.delete', key: 'text-doc', path: 'content', position: 0, length: 1 } as any); // 'H'
92
+ store.edit({ ot: 'text.delete', key: 'text-doc', path: 'content', position: 4, length: 1 } as any); // 'o'
93
+ store.edit({ ot: 'text.delete', key: 'text-doc', path: 'content', position: 3, length: 1 } as any); // 'l'
94
+ store.edit({ ot: 'text.delete', key: 'text-doc', path: 'content', position: 2, length: 1 } as any); // 'l'
95
+ store.edit({ ot: 'text.delete', key: 'text-doc', path: 'content', position: 1, length: 1 } as any); // 'e'
96
+ store.edit({ ot: 'text.delete', key: 'text-doc', path: 'content', position: 0, length: 1 } as any); // 'H'
97
97
  store.commit('rapid backspace');
98
98
 
99
99
  // Verify coalescence worked
@@ -102,22 +102,22 @@ describe('Graph Operation Coalescence (Integration)', () => {
102
102
  const msg = messages[0];
103
103
 
104
104
  // Find the delete operation(s)
105
- const deleteOps = msg.ops.filter(op => op.otype === 'text.delete');
105
+ const deleteOps = msg.ops.filter(op => op.ot === 'text.delete');
106
106
  expect(deleteOps.length).toBeGreaterThan(0);
107
107
 
108
108
  // CRITICAL TEST: Verify each operation has optimized deletions
109
109
  // The key fix is that isTextDeleteOp now accepts operations with deletions arrays
110
110
  for (const op of deleteOps) {
111
111
  const deleteOp = op as any;
112
- expect(deleteOp.deletions).toBeDefined();
113
- expect(Array.isArray(deleteOp.deletions)).toBe(true);
114
- expect(deleteOp.deletions.length).toBeGreaterThan(0);
112
+ expect(deleteOp.rm).toBeDefined();
113
+ expect(Array.isArray(deleteOp.rm)).toBe(true);
114
+ expect(deleteOp.rm.length).toBeGreaterThan(0);
115
115
 
116
116
  // Each deletion should be valid
117
- for (const del of deleteOp.deletions) {
118
- expect(del.id).toBeDefined();
119
- expect(typeof del.length).toBe('number');
120
- expect(del.length).toBeGreaterThan(0);
117
+ for (const del of deleteOp.rm) {
118
+ expect(del[0]).toBeDefined();
119
+ expect(typeof del[1]).toBe('number');
120
+ expect(del[1]).toBeGreaterThan(0);
121
121
  }
122
122
  }
123
123
 
@@ -130,7 +130,7 @@ describe('Graph Operation Coalescence (Integration)', () => {
130
130
  const messages: CRDTMessage[] = [];
131
131
 
132
132
  const store = createGraph({
133
- sessionId: 'alice',
133
+ client: 'alice',
134
134
  coalescingEnabled: true,
135
135
  coalescingDelayMs: 0,
136
136
  coalescingThresholdMs: 1000,
@@ -138,19 +138,19 @@ describe('Graph Operation Coalescence (Integration)', () => {
138
138
  });
139
139
 
140
140
  // Initialize
141
- store.edit({ otype: 'node.init', key: 'scene', path: '', value: { tag: 'Scene' } });
141
+ store.edit({ ot: 'node.init', key: 'scene', path: '', value: { tag: 'Scene' } });
142
142
  store.commit('init');
143
143
 
144
- store.edit({ otype: 'node.insert', key: 'scene', path: 'children', value: { key: 'text-doc', tag: 'Text' } });
144
+ store.edit({ ot: 'node.insert', key: 'scene', path: 'children', value: { key: 'text-doc', tag: 'Text' } });
145
145
  store.commit('add text');
146
146
 
147
- store.edit({ otype: 'text.init', key: 'text-doc', path: 'content', value: 'Test' } as any);
147
+ store.edit({ ot: 'text.init', key: 'text-doc', path: 'content', value: 'Test' } as any);
148
148
  store.commit('init text');
149
149
 
150
150
  messages.length = 0;
151
151
 
152
152
  // Single delete operation
153
- store.edit({ otype: 'text.delete', key: 'text-doc', path: 'content', position: 0, length: 4 } as any);
153
+ store.edit({ ot: 'text.delete', key: 'text-doc', path: 'content', position: 0, length: 4 } as any);
154
154
  store.commit('delete');
155
155
 
156
156
  expect(messages).toHaveLength(1);
@@ -159,9 +159,9 @@ describe('Graph Operation Coalescence (Integration)', () => {
159
159
 
160
160
  // CRITICAL: Verify the operation has the deletions field
161
161
  // This is what isTextDeleteOp checks for
162
- expect(deleteOp.otype).toBe('text.delete');
163
- expect(Array.isArray(deleteOp.deletions)).toBe(true);
164
- expect(deleteOp.deletions.length).toBeGreaterThan(0);
162
+ expect(deleteOp.ot).toBe('text.delete');
163
+ expect(Array.isArray(deleteOp.rm)).toBe(true);
164
+ expect(deleteOp.rm.length).toBeGreaterThan(0);
165
165
 
166
166
  // Verify it does NOT have seq/ts (those are on TextInsertOp only!)
167
167
  expect(deleteOp.seq).toBeUndefined();
@@ -174,7 +174,7 @@ describe('Graph Operation Coalescence (Integration)', () => {
174
174
  const messages: CRDTMessage[] = [];
175
175
 
176
176
  const store = createGraph({
177
- sessionId: 'alice',
177
+ client: 'alice',
178
178
  coalescingEnabled: true,
179
179
  coalescingDelayMs: 0,
180
180
  coalescingThresholdMs: 1000,
@@ -182,23 +182,23 @@ describe('Graph Operation Coalescence (Integration)', () => {
182
182
  });
183
183
 
184
184
  // Initialize
185
- store.edit({ otype: 'node.init', key: 'scene', path: '', value: { tag: 'Scene' } });
185
+ store.edit({ ot: 'node.init', key: 'scene', path: '', value: { tag: 'Scene' } });
186
186
  store.commit('init');
187
187
 
188
- store.edit({ otype: 'node.insert', key: 'scene', path: 'children', value: { key: 'text-doc', tag: 'Text' } });
188
+ store.edit({ ot: 'node.insert', key: 'scene', path: 'children', value: { key: 'text-doc', tag: 'Text' } });
189
189
  store.commit('add text');
190
190
 
191
- store.edit({ otype: 'text.init', key: 'text-doc', path: 'content', value: '' } as any);
191
+ store.edit({ ot: 'text.init', key: 'text-doc', path: 'content', value: '' } as any);
192
192
  store.commit('init text');
193
193
 
194
194
  messages.length = 0;
195
195
 
196
196
  // Type "Hello" (5 operations)
197
- store.edit({ otype: 'text.insert', key: 'text-doc', path: 'content', position: 0, value: 'H' } as any);
198
- store.edit({ otype: 'text.insert', key: 'text-doc', path: 'content', position: 1, value: 'e' } as any);
199
- store.edit({ otype: 'text.insert', key: 'text-doc', path: 'content', position: 2, value: 'l' } as any);
200
- store.edit({ otype: 'text.insert', key: 'text-doc', path: 'content', position: 3, value: 'l' } as any);
201
- store.edit({ otype: 'text.insert', key: 'text-doc', path: 'content', position: 4, value: 'o' } as any);
197
+ store.edit({ ot: 'text.insert', key: 'text-doc', path: 'content', position: 0, value: 'H' } as any);
198
+ store.edit({ ot: 'text.insert', key: 'text-doc', path: 'content', position: 1, value: 'e' } as any);
199
+ store.edit({ ot: 'text.insert', key: 'text-doc', path: 'content', position: 2, value: 'l' } as any);
200
+ store.edit({ ot: 'text.insert', key: 'text-doc', path: 'content', position: 3, value: 'l' } as any);
201
+ store.edit({ ot: 'text.insert', key: 'text-doc', path: 'content', position: 4, value: 'o' } as any);
202
202
  store.commit('type Hello');
203
203
 
204
204
  expect(messages).toHaveLength(1);
@@ -207,10 +207,10 @@ describe('Graph Operation Coalescence (Integration)', () => {
207
207
 
208
208
  // Verify inserts were coalesced
209
209
  expect(msg.ops).toHaveLength(1);
210
- expect(msg.ops[0].otype).toBe('text.insert');
210
+ expect(msg.ops[0].ot).toBe('text.insert');
211
211
 
212
212
  const insertOp = msg.ops[0] as any;
213
- expect(insertOp.content).toBe('Hello');
213
+ expect(insertOp.value[1]).toBe('Hello');
214
214
  expect(insertOp.id).toBeDefined();
215
215
  expect(insertOp.seq).toBeDefined();
216
216
  expect(insertOp.ts).toBeDefined();
@@ -27,18 +27,18 @@ import type { Operation } from '../../src/operations/OperationTypes.js';
27
27
  function nodeInsertOp(parentKey: string, nodeKey: string, props: Record<string, unknown> = {}): Operation {
28
28
  return {
29
29
  key: parentKey,
30
- otype: 'node.insert',
30
+ ot: 'node.insert',
31
31
  path: 'children',
32
32
  value: { key: nodeKey, tag: 'Mesh', name: nodeKey, ...props },
33
33
  } as Operation;
34
34
  }
35
35
 
36
36
  function vec3SetOp(nodeKey: string, path: string, value: [number, number, number]): Operation {
37
- return { key: nodeKey, otype: 'vector3.set', path, value } as Operation;
37
+ return { key: nodeKey, ot: 'vector3.set', path, value } as Operation;
38
38
  }
39
39
 
40
40
  function numAddOp(nodeKey: string, path: string, value: number): Operation {
41
- return { key: nodeKey, otype: 'number.add', path, value } as Operation;
41
+ return { key: nodeKey, ot: 'number.add', path, value } as Operation;
42
42
  }
43
43
 
44
44
  function commitAndAck(state: ClientState, op: Operation): ClientState {
@@ -47,8 +47,8 @@ function commitAndAck(state: ClientState, op: Operation): ClientState {
47
47
  return onServerAck(committed, msg!.id);
48
48
  }
49
49
 
50
- function setupState(sessionId = 'bench-session'): ClientState {
51
- let s = createInitialState(sessionId);
50
+ function setupState(client = 'bench-session'): ClientState {
51
+ let s = createInitialState(client);
52
52
  s = onEdit(s, nodeInsertOp('', 'scene', { tag: 'Scene' }));
53
53
  const { state: s1, msg: m1 } = commitEdits(s);
54
54
  s = onServerAck(s1, m1!.id);