@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
@@ -14,7 +14,7 @@ describe('Graph Operation Coalescence (end-to-end)', () => {
14
14
  it('should coalesce consecutive text.insert operations when coalescingEnabled', async () => {
15
15
  const messages: CRDTMessage[] = [];
16
16
  const store = createGraph({
17
- sessionId: 'alice',
17
+ client: 'alice',
18
18
  onSend: (msg) => messages.push(msg),
19
19
  coalescingEnabled: true,
20
20
  coalescingDelayMs: 50, // Short delay for testing
@@ -22,17 +22,17 @@ describe('Graph Operation Coalescence (end-to-end)', () => {
22
22
  });
23
23
 
24
24
  // Create node first (insert as child of root '')
25
- store.edit({ otype: 'node.insert', key: '', path: 'children', value: { key: 'doc', tag: 'Doc', name: 'Document' } });
25
+ store.edit({ ot: 'node.insert', key: '', path: 'children', value: { key: 'doc', tag: 'Doc', name: 'Document' } });
26
26
 
27
27
  // Initialize a text property
28
- store.edit({ otype: 'text.init', key: 'doc', path: 'content', value: '' });
28
+ store.edit({ ot: 'text.init', key: 'doc', path: 'content', value: '' });
29
29
 
30
30
  // Simulate typing "Hello" quickly (within 300ms)
31
- store.edit({ otype: 'text.insert', key: 'doc', path: 'content', position: 0, value: 'H' });
32
- store.edit({ otype: 'text.insert', key: 'doc', path: 'content', position: 1, value: 'e' });
33
- store.edit({ otype: 'text.insert', key: 'doc', path: 'content', position: 2, value: 'l' });
34
- store.edit({ otype: 'text.insert', key: 'doc', path: 'content', position: 3, value: 'l' });
35
- store.edit({ otype: 'text.insert', key: 'doc', path: 'content', position: 4, value: 'o' });
31
+ store.edit({ ot: 'text.insert', key: 'doc', path: 'content', position: 0, value: 'H' });
32
+ store.edit({ ot: 'text.insert', key: 'doc', path: 'content', position: 1, value: 'e' });
33
+ store.edit({ ot: 'text.insert', key: 'doc', path: 'content', position: 2, value: 'l' });
34
+ store.edit({ ot: 'text.insert', key: 'doc', path: 'content', position: 3, value: 'l' });
35
+ store.edit({ ot: 'text.insert', key: 'doc', path: 'content', position: 4, value: 'o' });
36
36
 
37
37
  // Wait for coalescing timer to fire
38
38
  await new Promise(resolve => setTimeout(resolve, 100));
@@ -42,66 +42,66 @@ describe('Graph Operation Coalescence (end-to-end)', () => {
42
42
 
43
43
  // The message should contain coalesced text.insert operations
44
44
  const msg = messages[0];
45
- const textInserts = msg.ops.filter(op => op.otype === 'text.insert');
45
+ const textInserts = msg.ops.filter(op => op.ot === 'text.insert');
46
46
 
47
47
  // All 5 inserts should be coalesced into 1 operation
48
48
  expect(textInserts.length).toBeLessThan(5);
49
49
 
50
- // The coalesced operation should have content "Hello"
50
+ // The coalesced operation should have value "Hello"
51
51
  const firstInsert = textInserts[0];
52
- expect(firstInsert.content).toBe('Hello');
52
+ expect((firstInsert as any).value[1]).toBe('Hello'); // value is [anchor, content] tuple
53
53
  });
54
54
 
55
55
  it('should NOT coalesce when coalescingEnabled is false', async () => {
56
56
  const messages: CRDTMessage[] = [];
57
57
  const store = createGraph({
58
- sessionId: 'alice',
58
+ client: 'alice',
59
59
  onSend: (msg) => messages.push(msg),
60
60
  coalescingEnabled: false, // Disabled
61
61
  });
62
62
 
63
- store.edit({ otype: 'node.insert', key: '', path: 'children', value: { key: 'doc', tag: 'Doc', name: 'Document' } });
64
- store.edit({ otype: 'text.init', key: 'doc', path: 'content', value: '' });
63
+ store.edit({ ot: 'node.insert', key: '', path: 'children', value: { key: 'doc', tag: 'Doc', name: 'Document' } });
64
+ store.edit({ ot: 'text.init', key: 'doc', path: 'content', value: '' });
65
65
 
66
66
  // Type "Hi" and commit immediately
67
- store.edit({ otype: 'text.insert', key: 'doc', path: 'content', position: 0, value: 'H' });
68
- store.edit({ otype: 'text.insert', key: 'doc', path: 'content', position: 1, value: 'i' });
67
+ store.edit({ ot: 'text.insert', key: 'doc', path: 'content', position: 0, value: 'H' });
68
+ store.edit({ ot: 'text.insert', key: 'doc', path: 'content', position: 1, value: 'i' });
69
69
  store.commit('typing');
70
70
 
71
71
  // Should send ONE message with init and 2 separate insert operations (not coalesced)
72
72
  expect(messages.length).toBe(1);
73
- const textInserts = messages[0].ops.filter(op => op.otype === 'text.insert');
73
+ const textInserts = messages[0].ops.filter(op => op.ot === 'text.insert');
74
74
 
75
75
  // Should have 2 separate operations
76
76
  expect(textInserts.length).toBe(2);
77
- expect(textInserts[0].content).toBe('H');
78
- expect(textInserts[1].content).toBe('i');
77
+ expect((textInserts[0] as any).value[1]).toBe('H'); // value is [anchor, content] tuple
78
+ expect((textInserts[1] as any).value[1]).toBe('i');
79
79
  });
80
80
 
81
81
  it('should respect time threshold and create separate operations for slow typing', async () => {
82
82
  const messages: CRDTMessage[] = [];
83
83
  const store = createGraph({
84
- sessionId: 'alice',
84
+ client: 'alice',
85
85
  onSend: (msg) => messages.push(msg),
86
86
  coalescingEnabled: true,
87
87
  coalescingDelayMs: 50,
88
88
  coalescingThresholdMs: 100, // 100ms threshold
89
89
  });
90
90
 
91
- store.edit({ otype: 'node.insert', key: '', path: 'children', value: { key: 'doc', tag: 'Doc', name: 'Document' } });
92
- store.edit({ otype: 'text.init', key: 'doc', path: 'content', value: '' });
91
+ store.edit({ ot: 'node.insert', key: '', path: 'children', value: { key: 'doc', tag: 'Doc', name: 'Document' } });
92
+ store.edit({ ot: 'text.init', key: 'doc', path: 'content', value: '' });
93
93
 
94
94
  // Type "H" and "e" quickly
95
- store.edit({ otype: 'text.insert', key: 'doc', path: 'content', position: 0, value: 'H' });
96
- store.edit({ otype: 'text.insert', key: 'doc', path: 'content', position: 1, value: 'e' });
95
+ store.edit({ ot: 'text.insert', key: 'doc', path: 'content', position: 0, value: 'H' });
96
+ store.edit({ ot: 'text.insert', key: 'doc', path: 'content', position: 1, value: 'e' });
97
97
 
98
98
  // Wait for first batch to be sent
99
99
  await new Promise(resolve => setTimeout(resolve, 100));
100
100
 
101
101
  // Type "l" and "l" quickly (after threshold)
102
102
  await new Promise(resolve => setTimeout(resolve, 150)); // Exceed threshold
103
- store.edit({ otype: 'text.insert', key: 'doc', path: 'content', position: 2, value: 'l' });
104
- store.edit({ otype: 'text.insert', key: 'doc', path: 'content', position: 3, value: 'l' });
103
+ store.edit({ ot: 'text.insert', key: 'doc', path: 'content', position: 2, value: 'l' });
104
+ store.edit({ ot: 'text.insert', key: 'doc', path: 'content', position: 3, value: 'l' });
105
105
 
106
106
  // Wait for second batch
107
107
  await new Promise(resolve => setTimeout(resolve, 100));
@@ -109,14 +109,14 @@ describe('Graph Operation Coalescence (end-to-end)', () => {
109
109
  // Should have sent TWO messages: one with "He", one with "ll"
110
110
  expect(messages.length).toBe(2);
111
111
 
112
- const firstInserts = messages[0].ops.filter(op => op.otype === 'text.insert');
113
- const secondInserts = messages[1].ops.filter(op => op.otype === 'text.insert');
112
+ const firstInserts = messages[0].ops.filter(op => op.ot === 'text.insert');
113
+ const secondInserts = messages[1].ops.filter(op => op.ot === 'text.insert');
114
114
 
115
115
  expect(firstInserts.length).toBe(1);
116
- expect(firstInserts[0].content).toBe('He');
116
+ expect((firstInserts[0] as any).value[1]).toBe('He'); // value is [anchor, content] tuple
117
117
 
118
118
  expect(secondInserts.length).toBe(1);
119
- expect(secondInserts[0].content).toBe('ll');
119
+ expect((secondInserts[0] as any).value[1]).toBe('ll');
120
120
  });
121
121
  });
122
122
 
@@ -124,7 +124,7 @@ describe('Graph Operation Coalescence (end-to-end)', () => {
124
124
  it('should coalesce consecutive text.delete operations', async () => {
125
125
  const messages: CRDTMessage[] = [];
126
126
  const store = createGraph({
127
- sessionId: 'alice',
127
+ client: 'alice',
128
128
  onSend: (msg) => messages.push(msg),
129
129
  coalescingEnabled: true,
130
130
  coalescingDelayMs: 50,
@@ -132,17 +132,17 @@ describe('Graph Operation Coalescence (end-to-end)', () => {
132
132
  });
133
133
 
134
134
  // Create node and initialize with some text
135
- store.edit({ otype: 'node.insert', key: '', path: 'children', value: { key: 'doc', tag: 'Doc', name: 'Document' } });
136
- store.edit({ otype: 'text.init', key: 'doc', path: 'content', value: 'Hello World' });
135
+ store.edit({ ot: 'node.insert', key: '', path: 'children', value: { key: 'doc', tag: 'Doc', name: 'Document' } });
136
+ store.edit({ ot: 'text.init', key: 'doc', path: 'content', value: 'Hello World' });
137
137
  await new Promise(resolve => setTimeout(resolve, 100));
138
138
  messages.length = 0; // Clear init message
139
139
 
140
140
  // Delete "World" one character at a time (positions 6-10)
141
- store.edit({ otype: 'text.delete', key: 'doc', path: 'content', position: 6, length: 1 }); // Delete 'W'
142
- store.edit({ otype: 'text.delete', key: 'doc', path: 'content', position: 6, length: 1 }); // Delete 'o'
143
- store.edit({ otype: 'text.delete', key: 'doc', path: 'content', position: 6, length: 1 }); // Delete 'r'
144
- store.edit({ otype: 'text.delete', key: 'doc', path: 'content', position: 6, length: 1 }); // Delete 'l'
145
- store.edit({ otype: 'text.delete', key: 'doc', path: 'content', position: 6, length: 1 }); // Delete 'd'
141
+ store.edit({ ot: 'text.delete', key: 'doc', path: 'content', position: 6, length: 1 }); // Delete 'W'
142
+ store.edit({ ot: 'text.delete', key: 'doc', path: 'content', position: 6, length: 1 }); // Delete 'o'
143
+ store.edit({ ot: 'text.delete', key: 'doc', path: 'content', position: 6, length: 1 }); // Delete 'r'
144
+ store.edit({ ot: 'text.delete', key: 'doc', path: 'content', position: 6, length: 1 }); // Delete 'l'
145
+ store.edit({ ot: 'text.delete', key: 'doc', path: 'content', position: 6, length: 1 }); // Delete 'd'
146
146
 
147
147
  // Wait for coalescing timer
148
148
  await new Promise(resolve => setTimeout(resolve, 100));
@@ -150,7 +150,7 @@ describe('Graph Operation Coalescence (end-to-end)', () => {
150
150
  // Should have ONE coalesced delete message
151
151
  expect(messages.length).toBe(1);
152
152
 
153
- const deleteOps = messages[0].ops.filter(op => op.otype === 'text.delete');
153
+ const deleteOps = messages[0].ops.filter(op => op.ot === 'text.delete');
154
154
 
155
155
  // Should be coalesced into fewer operations
156
156
  expect(deleteOps.length).toBeLessThan(5);
@@ -158,33 +158,33 @@ describe('Graph Operation Coalescence (end-to-end)', () => {
158
158
  // If coalesced into 1 operation, it should have a deletions array
159
159
  if (deleteOps.length === 1) {
160
160
  const firstDelete = deleteOps[0] as any;
161
- expect(firstDelete.deletions).toBeDefined();
162
- expect(firstDelete.deletions.length).toBeGreaterThanOrEqual(1);
161
+ expect(firstDelete.rm).toBeDefined(); // rm field contains deletions
162
+ expect(firstDelete.rm.length).toBeGreaterThanOrEqual(1); // rm is [[id, len], ...]
163
163
  }
164
164
  });
165
165
 
166
166
  it('should handle mixed insert and delete operations', async () => {
167
167
  const messages: CRDTMessage[] = [];
168
168
  const store = createGraph({
169
- sessionId: 'alice',
169
+ client: 'alice',
170
170
  onSend: (msg) => messages.push(msg),
171
171
  coalescingEnabled: true,
172
172
  coalescingDelayMs: 50,
173
173
  coalescingThresholdMs: 300,
174
174
  });
175
175
 
176
- store.edit({ otype: 'node.insert', key: '', path: 'children', value: { key: 'doc', tag: 'Doc', name: 'Document' } });
177
- store.edit({ otype: 'text.init', key: 'doc', path: 'content', value: '' });
176
+ store.edit({ ot: 'node.insert', key: '', path: 'children', value: { key: 'doc', tag: 'Doc', name: 'Document' } });
177
+ store.edit({ ot: 'text.init', key: 'doc', path: 'content', value: '' });
178
178
 
179
179
  // Type "Hello", then delete "lo", then type "p"
180
- store.edit({ otype: 'text.insert', key: 'doc', path: 'content', position: 0, value: 'H' });
181
- store.edit({ otype: 'text.insert', key: 'doc', path: 'content', position: 1, value: 'e' });
182
- store.edit({ otype: 'text.insert', key: 'doc', path: 'content', position: 2, value: 'l' });
183
- store.edit({ otype: 'text.insert', key: 'doc', path: 'content', position: 3, value: 'l' });
184
- store.edit({ otype: 'text.insert', key: 'doc', path: 'content', position: 4, value: 'o' });
185
- store.edit({ otype: 'text.delete', key: 'doc', path: 'content', position: 3, length: 1 }); // Delete 'l'
186
- store.edit({ otype: 'text.delete', key: 'doc', path: 'content', position: 3, length: 1 }); // Delete 'o'
187
- store.edit({ otype: 'text.insert', key: 'doc', path: 'content', position: 3, value: 'p' });
180
+ store.edit({ ot: 'text.insert', key: 'doc', path: 'content', position: 0, value: 'H' });
181
+ store.edit({ ot: 'text.insert', key: 'doc', path: 'content', position: 1, value: 'e' });
182
+ store.edit({ ot: 'text.insert', key: 'doc', path: 'content', position: 2, value: 'l' });
183
+ store.edit({ ot: 'text.insert', key: 'doc', path: 'content', position: 3, value: 'l' });
184
+ store.edit({ ot: 'text.insert', key: 'doc', path: 'content', position: 4, value: 'o' });
185
+ store.edit({ ot: 'text.delete', key: 'doc', path: 'content', position: 3, length: 1 }); // Delete 'l'
186
+ store.edit({ ot: 'text.delete', key: 'doc', path: 'content', position: 3, length: 1 }); // Delete 'o'
187
+ store.edit({ ot: 'text.insert', key: 'doc', path: 'content', position: 3, value: 'p' });
188
188
 
189
189
  // Wait for coalescing timer
190
190
  await new Promise(resolve => setTimeout(resolve, 100));
@@ -193,8 +193,8 @@ describe('Graph Operation Coalescence (end-to-end)', () => {
193
193
  expect(messages.length).toBe(1);
194
194
 
195
195
  const msg = messages[0];
196
- const inserts = msg.ops.filter(op => op.otype === 'text.insert');
197
- const deletes = msg.ops.filter(op => op.otype === 'text.delete');
196
+ const inserts = msg.ops.filter(op => op.ot === 'text.insert');
197
+ const deletes = msg.ops.filter(op => op.ot === 'text.delete');
198
198
 
199
199
  // Should have some coalesced inserts and deletes
200
200
  expect(inserts.length).toBeGreaterThan(0);
@@ -206,16 +206,16 @@ describe('Graph Operation Coalescence (end-to-end)', () => {
206
206
  it('should coalesce operations when explicitly committing with coalescingEnabled', () => {
207
207
  const messages: CRDTMessage[] = [];
208
208
  const store = createGraph({
209
- sessionId: 'alice',
209
+ client: 'alice',
210
210
  onSend: (msg) => messages.push(msg),
211
211
  coalescingEnabled: true,
212
212
  coalescingThresholdMs: 300,
213
213
  });
214
214
 
215
- store.edit({ otype: 'node.insert', key: '', path: 'children', value: { key: 'doc', tag: 'Doc', name: 'Document' } });
216
- store.edit({ otype: 'text.init', key: 'doc', path: 'content', value: '' });
217
- store.edit({ otype: 'text.insert', key: 'doc', path: 'content', position: 0, value: 'H' });
218
- store.edit({ otype: 'text.insert', key: 'doc', path: 'content', position: 1, value: 'i' });
215
+ store.edit({ ot: 'node.insert', key: '', path: 'children', value: { key: 'doc', tag: 'Doc', name: 'Document' } });
216
+ store.edit({ ot: 'text.init', key: 'doc', path: 'content', value: '' });
217
+ store.edit({ ot: 'text.insert', key: 'doc', path: 'content', position: 0, value: 'H' });
218
+ store.edit({ ot: 'text.insert', key: 'doc', path: 'content', position: 1, value: 'i' });
219
219
 
220
220
  // Explicitly commit (should clear timer and coalesce immediately)
221
221
  store.commit('typing');
@@ -223,9 +223,9 @@ describe('Graph Operation Coalescence (end-to-end)', () => {
223
223
  // Should send ONE message immediately with coalesced operations
224
224
  expect(messages.length).toBe(1);
225
225
 
226
- const textInserts = messages[0].ops.filter(op => op.otype === 'text.insert');
226
+ const textInserts = messages[0].ops.filter(op => op.ot === 'text.insert');
227
227
  expect(textInserts.length).toBe(1);
228
- expect(textInserts[0].content).toBe('Hi');
228
+ expect((textInserts[0] as any).value[1]).toBe('Hi'); // value is [anchor, content] tuple
229
229
  });
230
230
  });
231
231
 
@@ -233,7 +233,7 @@ describe('Graph Operation Coalescence (end-to-end)', () => {
233
233
  it('should coalesce operations on different documents independently', async () => {
234
234
  const messages: CRDTMessage[] = [];
235
235
  const store = createGraph({
236
- sessionId: 'alice',
236
+ client: 'alice',
237
237
  onSend: (msg) => messages.push(msg),
238
238
  coalescingEnabled: true,
239
239
  coalescingDelayMs: 50,
@@ -241,16 +241,16 @@ describe('Graph Operation Coalescence (end-to-end)', () => {
241
241
  });
242
242
 
243
243
  // Edit doc1
244
- store.edit({ otype: 'node.insert', key: '', path: 'children', value: { key: 'doc1', tag: 'Doc', name: 'Document 1' } });
245
- store.edit({ otype: 'text.init', key: 'doc1', path: 'content', value: '' });
246
- store.edit({ otype: 'text.insert', key: 'doc1', path: 'content', position: 0, value: 'A' });
247
- store.edit({ otype: 'text.insert', key: 'doc1', path: 'content', position: 1, value: 'B' });
244
+ store.edit({ ot: 'node.insert', key: '', path: 'children', value: { key: 'doc1', tag: 'Doc', name: 'Document 1' } });
245
+ store.edit({ ot: 'text.init', key: 'doc1', path: 'content', value: '' });
246
+ store.edit({ ot: 'text.insert', key: 'doc1', path: 'content', position: 0, value: 'A' });
247
+ store.edit({ ot: 'text.insert', key: 'doc1', path: 'content', position: 1, value: 'B' });
248
248
 
249
249
  // Edit doc2
250
- store.edit({ otype: 'node.insert', key: '', path: 'children', value: { key: 'doc2', tag: 'Doc', name: 'Document 2' } });
251
- store.edit({ otype: 'text.init', key: 'doc2', path: 'content', value: '' });
252
- store.edit({ otype: 'text.insert', key: 'doc2', path: 'content', position: 0, value: 'X' });
253
- store.edit({ otype: 'text.insert', key: 'doc2', path: 'content', position: 1, value: 'Y' });
250
+ store.edit({ ot: 'node.insert', key: '', path: 'children', value: { key: 'doc2', tag: 'Doc', name: 'Document 2' } });
251
+ store.edit({ ot: 'text.init', key: 'doc2', path: 'content', value: '' });
252
+ store.edit({ ot: 'text.insert', key: 'doc2', path: 'content', position: 0, value: 'X' });
253
+ store.edit({ ot: 'text.insert', key: 'doc2', path: 'content', position: 1, value: 'Y' });
254
254
 
255
255
  // Wait for coalescing
256
256
  await new Promise(resolve => setTimeout(resolve, 100));
@@ -259,15 +259,15 @@ describe('Graph Operation Coalescence (end-to-end)', () => {
259
259
  expect(messages.length).toBe(1);
260
260
 
261
261
  const msg = messages[0];
262
- const doc1Inserts = msg.ops.filter(op => op.otype === 'text.insert' && op.key === 'doc1');
263
- const doc2Inserts = msg.ops.filter(op => op.otype === 'text.insert' && op.key === 'doc2');
262
+ const doc1Inserts = msg.ops.filter(op => op.ot === 'text.insert' && op.key === 'doc1');
263
+ const doc2Inserts = msg.ops.filter(op => op.ot === 'text.insert' && op.key === 'doc2');
264
264
 
265
265
  // Each document's operations should be coalesced independently
266
266
  expect(doc1Inserts.length).toBe(1);
267
- expect(doc1Inserts[0].content).toBe('AB');
267
+ expect((doc1Inserts[0] as any).value[1]).toBe('AB'); // value is [anchor, content] tuple
268
268
 
269
269
  expect(doc2Inserts.length).toBe(1);
270
- expect(doc2Inserts[0].content).toBe('XY');
270
+ expect((doc2Inserts[0] as any).value[1]).toBe('XY');
271
271
  });
272
272
  });
273
273
 
@@ -275,37 +275,37 @@ describe('Graph Operation Coalescence (end-to-end)', () => {
275
275
  it('should handle rapid commits without losing operations', async () => {
276
276
  const messages: CRDTMessage[] = [];
277
277
  const store = createGraph({
278
- sessionId: 'alice',
278
+ client: 'alice',
279
279
  onSend: (msg) => messages.push(msg),
280
280
  coalescingEnabled: true,
281
281
  coalescingDelayMs: 100,
282
282
  coalescingThresholdMs: 300,
283
283
  });
284
284
 
285
- store.edit({ otype: 'node.insert', key: '', path: 'children', value: { key: 'doc', tag: 'Doc', name: 'Document' } });
286
- store.edit({ otype: 'text.init', key: 'doc', path: 'content', value: '' });
287
- store.edit({ otype: 'text.insert', key: 'doc', path: 'content', position: 0, value: 'A' });
285
+ store.edit({ ot: 'node.insert', key: '', path: 'children', value: { key: 'doc', tag: 'Doc', name: 'Document' } });
286
+ store.edit({ ot: 'text.init', key: 'doc', path: 'content', value: '' });
287
+ store.edit({ ot: 'text.insert', key: 'doc', path: 'content', position: 0, value: 'A' });
288
288
  store.commit('first');
289
289
 
290
- store.edit({ otype: 'text.insert', key: 'doc', path: 'content', position: 1, value: 'B' });
290
+ store.edit({ ot: 'text.insert', key: 'doc', path: 'content', position: 1, value: 'B' });
291
291
  store.commit('second');
292
292
 
293
- store.edit({ otype: 'text.insert', key: 'doc', path: 'content', position: 2, value: 'C' });
293
+ store.edit({ ot: 'text.insert', key: 'doc', path: 'content', position: 2, value: 'C' });
294
294
  store.commit('third');
295
295
 
296
296
  // Should have 3 messages (one per commit)
297
297
  expect(messages.length).toBe(3);
298
298
 
299
299
  // Each message should contain the expected operation
300
- expect(messages[0].ops.some(op => op.otype === 'text.insert' && op.content === 'A')).toBe(true);
301
- expect(messages[1].ops.some(op => op.otype === 'text.insert' && op.content === 'B')).toBe(true);
302
- expect(messages[2].ops.some(op => op.otype === 'text.insert' && op.content === 'C')).toBe(true);
300
+ expect(messages[0].ops.some(op => op.ot === 'text.insert' && (op as any).value[1] === 'A')).toBe(true); // value is [anchor, content] tuple
301
+ expect(messages[1].ops.some(op => op.ot === 'text.insert' && (op as any).value[1] === 'B')).toBe(true);
302
+ expect(messages[2].ops.some(op => op.ot === 'text.insert' && (op as any).value[1] === 'C')).toBe(true);
303
303
  });
304
304
 
305
305
  it('should handle empty edits gracefully', async () => {
306
306
  const messages: CRDTMessage[] = [];
307
307
  const store = createGraph({
308
- sessionId: 'alice',
308
+ client: 'alice',
309
309
  onSend: (msg) => messages.push(msg),
310
310
  coalescingEnabled: true,
311
311
  coalescingDelayMs: 50,