@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.
- package/CLAUDE.md +3 -2
- package/dist/client/EditBuffer.d.ts +4 -4
- package/dist/client/EditBuffer.d.ts.map +1 -1
- package/dist/client/EditBuffer.js +26 -25
- package/dist/client/EditBuffer.js.map +1 -1
- package/dist/client/actions.d.ts +3 -3
- package/dist/client/actions.d.ts.map +1 -1
- package/dist/client/actions.js +71 -70
- package/dist/client/actions.js.map +1 -1
- package/dist/client/coalesceGraphOps.d.ts +4 -4
- package/dist/client/coalesceGraphOps.js +4 -4
- package/dist/client/coalesceTextOperations.d.ts.map +1 -1
- package/dist/client/coalesceTextOperations.js +23 -20
- package/dist/client/coalesceTextOperations.js.map +1 -1
- package/dist/client/coalescence/lwwOperations.js +3 -3
- package/dist/client/coalescence/lwwOperations.js.map +1 -1
- package/dist/client/coalescence/numberOperations.js +2 -2
- package/dist/client/coalescence/numberOperations.js.map +1 -1
- package/dist/client/coalescence/registry.d.ts +3 -3
- package/dist/client/coalescence/registry.d.ts.map +1 -1
- package/dist/client/coalescence/registry.js +11 -11
- package/dist/client/coalescence/registry.js.map +1 -1
- package/dist/client/coalescence/textDeletes.d.ts +8 -7
- package/dist/client/coalescence/textDeletes.d.ts.map +1 -1
- package/dist/client/coalescence/textDeletes.js +11 -11
- package/dist/client/coalescence/textDeletes.js.map +1 -1
- package/dist/client/coalescence/textInserts.d.ts +8 -5
- package/dist/client/coalescence/textInserts.d.ts.map +1 -1
- package/dist/client/coalescence/textInserts.js +32 -12
- package/dist/client/coalescence/textInserts.js.map +1 -1
- package/dist/client/coalescence/utils.d.ts +3 -9
- package/dist/client/coalescence/utils.d.ts.map +1 -1
- package/dist/client/coalescence/utils.js +10 -8
- package/dist/client/coalescence/utils.js.map +1 -1
- package/dist/client/coalescence/vector3Operations.js +2 -2
- package/dist/client/coalescence/vector3Operations.js.map +1 -1
- package/dist/client/createGraph.d.ts +2 -2
- package/dist/client/createGraph.js +4 -4
- package/dist/client/createGraph.js.map +1 -1
- package/dist/client/createTextDocument.d.ts +1 -1
- package/dist/client/createTextDocument.js +3 -3
- package/dist/client/createTextDocument.js.map +1 -1
- package/dist/client/hooks.d.ts +3 -3
- package/dist/client/hooks.d.ts.map +1 -1
- package/dist/client/hooks.js +4 -4
- package/dist/client/hooks.js.map +1 -1
- package/dist/client/textActions.d.ts +2 -2
- package/dist/client/textActions.d.ts.map +1 -1
- package/dist/client/textActions.js +47 -47
- package/dist/client/textActions.js.map +1 -1
- package/dist/client/textTypes.d.ts +8 -8
- package/dist/client/textTypes.d.ts.map +1 -1
- package/dist/client/types.d.ts +4 -4
- package/dist/client/types.d.ts.map +1 -1
- package/dist/crdt/GraphTextCRDT.d.ts +2 -2
- package/dist/crdt/GraphTextCRDT.d.ts.map +1 -1
- package/dist/crdt/GraphTextCRDT.js +6 -6
- package/dist/crdt/GraphTextCRDT.js.map +1 -1
- package/dist/crdt/Rope.d.ts +13 -14
- package/dist/crdt/Rope.d.ts.map +1 -1
- package/dist/crdt/Rope.js +130 -59
- package/dist/crdt/Rope.js.map +1 -1
- package/dist/crdt/index.d.ts +1 -1
- package/dist/crdt/index.d.ts.map +1 -1
- package/dist/crdt/index.js +1 -1
- package/dist/crdt/index.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/operations/OperationTypes.d.ts +45 -48
- package/dist/operations/OperationTypes.d.ts.map +1 -1
- package/dist/operations/OperationValidator.js +11 -11
- package/dist/operations/OperationValidator.js.map +1 -1
- package/dist/operations/apply/node.js +3 -3
- package/dist/operations/apply/node.js.map +1 -1
- package/dist/operations/apply/text.d.ts.map +1 -1
- package/dist/operations/apply/text.js +35 -32
- package/dist/operations/apply/text.js.map +1 -1
- package/dist/operations/apply/types.d.ts +4 -4
- package/dist/operations/apply/types.d.ts.map +1 -1
- package/dist/operations/apply/types.js +8 -8
- package/dist/operations/apply/types.js.map +1 -1
- package/dist/operations/dispatcher.d.ts.map +1 -1
- package/dist/operations/dispatcher.js +52 -13
- package/dist/operations/dispatcher.js.map +1 -1
- package/dist/serdes.d.ts +1 -1
- package/dist/serdes.d.ts.map +1 -1
- package/dist/state/ConflictResolver.d.ts +9 -9
- package/dist/state/ConflictResolver.d.ts.map +1 -1
- package/dist/state/ConflictResolver.js +20 -20
- package/dist/state/ConflictResolver.js.map +1 -1
- package/dist/state/DType.d.ts +2 -2
- package/dist/state/DType.d.ts.map +1 -1
- package/dist/state/DType.js +14 -14
- package/dist/state/DType.js.map +1 -1
- package/dist/state/VectorClock.d.ts +6 -6
- package/dist/state/VectorClock.d.ts.map +1 -1
- package/dist/state/VectorClock.js +14 -14
- package/dist/state/VectorClock.js.map +1 -1
- package/dist/state/index.d.ts +1 -1
- package/dist/state/index.js +1 -1
- package/examples/01-basic-usage.ts +16 -16
- package/examples/02-concurrent-edits.ts +29 -29
- package/examples/03-scene-building.ts +28 -28
- package/examples/04-conflict-resolution.ts +56 -56
- package/examples/05-coalescence-usage.ts +23 -23
- package/examples/README.md +12 -12
- package/package.json +1 -1
- package/src/client/EditBuffer.ts +28 -27
- package/src/client/TEXT_DOCUMENT_API.md +9 -9
- package/src/client/actions.ts +74 -70
- package/src/client/coalesceGraphOps.ts +4 -4
- package/src/client/coalesceTextOperations.ts +26 -22
- package/src/client/coalescence/lwwOperations.ts +3 -3
- package/src/client/coalescence/numberOperations.ts +2 -2
- package/src/client/coalescence/registry.ts +13 -12
- package/src/client/coalescence/textDeletes.ts +22 -18
- package/src/client/coalescence/textInserts.ts +49 -25
- package/src/client/coalescence/utils.ts +14 -11
- package/src/client/coalescence/vector3Operations.ts +2 -2
- package/src/client/createGraph.ts +4 -4
- package/src/client/createTextDocument.ts +3 -3
- package/src/client/hooks.tsx +5 -5
- package/src/client/textActions.ts +47 -47
- package/src/client/textTypes.ts +8 -8
- package/src/client/types.ts +4 -4
- package/src/crdt/GraphTextCRDT.ts +6 -6
- package/src/crdt/Rope.ts +156 -71
- package/src/crdt/index.ts +2 -0
- package/src/index.ts +2 -0
- package/src/operations/OperationTypes.ts +47 -47
- package/src/operations/OperationValidator.ts +11 -11
- package/src/operations/apply/node.ts +3 -3
- package/src/operations/apply/text.ts +38 -32
- package/src/operations/apply/types.ts +11 -11
- package/src/operations/dispatcher.ts +57 -13
- package/src/serdes.ts +1 -1
- package/src/state/ConflictResolver.ts +23 -23
- package/src/state/DType.ts +16 -16
- package/src/state/VectorClock.ts +14 -14
- package/src/state/index.ts +1 -1
- package/tests/client/actions.test.ts +76 -76
- package/tests/client/coalesce-graph-operations.test.ts +84 -84
- package/tests/client/coalesce-text-operations.test.ts +91 -114
- package/tests/client/compaction.test.ts +18 -18
- package/tests/client/delete-coalescence-bug.test.ts +34 -34
- package/tests/client/edit-buffer.test.ts +27 -30
- package/tests/client/graph-coalescence-phase1.test.ts +66 -66
- package/tests/client/graph-coalescence.test.ts +50 -50
- package/tests/client/journal-benchmark.test.ts +5 -5
- package/tests/crdt/graph-text-crdt.test.ts +60 -64
- package/tests/crdt/rope.test.ts +9 -8
- package/tests/crdt/text-operations.test.ts +28 -28
- package/tests/fixtures/array-ops.jsonl +6 -6
- package/tests/fixtures/boolean-ops.jsonl +6 -6
- package/tests/fixtures/color-ops.jsonl +4 -4
- package/tests/fixtures/edit-buffer.jsonl +3 -3
- package/tests/fixtures/node-ops.jsonl +6 -6
- package/tests/fixtures/number-ops.jsonl +7 -7
- package/tests/fixtures/object-ops.jsonl +4 -4
- package/tests/fixtures/operations.jsonl +7 -7
- package/tests/fixtures/string-ops.jsonl +4 -4
- package/tests/fixtures/undo-redo.jsonl +3 -3
- package/tests/fixtures/vector-ops.jsonl +17 -17
- package/tests/operations/collections.test.ts +4 -4
- package/tests/operations/nodes.test.ts +5 -5
- package/tests/operations/operation-ordering.test.ts +406 -0
- package/tests/operations/primitives.test.ts +4 -4
- package/tests/operations/unified-schema.test.ts +27 -27
- package/tests/operations/vectors.test.ts +4 -4
- package/tests/sync/digest.test.ts +5 -5
|
@@ -98,7 +98,7 @@ describe('GraphTextCRDT', () => {
|
|
|
98
98
|
expect(op.id).toBeDefined();
|
|
99
99
|
expect(typeof op.id).toBe('string');
|
|
100
100
|
expect(op.id).toMatch(/^session-1:/); // String format: "agent:seq"
|
|
101
|
-
expect(op.
|
|
101
|
+
expect(op.value[1]).toBe('test');
|
|
102
102
|
expect(op.seq).toBeDefined();
|
|
103
103
|
expect(op.ts).toBeDefined();
|
|
104
104
|
});
|
|
@@ -142,15 +142,15 @@ describe('GraphTextCRDT', () => {
|
|
|
142
142
|
|
|
143
143
|
it('should return DeleteOp with deletion metadata', () => {
|
|
144
144
|
const op = crdt.deleteLocal('node-1', 'content', 0, 5);
|
|
145
|
-
expect(op.
|
|
146
|
-
expect(op.
|
|
147
|
-
expect(op.
|
|
148
|
-
expect(op.
|
|
145
|
+
expect(op.rm).toBeDefined();
|
|
146
|
+
expect(op.rm.length).toBeGreaterThan(0);
|
|
147
|
+
expect(op.rm[0][0]).toBeDefined();
|
|
148
|
+
expect(op.rm[0][1]).toBeDefined();
|
|
149
149
|
});
|
|
150
150
|
|
|
151
151
|
it('should handle delete of zero length', () => {
|
|
152
152
|
const op = crdt.deleteLocal('node-1', 'content', 0, 0);
|
|
153
|
-
expect(op.
|
|
153
|
+
expect(op.rm).toHaveLength(0);
|
|
154
154
|
expect(crdt.getText('node-1', 'content')).toBe('Hello World');
|
|
155
155
|
});
|
|
156
156
|
|
|
@@ -161,7 +161,7 @@ describe('GraphTextCRDT', () => {
|
|
|
161
161
|
|
|
162
162
|
it('should auto-create rope if not initialized', () => {
|
|
163
163
|
const op = crdt.deleteLocal('node-2', 'title', 0, 5);
|
|
164
|
-
expect(op.
|
|
164
|
+
expect(op.rm).toHaveLength(0);
|
|
165
165
|
});
|
|
166
166
|
});
|
|
167
167
|
|
|
@@ -180,9 +180,9 @@ describe('GraphTextCRDT', () => {
|
|
|
180
180
|
|
|
181
181
|
it('should return ReplaceOp with delete and insert metadata', () => {
|
|
182
182
|
const op = crdt.replaceLocal('node-1', 'content', 0, 5, 'Hi');
|
|
183
|
-
expect(op.
|
|
184
|
-
expect(op.
|
|
185
|
-
expect(op.
|
|
183
|
+
expect(op.rm).toBeDefined();
|
|
184
|
+
expect(op.id).toBeDefined();
|
|
185
|
+
expect(op.value[1]).toBe('Hi');
|
|
186
186
|
});
|
|
187
187
|
|
|
188
188
|
it('should handle delete-only replacement (empty insert)', () => {
|
|
@@ -203,7 +203,7 @@ describe('GraphTextCRDT', () => {
|
|
|
203
203
|
it('should auto-create rope if not initialized', () => {
|
|
204
204
|
const op = crdt.replaceLocal('node-2', 'title', 0, 0, 'Title');
|
|
205
205
|
expect(crdt.getText('node-2', 'title')).toBe('Title');
|
|
206
|
-
expect(op.
|
|
206
|
+
expect(op.value[1]).toBe('Title');
|
|
207
207
|
});
|
|
208
208
|
});
|
|
209
209
|
|
|
@@ -217,9 +217,9 @@ describe('GraphTextCRDT', () => {
|
|
|
217
217
|
|
|
218
218
|
it('should apply remote insert operation', () => {
|
|
219
219
|
const op: InsertOp = {
|
|
220
|
+
ot: 'insert',
|
|
220
221
|
id: 'session-2:0',
|
|
221
|
-
|
|
222
|
-
parentId: null,
|
|
222
|
+
value: [null, 'Hello'],
|
|
223
223
|
seq: 1,
|
|
224
224
|
ts: Date.now() / 1000,
|
|
225
225
|
};
|
|
@@ -229,18 +229,18 @@ describe('GraphTextCRDT', () => {
|
|
|
229
229
|
|
|
230
230
|
it('should apply remote insert with parent', () => {
|
|
231
231
|
const op1: InsertOp = {
|
|
232
|
+
ot: 'insert',
|
|
232
233
|
id: 'session-1:0',
|
|
233
|
-
|
|
234
|
-
parentId: null,
|
|
234
|
+
value: [null, 'Hello'],
|
|
235
235
|
seq: 1,
|
|
236
236
|
ts: Date.now() / 1000,
|
|
237
237
|
};
|
|
238
238
|
crdt.applyInsert('node-1', 'content', op1);
|
|
239
239
|
|
|
240
240
|
const op2: InsertOp = {
|
|
241
|
+
ot: 'insert',
|
|
241
242
|
id: 'session-2:0',
|
|
242
|
-
|
|
243
|
-
parentId: 'session-1:4',
|
|
243
|
+
value: ['session-1:4', ' World'],
|
|
244
244
|
seq: 2,
|
|
245
245
|
ts: Date.now() / 1000,
|
|
246
246
|
};
|
|
@@ -250,9 +250,9 @@ describe('GraphTextCRDT', () => {
|
|
|
250
250
|
|
|
251
251
|
it('should auto-create rope if not initialized', () => {
|
|
252
252
|
const op: InsertOp = {
|
|
253
|
+
ot: 'insert',
|
|
253
254
|
id: 'session-2:0',
|
|
254
|
-
|
|
255
|
-
parentId: null,
|
|
255
|
+
value: [null, 'test'],
|
|
256
256
|
seq: 1,
|
|
257
257
|
ts: Date.now() / 1000,
|
|
258
258
|
};
|
|
@@ -264,9 +264,9 @@ describe('GraphTextCRDT', () => {
|
|
|
264
264
|
const op1 = crdt.insertLocal('node-1', 'content', 0, 'A');
|
|
265
265
|
|
|
266
266
|
const op2: InsertOp = {
|
|
267
|
+
ot: 'insert',
|
|
267
268
|
id: 'session-2:0',
|
|
268
|
-
|
|
269
|
-
parentId: null,
|
|
269
|
+
value: [null, 'B'],
|
|
270
270
|
seq: 1,
|
|
271
271
|
ts: Date.now() / 1000,
|
|
272
272
|
};
|
|
@@ -293,21 +293,26 @@ describe('GraphTextCRDT', () => {
|
|
|
293
293
|
expect(items.length).toBeGreaterThan(0);
|
|
294
294
|
|
|
295
295
|
const op: DeleteOp = {
|
|
296
|
-
|
|
296
|
+
ot: 'delete',
|
|
297
|
+
rm: [[items[0].id, 5]],
|
|
297
298
|
};
|
|
298
299
|
crdt.applyDelete('node-1', 'content', op);
|
|
299
300
|
expect(crdt.getText('node-1', 'content')).toBe(' World');
|
|
300
301
|
});
|
|
301
302
|
|
|
302
303
|
it('should handle empty deletion list', () => {
|
|
303
|
-
const op: DeleteOp = {
|
|
304
|
+
const op: DeleteOp = {
|
|
305
|
+
ot: 'delete',
|
|
306
|
+
rm: []
|
|
307
|
+
};
|
|
304
308
|
crdt.applyDelete('node-1', 'content', op);
|
|
305
309
|
expect(crdt.getText('node-1', 'content')).toBe('Hello World');
|
|
306
310
|
});
|
|
307
311
|
|
|
308
312
|
it('should auto-create rope if not initialized', () => {
|
|
309
313
|
const op: DeleteOp = {
|
|
310
|
-
|
|
314
|
+
ot: 'delete',
|
|
315
|
+
rm: [['session-1:0', 5]],
|
|
311
316
|
};
|
|
312
317
|
crdt.applyDelete('node-2', 'title', op);
|
|
313
318
|
expect(crdt.getText('node-2', 'title')).toBe('');
|
|
@@ -317,7 +322,8 @@ describe('GraphTextCRDT', () => {
|
|
|
317
322
|
const delOp = crdt.deleteLocal('node-1', 'content', 0, 5);
|
|
318
323
|
|
|
319
324
|
const remoteOp: DeleteOp = {
|
|
320
|
-
|
|
325
|
+
ot: 'delete',
|
|
326
|
+
rm: [['session-1:5', 6]],
|
|
321
327
|
};
|
|
322
328
|
crdt.applyDelete('node-1', 'content', remoteOp);
|
|
323
329
|
|
|
@@ -338,16 +344,12 @@ describe('GraphTextCRDT', () => {
|
|
|
338
344
|
const items = getItems(rope);
|
|
339
345
|
|
|
340
346
|
const op: ReplaceOp = {
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
parentId: null,
|
|
348
|
-
seq: 2,
|
|
349
|
-
ts: Date.now() / 1000,
|
|
350
|
-
},
|
|
347
|
+
ot: 'replace',
|
|
348
|
+
rm: [[items[0].id, 5]],
|
|
349
|
+
id: 'session-2:0',
|
|
350
|
+
value: [null, 'Hi'],
|
|
351
|
+
seq: 2,
|
|
352
|
+
ts: Date.now() / 1000,
|
|
351
353
|
};
|
|
352
354
|
crdt.applyReplace('node-1', 'content', op);
|
|
353
355
|
expect(crdt.getText('node-1', 'content')).toBe('Hi World');
|
|
@@ -358,16 +360,12 @@ describe('GraphTextCRDT', () => {
|
|
|
358
360
|
const items = getItems(rope);
|
|
359
361
|
|
|
360
362
|
const op: ReplaceOp = {
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
parentId: null,
|
|
368
|
-
seq: 2,
|
|
369
|
-
ts: Date.now() / 1000,
|
|
370
|
-
},
|
|
363
|
+
ot: 'replace',
|
|
364
|
+
rm: [[items[0].id, 6]],
|
|
365
|
+
id: 'session-2:0',
|
|
366
|
+
value: [null, ''],
|
|
367
|
+
seq: 2,
|
|
368
|
+
ts: Date.now() / 1000,
|
|
371
369
|
};
|
|
372
370
|
crdt.applyReplace('node-1', 'content', op);
|
|
373
371
|
expect(crdt.getText('node-1', 'content')).toBe('World');
|
|
@@ -375,14 +373,12 @@ describe('GraphTextCRDT', () => {
|
|
|
375
373
|
|
|
376
374
|
it('should auto-create rope if not initialized', () => {
|
|
377
375
|
const op: ReplaceOp = {
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
ts: Date.now() / 1000,
|
|
385
|
-
},
|
|
376
|
+
ot: 'replace',
|
|
377
|
+
rm: [],
|
|
378
|
+
id: 'session-2:0',
|
|
379
|
+
value: [null, 'new'],
|
|
380
|
+
seq: 1,
|
|
381
|
+
ts: Date.now() / 1000,
|
|
386
382
|
};
|
|
387
383
|
crdt.applyReplace('node-2', 'title', op);
|
|
388
384
|
expect(crdt.getText('node-2', 'title')).toBe('new');
|
|
@@ -571,9 +567,9 @@ describe('GraphTextCRDT', () => {
|
|
|
571
567
|
|
|
572
568
|
// Initialize both from the same initial state
|
|
573
569
|
const initOp: InsertOp = {
|
|
570
|
+
ot: 'insert',
|
|
574
571
|
id: 'init:0',
|
|
575
|
-
|
|
576
|
-
parentId: null,
|
|
572
|
+
value: [null, 'Hello'],
|
|
577
573
|
seq: 1,
|
|
578
574
|
ts: Date.now() / 1000,
|
|
579
575
|
};
|
|
@@ -600,9 +596,9 @@ describe('GraphTextCRDT', () => {
|
|
|
600
596
|
|
|
601
597
|
// Initialize from same initial state
|
|
602
598
|
const initOp: InsertOp = {
|
|
599
|
+
ot: 'insert',
|
|
603
600
|
id: 'init:0',
|
|
604
|
-
|
|
605
|
-
parentId: null,
|
|
601
|
+
value: [null, 'ABCDEF'],
|
|
606
602
|
seq: 1,
|
|
607
603
|
ts: Date.now() / 1000,
|
|
608
604
|
};
|
|
@@ -626,9 +622,9 @@ describe('GraphTextCRDT', () => {
|
|
|
626
622
|
|
|
627
623
|
// Initialize from same initial state
|
|
628
624
|
const initOp: InsertOp = {
|
|
625
|
+
ot: 'insert',
|
|
629
626
|
id: 'init:0',
|
|
630
|
-
|
|
631
|
-
parentId: null,
|
|
627
|
+
value: [null, 'test'],
|
|
632
628
|
seq: 1,
|
|
633
629
|
ts: Date.now() / 1000,
|
|
634
630
|
};
|
|
@@ -645,9 +641,9 @@ describe('GraphTextCRDT', () => {
|
|
|
645
641
|
|
|
646
642
|
// Apply ops from crdt2 to crdt1
|
|
647
643
|
for (const op of ops2) {
|
|
648
|
-
if ('
|
|
644
|
+
if (op.ot === 'insert') {
|
|
649
645
|
crdt1.applyInsert('doc-1', 'content', op as InsertOp);
|
|
650
|
-
} else if ('
|
|
646
|
+
} else if (op.ot === 'delete') {
|
|
651
647
|
crdt1.applyDelete('doc-1', 'content', op as DeleteOp);
|
|
652
648
|
} else {
|
|
653
649
|
crdt1.applyReplace('doc-1', 'content', op as ReplaceOp);
|
|
@@ -656,9 +652,9 @@ describe('GraphTextCRDT', () => {
|
|
|
656
652
|
|
|
657
653
|
// Apply ops from crdt1 to crdt2
|
|
658
654
|
for (const op of ops1) {
|
|
659
|
-
if ('
|
|
655
|
+
if (op.ot === 'insert') {
|
|
660
656
|
crdt2.applyInsert('doc-1', 'content', op as InsertOp);
|
|
661
|
-
} else if ('
|
|
657
|
+
} else if (op.ot === 'delete') {
|
|
662
658
|
crdt2.applyDelete('doc-1', 'content', op as DeleteOp);
|
|
663
659
|
} else {
|
|
664
660
|
crdt2.applyReplace('doc-1', 'content', op as ReplaceOp);
|
|
@@ -682,7 +678,7 @@ describe('GraphTextCRDT', () => {
|
|
|
682
678
|
crdt.init('node-1', 'content', 'test');
|
|
683
679
|
const op = crdt.insertLocal('node-1', 'content', 0, '');
|
|
684
680
|
expect(crdt.getText('node-1', 'content')).toBe('test');
|
|
685
|
-
expect(op.
|
|
681
|
+
expect(op.value[1]).toBe('');
|
|
686
682
|
});
|
|
687
683
|
|
|
688
684
|
it('should handle very long text', () => {
|
package/tests/crdt/rope.test.ts
CHANGED
|
@@ -170,8 +170,8 @@ describe('TextRope', () => {
|
|
|
170
170
|
remove(rope1, 0, 1); // delete 'a'
|
|
171
171
|
remove(rope2, 2, 1); // delete 'c'
|
|
172
172
|
|
|
173
|
-
applyDelete(rope1, {
|
|
174
|
-
applyDelete(rope2, {
|
|
173
|
+
applyDelete(rope1, { ot: 'delete', rm: [[opC.id, 1]] });
|
|
174
|
+
applyDelete(rope2, { ot: 'delete', rm: [[opA.id, 1]] });
|
|
175
175
|
|
|
176
176
|
expect(getText(rope1)).toBe('b');
|
|
177
177
|
expect(getText(rope2)).toBe('b');
|
|
@@ -224,8 +224,9 @@ describe('TextRope', () => {
|
|
|
224
224
|
|
|
225
225
|
// Critical assertion: Alice's "range" should have Alice's last character as parent
|
|
226
226
|
// NOT Bob's character, even though Bob's content is in the merged rope
|
|
227
|
-
const aliceLastCharSeq = parseItemId(aliceOp1.id).seq + aliceOp1.
|
|
228
|
-
const
|
|
227
|
+
const aliceLastCharSeq = parseItemId(aliceOp1.id).seq + aliceOp1.value[1].length - 1;
|
|
228
|
+
const anchor = aliceOp2.value[0]; // anchor is at [0]
|
|
229
|
+
const aliceOp2Parent = anchor ? parseItemId(anchor) : null;
|
|
229
230
|
|
|
230
231
|
expect(aliceOp2Parent).not.toBeNull();
|
|
231
232
|
expect(aliceOp2Parent!.agent).toBe('Alice'); // Parent should be Alice's agent
|
|
@@ -407,7 +408,7 @@ describe('TextRope', () => {
|
|
|
407
408
|
insert(rope, 0, 'hello');
|
|
408
409
|
const deleted = remove(rope, 2, 0);
|
|
409
410
|
expect(getText(rope)).toBe('hello');
|
|
410
|
-
expect(deleted.
|
|
411
|
+
expect(deleted.rm).toHaveLength(0);
|
|
411
412
|
});
|
|
412
413
|
|
|
413
414
|
it('should handle delete past end', () => {
|
|
@@ -562,8 +563,8 @@ describe('TextRope', () => {
|
|
|
562
563
|
|
|
563
564
|
const op = replace(rope, 0, 5, 'world');
|
|
564
565
|
|
|
565
|
-
expect(op.
|
|
566
|
-
expect(op.
|
|
566
|
+
expect(op.rm.length).toBeGreaterThan(0);
|
|
567
|
+
expect(op.value[1]).toBe('world'); // content is at [1]
|
|
567
568
|
});
|
|
568
569
|
|
|
569
570
|
it('should work with applyReplace on remote rope', () => {
|
|
@@ -1132,7 +1133,7 @@ describe('TextRope', () => {
|
|
|
1132
1133
|
apply(rope1, ins2);
|
|
1133
1134
|
apply(rope2, ins1);
|
|
1134
1135
|
// Also need to sync the delete
|
|
1135
|
-
const delOp = {
|
|
1136
|
+
const delOp: DeleteOp = { ot: 'delete', rm: [['agent-1:2', 2]] };
|
|
1136
1137
|
applyDelete(rope2, delOp);
|
|
1137
1138
|
|
|
1138
1139
|
expect(getText(rope1)).toBe(getText(rope2));
|
|
@@ -18,13 +18,13 @@ import type {
|
|
|
18
18
|
TextDeleteOp,
|
|
19
19
|
} from '../../src/operations/OperationTypes.js';
|
|
20
20
|
|
|
21
|
-
function createMsg(op: Operation,
|
|
21
|
+
function createMsg(op: Operation, client = 'test-session', lamport = 1): CRDTMessage {
|
|
22
22
|
return {
|
|
23
23
|
id: `msg-${Math.random().toString(36).slice(2)}`,
|
|
24
|
-
|
|
25
|
-
clock: { [
|
|
26
|
-
|
|
27
|
-
|
|
24
|
+
client,
|
|
25
|
+
clock: { [client]: lamport },
|
|
26
|
+
lt: lamport,
|
|
27
|
+
ts: Date.now() / 1000,
|
|
28
28
|
ops: [op],
|
|
29
29
|
};
|
|
30
30
|
}
|
|
@@ -39,7 +39,7 @@ function getText(value: any): string {
|
|
|
39
39
|
function createNode(graph: SceneGraph, key: string): SceneGraph {
|
|
40
40
|
const nodeOp: Operation = {
|
|
41
41
|
key: '',
|
|
42
|
-
|
|
42
|
+
ot: 'node.insert',
|
|
43
43
|
path: 'children',
|
|
44
44
|
value: {
|
|
45
45
|
key,
|
|
@@ -62,7 +62,7 @@ describe('Text CRDT Operations', () => {
|
|
|
62
62
|
it('should initialize an empty text property', () => {
|
|
63
63
|
const op: TextInitOp = {
|
|
64
64
|
key: 'doc-1',
|
|
65
|
-
|
|
65
|
+
ot: 'text.init',
|
|
66
66
|
path: 'content',
|
|
67
67
|
};
|
|
68
68
|
const result = applyMessage(graph, createMsg(op));
|
|
@@ -73,7 +73,7 @@ describe('Text CRDT Operations', () => {
|
|
|
73
73
|
it('should initialize text with initial content', () => {
|
|
74
74
|
const op: TextInitOp = {
|
|
75
75
|
key: 'doc-1',
|
|
76
|
-
|
|
76
|
+
ot: 'text.init',
|
|
77
77
|
path: 'content',
|
|
78
78
|
value: 'Hello, World!',
|
|
79
79
|
};
|
|
@@ -88,14 +88,14 @@ describe('Text CRDT Operations', () => {
|
|
|
88
88
|
// Initialize
|
|
89
89
|
let result = applyMessage(graph, createMsg({
|
|
90
90
|
key: 'doc-1',
|
|
91
|
-
|
|
91
|
+
ot: 'text.init',
|
|
92
92
|
path: 'content',
|
|
93
93
|
} as TextInitOp));
|
|
94
94
|
|
|
95
95
|
// Insert
|
|
96
96
|
result = applyMessage(result, createMsg({
|
|
97
97
|
key: 'doc-1',
|
|
98
|
-
|
|
98
|
+
ot: 'text.insert',
|
|
99
99
|
path: 'content',
|
|
100
100
|
position: 0,
|
|
101
101
|
value: 'Hello',
|
|
@@ -107,14 +107,14 @@ describe('Text CRDT Operations', () => {
|
|
|
107
107
|
it('should insert text at the end', () => {
|
|
108
108
|
let result = applyMessage(graph, createMsg({
|
|
109
109
|
key: 'doc-1',
|
|
110
|
-
|
|
110
|
+
ot: 'text.init',
|
|
111
111
|
path: 'content',
|
|
112
112
|
value: 'Hello',
|
|
113
113
|
} as TextInitOp));
|
|
114
114
|
|
|
115
115
|
result = applyMessage(result, createMsg({
|
|
116
116
|
key: 'doc-1',
|
|
117
|
-
|
|
117
|
+
ot: 'text.insert',
|
|
118
118
|
path: 'content',
|
|
119
119
|
position: 5,
|
|
120
120
|
value: ' World',
|
|
@@ -126,14 +126,14 @@ describe('Text CRDT Operations', () => {
|
|
|
126
126
|
it('should insert text in the middle', () => {
|
|
127
127
|
let result = applyMessage(graph, createMsg({
|
|
128
128
|
key: 'doc-1',
|
|
129
|
-
|
|
129
|
+
ot: 'text.init',
|
|
130
130
|
path: 'content',
|
|
131
131
|
value: 'Hllo',
|
|
132
132
|
} as TextInitOp));
|
|
133
133
|
|
|
134
134
|
result = applyMessage(result, createMsg({
|
|
135
135
|
key: 'doc-1',
|
|
136
|
-
|
|
136
|
+
ot: 'text.insert',
|
|
137
137
|
path: 'content',
|
|
138
138
|
position: 1,
|
|
139
139
|
value: 'e',
|
|
@@ -147,14 +147,14 @@ describe('Text CRDT Operations', () => {
|
|
|
147
147
|
it('should delete text from the beginning', () => {
|
|
148
148
|
let result = applyMessage(graph, createMsg({
|
|
149
149
|
key: 'doc-1',
|
|
150
|
-
|
|
150
|
+
ot: 'text.init',
|
|
151
151
|
path: 'content',
|
|
152
152
|
value: 'Hello World',
|
|
153
153
|
} as TextInitOp));
|
|
154
154
|
|
|
155
155
|
result = applyMessage(result, createMsg({
|
|
156
156
|
key: 'doc-1',
|
|
157
|
-
|
|
157
|
+
ot: 'text.delete',
|
|
158
158
|
path: 'content',
|
|
159
159
|
position: 0,
|
|
160
160
|
length: 6,
|
|
@@ -166,14 +166,14 @@ describe('Text CRDT Operations', () => {
|
|
|
166
166
|
it('should delete text from the end', () => {
|
|
167
167
|
let result = applyMessage(graph, createMsg({
|
|
168
168
|
key: 'doc-1',
|
|
169
|
-
|
|
169
|
+
ot: 'text.init',
|
|
170
170
|
path: 'content',
|
|
171
171
|
value: 'Hello World',
|
|
172
172
|
} as TextInitOp));
|
|
173
173
|
|
|
174
174
|
result = applyMessage(result, createMsg({
|
|
175
175
|
key: 'doc-1',
|
|
176
|
-
|
|
176
|
+
ot: 'text.delete',
|
|
177
177
|
path: 'content',
|
|
178
178
|
position: 5,
|
|
179
179
|
length: 6,
|
|
@@ -185,14 +185,14 @@ describe('Text CRDT Operations', () => {
|
|
|
185
185
|
it('should delete text from the middle', () => {
|
|
186
186
|
let result = applyMessage(graph, createMsg({
|
|
187
187
|
key: 'doc-1',
|
|
188
|
-
|
|
188
|
+
ot: 'text.init',
|
|
189
189
|
path: 'content',
|
|
190
190
|
value: 'Hello World',
|
|
191
191
|
} as TextInitOp));
|
|
192
192
|
|
|
193
193
|
result = applyMessage(result, createMsg({
|
|
194
194
|
key: 'doc-1',
|
|
195
|
-
|
|
195
|
+
ot: 'text.delete',
|
|
196
196
|
path: 'content',
|
|
197
197
|
position: 2,
|
|
198
198
|
length: 6,
|
|
@@ -207,14 +207,14 @@ describe('Text CRDT Operations', () => {
|
|
|
207
207
|
// User A and User B both start with same content
|
|
208
208
|
let graphA = applyMessage(graph, createMsg({
|
|
209
209
|
key: 'doc-1',
|
|
210
|
-
|
|
210
|
+
ot: 'text.init',
|
|
211
211
|
path: 'content',
|
|
212
212
|
value: 'Hello',
|
|
213
213
|
} as TextInitOp, 'user-a', 1));
|
|
214
214
|
|
|
215
215
|
let graphB = applyMessage(graph, createMsg({
|
|
216
216
|
key: 'doc-1',
|
|
217
|
-
|
|
217
|
+
ot: 'text.init',
|
|
218
218
|
path: 'content',
|
|
219
219
|
value: 'Hello',
|
|
220
220
|
} as TextInitOp, 'user-b', 1));
|
|
@@ -222,7 +222,7 @@ describe('Text CRDT Operations', () => {
|
|
|
222
222
|
// User A inserts at position 0
|
|
223
223
|
graphA = applyMessage(graphA, createMsg({
|
|
224
224
|
key: 'doc-1',
|
|
225
|
-
|
|
225
|
+
ot: 'text.insert',
|
|
226
226
|
path: 'content',
|
|
227
227
|
position: 0,
|
|
228
228
|
value: 'A',
|
|
@@ -231,7 +231,7 @@ describe('Text CRDT Operations', () => {
|
|
|
231
231
|
// User B inserts at position 5
|
|
232
232
|
graphB = applyMessage(graphB, createMsg({
|
|
233
233
|
key: 'doc-1',
|
|
234
|
-
|
|
234
|
+
ot: 'text.insert',
|
|
235
235
|
path: 'content',
|
|
236
236
|
position: 5,
|
|
237
237
|
value: 'B',
|
|
@@ -250,7 +250,7 @@ describe('Text CRDT Operations', () => {
|
|
|
250
250
|
it('should return the underlying TextRope', () => {
|
|
251
251
|
const result = applyMessage(graph, createMsg({
|
|
252
252
|
key: 'doc-1',
|
|
253
|
-
|
|
253
|
+
ot: 'text.init',
|
|
254
254
|
path: 'content',
|
|
255
255
|
value: 'Test',
|
|
256
256
|
} as TextInitOp));
|
|
@@ -278,7 +278,7 @@ describe('Text CRDT Operations', () => {
|
|
|
278
278
|
// Initialize empty document
|
|
279
279
|
let result = applyMessage(graph, createMsg({
|
|
280
280
|
key: 'doc-1',
|
|
281
|
-
|
|
281
|
+
ot: 'text.init',
|
|
282
282
|
path: 'content',
|
|
283
283
|
} as TextInitOp));
|
|
284
284
|
|
|
@@ -297,7 +297,7 @@ describe('Text CRDT Operations', () => {
|
|
|
297
297
|
if (del > 0) {
|
|
298
298
|
result = applyMessage(result, createMsg({
|
|
299
299
|
key: 'doc-1',
|
|
300
|
-
|
|
300
|
+
ot: 'text.delete',
|
|
301
301
|
path: 'content',
|
|
302
302
|
position: pos,
|
|
303
303
|
length: del,
|
|
@@ -308,7 +308,7 @@ describe('Text CRDT Operations', () => {
|
|
|
308
308
|
if (ins.length > 0) {
|
|
309
309
|
result = applyMessage(result, createMsg({
|
|
310
310
|
key: 'doc-1',
|
|
311
|
-
|
|
311
|
+
ot: 'text.insert',
|
|
312
312
|
path: 'content',
|
|
313
313
|
position: pos,
|
|
314
314
|
value: ins,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
{"name": "array_set", "op": {"key": "node-1", "
|
|
2
|
-
{"name": "array_set_empty", "op": {"key": "node-1", "
|
|
3
|
-
{"name": "array_push", "op": {"key": "node-1", "
|
|
4
|
-
{"name": "array_push_number", "op": {"key": "node-1", "
|
|
5
|
-
{"name": "array_union", "op": {"key": "node-1", "
|
|
6
|
-
{"name": "array_remove", "op": {"key": "node-1", "
|
|
1
|
+
{"name": "array_set", "op": {"key": "node-1", "ot": "array.set", "path": "tags", "value": ["enemy", "active"]}}
|
|
2
|
+
{"name": "array_set_empty", "op": {"key": "node-1", "ot": "array.set", "path": "tags", "value": []}}
|
|
3
|
+
{"name": "array_push", "op": {"key": "node-1", "ot": "array.push", "path": "tags", "value": "new-tag"}}
|
|
4
|
+
{"name": "array_push_number", "op": {"key": "node-1", "ot": "array.push", "path": "scores", "value": 100}}
|
|
5
|
+
{"name": "array_union", "op": {"key": "node-1", "ot": "array.union", "path": "tags", "value": ["tag-a", "tag-b"]}}
|
|
6
|
+
{"name": "array_remove", "op": {"key": "node-1", "ot": "array.remove", "path": "tags", "value": "enemy"}}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
{"name": "boolean_set_true", "op": {"key": "node-1", "
|
|
2
|
-
{"name": "boolean_set_false", "op": {"key": "node-1", "
|
|
3
|
-
{"name": "boolean_or_true", "op": {"key": "node-1", "
|
|
4
|
-
{"name": "boolean_or_false", "op": {"key": "node-1", "
|
|
5
|
-
{"name": "boolean_and_true", "op": {"key": "node-1", "
|
|
6
|
-
{"name": "boolean_and_false", "op": {"key": "node-1", "
|
|
1
|
+
{"name": "boolean_set_true", "op": {"key": "node-1", "ot": "boolean.set", "path": "visible", "value": true}}
|
|
2
|
+
{"name": "boolean_set_false", "op": {"key": "node-1", "ot": "boolean.set", "path": "visible", "value": false}}
|
|
3
|
+
{"name": "boolean_or_true", "op": {"key": "node-1", "ot": "boolean.or", "path": "enabled", "value": true}}
|
|
4
|
+
{"name": "boolean_or_false", "op": {"key": "node-1", "ot": "boolean.or", "path": "enabled", "value": false}}
|
|
5
|
+
{"name": "boolean_and_true", "op": {"key": "node-1", "ot": "boolean.and", "path": "active", "value": true}}
|
|
6
|
+
{"name": "boolean_and_false", "op": {"key": "node-1", "ot": "boolean.and", "path": "active", "value": false}}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
{"name": "color_set_red", "op": {"key": "node-1", "
|
|
2
|
-
{"name": "color_set_green", "op": {"key": "node-1", "
|
|
3
|
-
{"name": "color_set_rgb", "op": {"key": "node-1", "
|
|
4
|
-
{"name": "color_blend", "op": {"key": "node-1", "
|
|
1
|
+
{"name": "color_set_red", "op": {"key": "node-1", "ot": "color.set", "path": "color", "value": "#ff0000"}}
|
|
2
|
+
{"name": "color_set_green", "op": {"key": "node-1", "ot": "color.set", "path": "color", "value": "#00ff00"}}
|
|
3
|
+
{"name": "color_set_rgb", "op": {"key": "node-1", "ot": "color.set", "path": "color", "value": "#336699"}}
|
|
4
|
+
{"name": "color_blend", "op": {"key": "node-1", "ot": "color.blend", "path": "color", "value": "#ffffff"}}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
{"name": "merge_additive_ops", "ops": [{"key": "cube-1", "
|
|
2
|
-
{"name": "replace_lww_ops", "ops": [{"key": "cube-1", "
|
|
3
|
-
{"name": "multiple_properties", "ops": [{"key": "cube-1", "
|
|
1
|
+
{"name": "merge_additive_ops", "ops": [{"key": "cube-1", "ot": "vector3.add", "path": "position", "value": [1, 0, 0]}, {"key": "cube-1", "ot": "vector3.add", "path": "position", "value": [0, 1, 0]}, {"key": "cube-1", "ot": "vector3.add", "path": "position", "value": [0, 0, 1]}], "expected_merged": {"key": "cube-1", "ot": "vector3.add", "path": "position", "value": [1, 1, 1]}}
|
|
2
|
+
{"name": "replace_lww_ops", "ops": [{"key": "cube-1", "ot": "number.set", "path": "opacity", "value": 0.3}, {"key": "cube-1", "ot": "number.set", "path": "opacity", "value": 0.7}], "expected_merged": {"key": "cube-1", "ot": "number.set", "path": "opacity", "value": 0.7}}
|
|
3
|
+
{"name": "multiple_properties", "ops": [{"key": "cube-1", "ot": "vector3.add", "path": "position", "value": [1, 0, 0]}, {"key": "cube-1", "ot": "number.set", "path": "opacity", "value": 0.5}], "expected_count": 2}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
{"name": "node_insert_root", "op": {"key": "", "
|
|
2
|
-
{"name": "node_insert_mesh", "op": {"key": "scene", "
|
|
3
|
-
{"name": "node_insert_group", "op": {"key": "scene", "
|
|
4
|
-
{"name": "node_insert_nested", "op": {"key": "group-1", "
|
|
5
|
-
{"name": "node_remove", "op": {"key": "scene", "
|
|
6
|
-
{"name": "node_remove_nested", "op": {"key": "group-1", "
|
|
1
|
+
{"name": "node_insert_root", "op": {"key": "", "ot": "node.insert", "path": "children", "value": {"key": "scene", "tag": "Scene", "name": "Root Scene"}}}
|
|
2
|
+
{"name": "node_insert_mesh", "op": {"key": "scene", "ot": "node.insert", "path": "children", "value": {"key": "cube-1", "tag": "Mesh", "name": "Cube", "position": [0, 0, 0], "scale": [1, 1, 1]}}}
|
|
3
|
+
{"name": "node_insert_group", "op": {"key": "scene", "ot": "node.insert", "path": "children", "value": {"key": "group-1", "tag": "Group", "name": "Group"}}}
|
|
4
|
+
{"name": "node_insert_nested", "op": {"key": "group-1", "ot": "node.insert", "path": "children", "value": {"key": "sphere-1", "tag": "Mesh", "name": "Sphere"}}}
|
|
5
|
+
{"name": "node_remove", "op": {"key": "scene", "ot": "node.remove", "path": "children", "value": "cube-1"}}
|
|
6
|
+
{"name": "node_remove_nested", "op": {"key": "group-1", "ot": "node.remove", "path": "children", "value": "sphere-1"}}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
{"name": "number_set", "op": {"key": "node-1", "
|
|
2
|
-
{"name": "number_add", "op": {"key": "node-1", "
|
|
3
|
-
{"name": "number_multiply", "op": {"key": "node-1", "
|
|
4
|
-
{"name": "number_min", "op": {"key": "node-1", "
|
|
5
|
-
{"name": "number_max", "op": {"key": "node-1", "
|
|
6
|
-
{"name": "number_add_negative", "op": {"key": "node-1", "
|
|
7
|
-
{"name": "number_multiply_fraction", "op": {"key": "node-1", "
|
|
1
|
+
{"name": "number_set", "op": {"key": "node-1", "ot": "number.set", "path": "opacity", "value": 0.5}}
|
|
2
|
+
{"name": "number_add", "op": {"key": "node-1", "ot": "number.add", "path": "score", "value": 10}}
|
|
3
|
+
{"name": "number_multiply", "op": {"key": "node-1", "ot": "number.multiply", "path": "scale", "value": 2}}
|
|
4
|
+
{"name": "number_min", "op": {"key": "node-1", "ot": "number.min", "path": "health", "value": 50}}
|
|
5
|
+
{"name": "number_max", "op": {"key": "node-1", "ot": "number.max", "path": "damage", "value": 25}}
|
|
6
|
+
{"name": "number_add_negative", "op": {"key": "node-1", "ot": "number.add", "path": "score", "value": -5}}
|
|
7
|
+
{"name": "number_multiply_fraction", "op": {"key": "node-1", "ot": "number.multiply", "path": "scale", "value": 0.5}}
|