@vuer-ai/vuer-rtc-server 0.2.3 → 0.4.1

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 (104) hide show
  1. package/.env +1 -1
  2. package/README.md +56 -0
  3. package/dist/archive/ArchivalService.js +1 -1
  4. package/dist/archive/ArchivalService.js.map +1 -1
  5. package/dist/broker/InMemoryBroker.d.ts +2 -2
  6. package/dist/broker/InMemoryBroker.d.ts.map +1 -1
  7. package/dist/broker/InMemoryBroker.js +4 -4
  8. package/dist/broker/InMemoryBroker.js.map +1 -1
  9. package/dist/broker/types.d.ts +3 -3
  10. package/dist/broker/types.d.ts.map +1 -1
  11. package/dist/journal/CoalescingService.d.ts.map +1 -1
  12. package/dist/journal/CoalescingService.js +18 -208
  13. package/dist/journal/CoalescingService.js.map +1 -1
  14. package/dist/journal/GraphJournalService.d.ts +127 -0
  15. package/dist/journal/GraphJournalService.d.ts.map +1 -0
  16. package/dist/journal/GraphJournalService.js +491 -0
  17. package/dist/journal/GraphJournalService.js.map +1 -0
  18. package/dist/journal/JournalRLE.d.ts +2 -2
  19. package/dist/journal/JournalRLE.js +14 -14
  20. package/dist/journal/JournalRLE.js.map +1 -1
  21. package/dist/journal/JournalRepository.js +7 -7
  22. package/dist/journal/JournalRepository.js.map +1 -1
  23. package/dist/journal/JournalService.d.ts.map +1 -1
  24. package/dist/journal/JournalService.js +6 -40
  25. package/dist/journal/JournalService.js.map +1 -1
  26. package/dist/journal/RLECompression.d.ts +9 -9
  27. package/dist/journal/RLECompression.d.ts.map +1 -1
  28. package/dist/journal/RLECompression.js +22 -22
  29. package/dist/journal/RLECompression.js.map +1 -1
  30. package/dist/journal/TextJournalService.d.ts +98 -0
  31. package/dist/journal/TextJournalService.d.ts.map +1 -0
  32. package/dist/journal/TextJournalService.js +401 -0
  33. package/dist/journal/TextJournalService.js.map +1 -0
  34. package/dist/journal/index.d.ts +3 -1
  35. package/dist/journal/index.d.ts.map +1 -1
  36. package/dist/journal/index.js +4 -1
  37. package/dist/journal/index.js.map +1 -1
  38. package/dist/journal/rle-demo.js +11 -11
  39. package/dist/journal/rle-demo.js.map +1 -1
  40. package/dist/serve.d.ts +29 -11
  41. package/dist/serve.d.ts.map +1 -1
  42. package/dist/serve.js +558 -93
  43. package/dist/serve.js.map +1 -1
  44. package/dist/transport/RTCServer.d.ts +2 -2
  45. package/dist/transport/RTCServer.d.ts.map +1 -1
  46. package/dist/transport/RTCServer.js +22 -22
  47. package/dist/transport/RTCServer.js.map +1 -1
  48. package/docs/API.md +642 -0
  49. package/examples/compression-example.ts +3 -3
  50. package/package.json +2 -2
  51. package/prisma/schema.prisma +124 -6
  52. package/src/archive/ArchivalService.ts +1 -1
  53. package/src/broker/InMemoryBroker.ts +4 -4
  54. package/src/broker/types.ts +3 -3
  55. package/src/journal/CoalescingService.ts +18 -235
  56. package/src/journal/{JournalService.ts → GraphJournalService.ts} +34 -74
  57. package/src/journal/JournalRLE.ts +15 -15
  58. package/src/journal/JournalRepository.ts +7 -7
  59. package/src/journal/RLECompression.ts +24 -24
  60. package/src/journal/TextJournalService.ts +483 -0
  61. package/src/journal/index.ts +10 -2
  62. package/src/journal/rle-demo.ts +11 -11
  63. package/src/serve.ts +598 -94
  64. package/src/transport/RTCServer.ts +23 -23
  65. package/tests/benchmark/journal-optimization-benchmark.test.ts +14 -14
  66. package/tests/compression/compression.test.ts +8 -8
  67. package/tests/demo.ts +88 -88
  68. package/tests/e2e/convergence.test.ts +9 -9
  69. package/tests/e2e/helpers/assertions.ts +22 -0
  70. package/tests/e2e/helpers/createTestServer.ts +4 -4
  71. package/tests/e2e/latency.test.ts +47 -41
  72. package/tests/e2e/packet-loss.test.ts +6 -6
  73. package/tests/e2e/relay.test.ts +9 -9
  74. package/tests/e2e/sync-perf.test.ts +5 -5
  75. package/tests/e2e/sync-reconciliation.test.ts +6 -6
  76. package/tests/e2e/text-sync.test.ts +14 -14
  77. package/tests/e2e/tombstone-convergence.test.ts +22 -22
  78. package/tests/fixtures/array-ops.jsonl +6 -6
  79. package/tests/fixtures/boolean-ops.jsonl +6 -6
  80. package/tests/fixtures/color-ops.jsonl +4 -4
  81. package/tests/fixtures/edit-buffer.jsonl +3 -3
  82. package/tests/fixtures/messages.jsonl +4 -4
  83. package/tests/fixtures/node-ops.jsonl +6 -6
  84. package/tests/fixtures/number-ops.jsonl +7 -7
  85. package/tests/fixtures/object-ops.jsonl +4 -4
  86. package/tests/fixtures/operations.jsonl +7 -7
  87. package/tests/fixtures/string-ops.jsonl +4 -4
  88. package/tests/fixtures/undo-redo.jsonl +3 -3
  89. package/tests/fixtures/vector-ops.jsonl +9 -9
  90. package/tests/integration/repositories.test.ts +8 -9
  91. package/tests/journal/compaction-load-bug.test.ts +31 -31
  92. package/tests/journal/compaction.test.ts +26 -26
  93. package/tests/journal/journal-rle.test.ts +38 -38
  94. package/tests/journal/journal-service.test.ts +13 -13
  95. package/tests/journal/lww-ordering-bug.test.ts +39 -39
  96. package/tests/journal/rle-compression.test.ts +71 -71
  97. package/tests/journal/text-coalescing.test.ts +34 -34
  98. package/tests/test-data/datatypes.ts +85 -85
  99. package/tests/test-data/operations-example.ts +62 -62
  100. package/tests/test-data/scene-example.ts +11 -11
  101. package/tests/unit/operations.test.ts +7 -7
  102. package/tests/unit/s3-compression.test.ts +5 -3
  103. package/tests/unit/vectorClock.test.ts +2 -2
  104. package/tests/journal/multi-session-coalescing.test.ts +0 -871
@@ -21,17 +21,17 @@ import type { CRDTMessage, SceneGraph, Operation, Snapshot } from '@vuer-ai/vuer
21
21
  /** Build a CRDTMessage with sensible defaults */
22
22
  function makeMsg(
23
23
  id: string,
24
- sessionId: string,
24
+ client: string,
25
25
  ops: Operation[],
26
- lamportTime: number,
26
+ lt: number,
27
27
  clock?: Record<string, number>,
28
28
  ): CRDTMessage {
29
29
  return {
30
30
  id,
31
- sessionId,
32
- clock: clock ?? { [sessionId]: lamportTime },
33
- lamportTime,
34
- timestamp: Date.now() / 1000,
31
+ client,
32
+ clock: clock ?? { [client]: lt },
33
+ lt,
34
+ ts: Date.now() / 1000,
35
35
  ops,
36
36
  };
37
37
  }
@@ -40,7 +40,7 @@ function makeMsg(
40
40
  function nodeInsertOp(parentKey: string, nodeKey: string, props: Record<string, unknown> = {}): Operation {
41
41
  return {
42
42
  key: parentKey,
43
- otype: 'node.insert',
43
+ ot: 'node.insert',
44
44
  path: 'children',
45
45
  value: {
46
46
  key: nodeKey,
@@ -55,7 +55,7 @@ function nodeInsertOp(parentKey: string, nodeKey: string, props: Record<string,
55
55
  function vec3SetOp(nodeKey: string, path: string, value: [number, number, number]): Operation {
56
56
  return {
57
57
  key: nodeKey,
58
- otype: 'vector3.set',
58
+ ot: 'vector3.set',
59
59
  path,
60
60
  value,
61
61
  } as Operation;
@@ -65,7 +65,7 @@ function vec3SetOp(nodeKey: string, path: string, value: [number, number, number
65
65
  function numSetOp(nodeKey: string, path: string, value: number): Operation {
66
66
  return {
67
67
  key: nodeKey,
68
- otype: 'number.set',
68
+ ot: 'number.set',
69
69
  path,
70
70
  value,
71
71
  } as Operation;
@@ -75,7 +75,7 @@ function numSetOp(nodeKey: string, path: string, value: number): Operation {
75
75
  function numAddOp(nodeKey: string, path: string, value: number): Operation {
76
76
  return {
77
77
  key: nodeKey,
78
- otype: 'number.add',
78
+ ot: 'number.add',
79
79
  path,
80
80
  value,
81
81
  } as Operation;
@@ -85,7 +85,7 @@ function numAddOp(nodeKey: string, path: string, value: number): Operation {
85
85
  function nodeRemoveOp(parentKey: string, nodeKey: string): Operation {
86
86
  return {
87
87
  key: parentKey,
88
- otype: 'node.remove',
88
+ ot: 'node.remove',
89
89
  path: 'children',
90
90
  value: nodeKey,
91
91
  } as Operation;
@@ -131,7 +131,7 @@ class InMemoryJournalService {
131
131
  snapshot: {
132
132
  graph: createEmptyGraph(),
133
133
  vectorClock: {},
134
- lamportTime: 0,
134
+ lt: 0,
135
135
  journalIndex: 0,
136
136
  },
137
137
  journal: [],
@@ -161,11 +161,11 @@ class InMemoryJournalService {
161
161
 
162
162
  // Meta ops
163
163
  for (const op of msg.ops) {
164
- if (op.otype === 'meta.undo') {
164
+ if (op.ot === 'meta.undo') {
165
165
  const targetId = (op as any).targetMsgId;
166
166
  const target = state.journal.find((e) => e.msg.id === targetId);
167
- if (target) target.deletedAt = msg.timestamp;
168
- } else if (op.otype === 'meta.redo') {
167
+ if (target) target.deletedAt = msg.ts;
168
+ } else if (op.ot === 'meta.redo') {
169
169
  const targetId = (op as any).targetMsgId;
170
170
  const target = state.journal.find((e) => e.msg.id === targetId);
171
171
  if (target) delete target.deletedAt;
@@ -181,7 +181,7 @@ class InMemoryJournalService {
181
181
  let graph = state.snapshot.graph;
182
182
  for (const entry of state.journal) {
183
183
  if (entry.deletedAt) continue;
184
- const realOps = entry.msg.ops.filter((op) => !op.otype.startsWith('meta.'));
184
+ const realOps = entry.msg.ops.filter((op) => !op.ot.startsWith('meta.'));
185
185
  if (realOps.length > 0) {
186
186
  graph = applyMessage(graph, { ...entry.msg, ops: realOps });
187
187
  }
@@ -208,20 +208,20 @@ class InMemoryJournalService {
208
208
 
209
209
  let maxLamport = state.snapshot.journalIndex;
210
210
  for (const entry of state.journal) {
211
- maxLamport = Math.max(maxLamport, entry.msg.lamportTime);
211
+ maxLamport = Math.max(maxLamport, entry.msg.lt);
212
212
  }
213
213
 
214
214
  let mergedClock = { ...state.snapshot.vectorClock };
215
215
  for (const entry of state.journal) {
216
- for (const [sessionId, time] of Object.entries(entry.msg.clock)) {
217
- mergedClock[sessionId] = Math.max(mergedClock[sessionId] || 0, time);
216
+ for (const [client, time] of Object.entries(entry.msg.clock)) {
217
+ mergedClock[client] = Math.max(mergedClock[client] || 0, time);
218
218
  }
219
219
  }
220
220
 
221
221
  state.snapshot = {
222
222
  graph: newGraph,
223
223
  vectorClock: mergedClock,
224
- lamportTime: maxLamport,
224
+ lt: maxLamport,
225
225
  journalIndex: maxLamport,
226
226
  };
227
227
 
@@ -379,7 +379,7 @@ describe('JournalService.compact()', () => {
379
379
 
380
380
  const stateAfter = svc.loadDocument(DOC_ID)!;
381
381
  expect(stateAfter.journal).toHaveLength(0);
382
- expect(stateAfter.snapshot.lamportTime).toBe(snapshotBefore.lamportTime);
382
+ expect(stateAfter.snapshot.lt).toBe(snapshotBefore.lt);
383
383
  });
384
384
 
385
385
  it('should compact a single entry', () => {
@@ -390,7 +390,7 @@ describe('JournalService.compact()', () => {
390
390
  const state = svc.loadDocument(DOC_ID)!;
391
391
  expect(state.journal).toHaveLength(0);
392
392
  expect(state.snapshot.graph.nodes['scene']).toBeDefined();
393
- expect(state.snapshot.lamportTime).toBe(1);
393
+ expect(state.snapshot.lt).toBe(1);
394
394
  });
395
395
 
396
396
  it('should compact with mixed operation types', () => {
@@ -429,7 +429,7 @@ describe('JournalService.compact()', () => {
429
429
  const state = svc.loadDocument(DOC_ID)!;
430
430
  expect(state.snapshot.vectorClock['alice']).toBe(1);
431
431
  expect(state.snapshot.vectorClock['bob']).toBe(1);
432
- expect(state.snapshot.lamportTime).toBe(2);
432
+ expect(state.snapshot.lt).toBe(2);
433
433
  });
434
434
 
435
435
  it('should handle undo entries during compaction (skip deleted)', () => {
@@ -441,7 +441,7 @@ describe('JournalService.compact()', () => {
441
441
  // Undo the position change
442
442
  svc.processMessage(
443
443
  DOC_ID,
444
- makeMsg('m4-undo', 'a', [{ key: '_meta', otype: 'meta.undo', path: '_meta', targetMsgId: 'm3' } as any], 4),
444
+ makeMsg('m4-undo', 'a', [{ key: '_meta', ot: 'meta.undo', path: '_meta', targetMsgId: 'm3' } as any], 4),
445
445
  );
446
446
 
447
447
  const graphBefore = svc.computeGraph(svc.loadDocument(DOC_ID)!);
@@ -588,12 +588,12 @@ describe('Edge Cases', () => {
588
588
  // Undo m3
589
589
  svc.processMessage(
590
590
  DOC_ID,
591
- makeMsg('m4', 'a', [{ key: '_meta', otype: 'meta.undo', path: '_meta', targetMsgId: 'm3' } as any], 4),
591
+ makeMsg('m4', 'a', [{ key: '_meta', ot: 'meta.undo', path: '_meta', targetMsgId: 'm3' } as any], 4),
592
592
  );
593
593
  // Redo m3
594
594
  svc.processMessage(
595
595
  DOC_ID,
596
- makeMsg('m5', 'a', [{ key: '_meta', otype: 'meta.redo', path: '_meta', targetMsgId: 'm3' } as any], 5),
596
+ makeMsg('m5', 'a', [{ key: '_meta', ot: 'meta.redo', path: '_meta', targetMsgId: 'm3' } as any], 5),
597
597
  );
598
598
 
599
599
  const graphBefore = svc.computeGraph(svc.loadDocument(DOC_ID)!);
@@ -23,22 +23,22 @@ import {
23
23
  */
24
24
  function createTestMessage(
25
25
  id: string,
26
- sessionId: string,
27
- lamportTime: number,
28
- timestamp: number,
26
+ client: string,
27
+ lt: number,
28
+ ts: number,
29
29
  ops: any[] = []
30
30
  ): CRDTMessage {
31
31
  return {
32
32
  id,
33
- sessionId,
34
- clock: { [sessionId]: lamportTime },
35
- lamportTime,
36
- timestamp,
33
+ client,
34
+ clock: { [client]: lt },
35
+ lt,
36
+ ts,
37
37
  ops: ops.length > 0 ? ops : [{
38
38
  key: 'cube-1',
39
- otype: 'vector3.set',
39
+ ot: 'vector3.set',
40
40
  path: 'position',
41
- value: [lamportTime, 0, 0],
41
+ value: [lt, 0, 0],
42
42
  }],
43
43
  };
44
44
  }
@@ -118,8 +118,8 @@ describe('JournalRLE - Run-Length Encoding', () => {
118
118
 
119
119
  it('should handle many agents with alternating pattern', () => {
120
120
  const messages = Array.from({ length: 10 }, (_, i) => {
121
- const sessionId = i % 2 === 0 ? 'session-1' : 'session-2';
122
- return createTestMessage(`msg-${i}`, sessionId, i, 1000 + i);
121
+ const client = i % 2 === 0 ? 'session-1' : 'session-2';
122
+ return createTestMessage(`msg-${i}`, client, i, 1000 + i);
123
123
  });
124
124
 
125
125
  const encoded = encodeJournalRLE(messages);
@@ -136,18 +136,18 @@ describe('JournalRLE - Run-Length Encoding', () => {
136
136
  const messages = [
137
137
  {
138
138
  id: 'msg-1',
139
- sessionId: 'session-1',
139
+ client: 'session-1',
140
140
  clock: { 'session-1': 5, 'session-2': 3 },
141
- lamportTime: 8,
142
- timestamp: 1000,
141
+ lt: 8,
142
+ ts: 1000,
143
143
  ops: [],
144
144
  },
145
145
  {
146
146
  id: 'msg-2',
147
- sessionId: 'session-1',
147
+ client: 'session-1',
148
148
  clock: { 'session-1': 6, 'session-2': 3 },
149
- lamportTime: 9,
150
- timestamp: 1001,
149
+ lt: 9,
150
+ ts: 1001,
151
151
  ops: [],
152
152
  },
153
153
  ] as CRDTMessage[];
@@ -169,15 +169,15 @@ describe('JournalRLE - Run-Length Encoding', () => {
169
169
  const encoded = encodeJournalRLE(messages);
170
170
  const decoded = decodeJournalRLE(encoded);
171
171
 
172
- expect(decoded[0].lamportTime).toBe(1);
173
- expect(decoded[1].lamportTime).toBe(2);
174
- expect(decoded[2].lamportTime).toBe(3);
172
+ expect(decoded[0].lt).toBe(1);
173
+ expect(decoded[1].lt).toBe(2);
174
+ expect(decoded[2].lt).toBe(3);
175
175
  });
176
176
 
177
177
  it('should preserve operation semantics', () => {
178
178
  const ops = [
179
- { key: 'cube-1', otype: 'vector3.set', path: 'position', value: [1, 2, 3] },
180
- { key: 'cube-2', otype: 'vector3.add', path: 'position', value: [0.5, 0.5, 0.5] },
179
+ { key: 'cube-1', ot: 'vector3.set', path: 'position', value: [1, 2, 3] },
180
+ { key: 'cube-2', ot: 'vector3.add', path: 'position', value: [0.5, 0.5, 0.5] },
181
181
  ];
182
182
 
183
183
  const messages = [
@@ -245,8 +245,8 @@ describe('JournalRLE - Run-Length Encoding', () => {
245
245
  it('should have low compression for heterogeneous journal', () => {
246
246
  // 100 messages alternating between 10 agents
247
247
  const messages = Array.from({ length: 100 }, (_, i) => {
248
- const sessionId = `session-${i % 10}`;
249
- return createTestMessage(`msg-${i}`, sessionId, i, 1000 + i);
248
+ const client = `session-${i % 10}`;
249
+ return createTestMessage(`msg-${i}`, client, i, 1000 + i);
250
250
  });
251
251
 
252
252
  const encoded = encodeJournalRLE(messages);
@@ -286,7 +286,7 @@ describe('JournalRLE - Run-Length Encoding', () => {
286
286
  agentId: 'session-1',
287
287
  count: 5,
288
288
  messages: [
289
- createTestMessage('msg-1', 'session-2', 1, 1000), // Wrong sessionId!
289
+ createTestMessage('msg-1', 'session-2', 1, 1000), // Wrong client!
290
290
  ] as CRDTMessage[],
291
291
  },
292
292
  ],
@@ -294,7 +294,7 @@ describe('JournalRLE - Run-Length Encoding', () => {
294
294
  };
295
295
 
296
296
  expect(() => decodeJournalRLE(corrupted)).toThrow(
297
- /sessionId mismatch/
297
+ /client mismatch/
298
298
  );
299
299
  });
300
300
 
@@ -354,7 +354,7 @@ describe('JournalRLE - Run-Length Encoding', () => {
354
354
  const complexOps = [
355
355
  {
356
356
  key: 'node-1',
357
- otype: 'node.insert',
357
+ ot: 'node.insert',
358
358
  path: 'children',
359
359
  value: {
360
360
  key: 'child-1',
@@ -365,7 +365,7 @@ describe('JournalRLE - Run-Length Encoding', () => {
365
365
  },
366
366
  {
367
367
  key: '_meta',
368
- otype: 'meta.undo',
368
+ ot: 'meta.undo',
369
369
  path: '_meta',
370
370
  targetMsgId: 'msg-1',
371
371
  },
@@ -387,14 +387,14 @@ describe('JournalRLE - Run-Length Encoding', () => {
387
387
  it('should handle multiple operations per message', () => {
388
388
  const multiOpMsg: CRDTMessage = {
389
389
  id: 'msg-1',
390
- sessionId: 'session-1',
390
+ client: 'session-1',
391
391
  clock: { 'session-1': 1 },
392
- lamportTime: 1,
393
- timestamp: 1000,
392
+ lt: 1,
393
+ ts: 1000,
394
394
  ops: [
395
- { key: 'node-1', otype: 'vector3.set', path: 'position', value: [1, 2, 3] },
396
- { key: 'node-2', otype: 'vector3.set', path: 'position', value: [4, 5, 6] },
397
- { key: 'node-3', otype: 'vector3.set', path: 'position', value: [7, 8, 9] },
395
+ { key: 'node-1', ot: 'vector3.set', path: 'position', value: [1, 2, 3] },
396
+ { key: 'node-2', ot: 'vector3.set', path: 'position', value: [4, 5, 6] },
397
+ { key: 'node-3', ot: 'vector3.set', path: 'position', value: [7, 8, 9] },
398
398
  ],
399
399
  };
400
400
 
@@ -416,7 +416,7 @@ describe('JournalRLE - Run-Length Encoding', () => {
416
416
  const encoded = encodeJournalRLE(messages);
417
417
  const decoded = decodeJournalRLE(encoded);
418
418
 
419
- expect(decoded[0].timestamp).toBe(largeTimestamp);
419
+ expect(decoded[0].ts).toBe(largeTimestamp);
420
420
  });
421
421
  });
422
422
 
@@ -433,7 +433,7 @@ describe('JournalRLE - Run-Length Encoding', () => {
433
433
  'session-a',
434
434
  i,
435
435
  1000 + i,
436
- [{ key: 'cube-1', otype: 'vector3.add', path: 'position', value: [0.1, 0, 0] }]
436
+ [{ key: 'cube-1', ot: 'vector3.add', path: 'position', value: [0.1, 0, 0] }]
437
437
  )
438
438
  );
439
439
  }
@@ -446,7 +446,7 @@ describe('JournalRLE - Run-Length Encoding', () => {
446
446
  'session-b',
447
447
  i,
448
448
  1010 + i,
449
- [{ key: 'cube-2', otype: 'vector3.add', path: 'position', value: [0, 0.1, 0] }]
449
+ [{ key: 'cube-2', ot: 'vector3.add', path: 'position', value: [0, 0.1, 0] }]
450
450
  )
451
451
  );
452
452
  }
@@ -459,7 +459,7 @@ describe('JournalRLE - Run-Length Encoding', () => {
459
459
  'session-a',
460
460
  i,
461
461
  1020 + i,
462
- [{ key: 'cube-1', otype: 'vector3.add', path: 'position', value: [0.1, 0, 0] }]
462
+ [{ key: 'cube-1', ot: 'vector3.add', path: 'position', value: [0.1, 0, 0] }]
463
463
  )
464
464
  );
465
465
  }
@@ -47,10 +47,10 @@ describe('JournalService', () => {
47
47
  for (const fixture of fixtures) {
48
48
  const msg = fixture.msg;
49
49
  expect(msg.id).toBeDefined();
50
- expect(msg.sessionId).toBeDefined();
50
+ expect(msg.client).toBeDefined();
51
51
  expect(msg.clock).toBeDefined();
52
- expect(msg.lamportTime).toBeGreaterThanOrEqual(0);
53
- expect(msg.timestamp).toBeGreaterThan(0);
52
+ expect(msg.lt).toBeGreaterThanOrEqual(0);
53
+ expect(msg.ts).toBeGreaterThan(0);
54
54
  expect(Array.isArray(msg.ops)).toBe(true);
55
55
  }
56
56
  });
@@ -60,7 +60,7 @@ describe('JournalService', () => {
60
60
  expect(undoFixture).toBeDefined();
61
61
 
62
62
  const undoOp = undoFixture!.msg.ops[0];
63
- expect(undoOp.otype).toBe('meta.undo');
63
+ expect(undoOp.ot).toBe('meta.undo');
64
64
  expect(undoOp.key).toBe('_meta');
65
65
  expect((undoOp as any).targetMsgId).toBe('msg-1');
66
66
  });
@@ -70,7 +70,7 @@ describe('JournalService', () => {
70
70
  expect(redoFixture).toBeDefined();
71
71
 
72
72
  const redoOp = redoFixture!.msg.ops[0];
73
- expect(redoOp.otype).toBe('meta.redo');
73
+ expect(redoOp.ot).toBe('meta.redo');
74
74
  expect(redoOp.key).toBe('_meta');
75
75
  expect((redoOp as any).targetMsgId).toBe('msg-1');
76
76
  });
@@ -115,14 +115,14 @@ describe('JournalService', () => {
115
115
  // First create a node (key=parent, path='children', value.key=new node)
116
116
  const nodeMsg: CRDTMessage = {
117
117
  id: 'node-msg',
118
- sessionId: 'session-1',
118
+ client: 'session-1',
119
119
  clock: { 'session-1': 0 },
120
- lamportTime: 0,
121
- timestamp: Date.now() / 1000,
120
+ lt: 0,
121
+ ts: Date.now() / 1000,
122
122
  ops: [
123
123
  {
124
124
  key: '', // No parent for root node
125
- otype: 'node.insert',
125
+ ot: 'node.insert',
126
126
  path: 'children',
127
127
  value: {
128
128
  key: 'cube-1',
@@ -153,14 +153,14 @@ describe('JournalService', () => {
153
153
  // Create node (key=parent, path='children', value.key=new node)
154
154
  const nodeMsg: CRDTMessage = {
155
155
  id: 'node-msg',
156
- sessionId: 'session-1',
156
+ client: 'session-1',
157
157
  clock: { 'session-1': 0 },
158
- lamportTime: 0,
159
- timestamp: Date.now() / 1000,
158
+ lt: 0,
159
+ ts: Date.now() / 1000,
160
160
  ops: [
161
161
  {
162
162
  key: '', // No parent for root node
163
- otype: 'node.insert',
163
+ ot: 'node.insert',
164
164
  path: 'children',
165
165
  value: {
166
166
  key: 'cube-1',
@@ -19,14 +19,14 @@ function coalesceBuggy(messages: CRDTMessage[]): CRDTMessage[] {
19
19
  // Current implementation: Group by session
20
20
  const sessionGroups = new Map<string, CRDTMessage[]>();
21
21
  for (const msg of messages) {
22
- if (!sessionGroups.has(msg.sessionId)) {
23
- sessionGroups.set(msg.sessionId, []);
22
+ if (!sessionGroups.has(msg.client)) {
23
+ sessionGroups.set(msg.client, []);
24
24
  }
25
- sessionGroups.get(msg.sessionId)!.push(msg);
25
+ sessionGroups.get(msg.client)!.push(msg);
26
26
  }
27
27
 
28
28
  const coalesced: CRDTMessage[] = [];
29
- for (const [sessionId, sessionMessages] of sessionGroups) {
29
+ for (const [client, sessionMessages] of sessionGroups) {
30
30
  // Flatten all ops from this session
31
31
  const allOps: Operation[] = [];
32
32
  for (const msg of sessionMessages) {
@@ -56,12 +56,12 @@ function coalesceCorrect(messages: CRDTMessage[]): CRDTMessage[] {
56
56
  let lastSessionId: string | null = null;
57
57
 
58
58
  for (const msg of messages) {
59
- if (msg.sessionId !== lastSessionId && currentRun.length > 0) {
59
+ if (msg.client !== lastSessionId && currentRun.length > 0) {
60
60
  runs.push(currentRun);
61
61
  currentRun = [];
62
62
  }
63
63
  currentRun.push(msg);
64
- lastSessionId = msg.sessionId;
64
+ lastSessionId = msg.client;
65
65
  }
66
66
  if (currentRun.length > 0) runs.push(currentRun);
67
67
 
@@ -97,24 +97,24 @@ describe('LWW Ordering Bug', () => {
97
97
  let graph = createEmptyGraph();
98
98
  graph = applyMessage(graph, {
99
99
  id: 'init',
100
- sessionId: 'init',
100
+ client: 'init',
101
101
  clock: { init: 1 },
102
- lamportTime: 1,
103
- timestamp: 0,
104
- ops: [{ otype: 'node.insert', key: '__root', path: 'children', value: { key: 'cube', tag: 'Mesh', name: 'Cube' } }],
102
+ lt:1,
103
+ ts: 0,
104
+ ops: [{ ot: 'node.insert', key: '__root', path: 'children', value: { key: 'cube', tag: 'Mesh', name: 'Cube' } }],
105
105
  });
106
106
 
107
107
  // Original messages in interleaved order
108
108
  const messages: CRDTMessage[] = [
109
109
  {
110
110
  id: 'msg1',
111
- sessionId: 'alice',
111
+ client: 'alice',
112
112
  clock: { alice: 10 },
113
- lamportTime: 10,
114
- timestamp: 1000,
113
+ lt:10,
114
+ ts: 1000,
115
115
  ops: [
116
116
  {
117
- otype: 'string.set',
117
+ ot: 'string.set',
118
118
  key: 'cube',
119
119
  path: 'color',
120
120
  value: 'red',
@@ -123,13 +123,13 @@ describe('LWW Ordering Bug', () => {
123
123
  },
124
124
  {
125
125
  id: 'msg2',
126
- sessionId: 'bob',
126
+ client: 'bob',
127
127
  clock: { alice: 10, bob: 15 },
128
- lamportTime: 15,
129
- timestamp: 1500,
128
+ lt:15,
129
+ ts: 1500,
130
130
  ops: [
131
131
  {
132
- otype: 'string.set',
132
+ ot: 'string.set',
133
133
  key: 'cube',
134
134
  path: 'color',
135
135
  value: 'blue',
@@ -138,13 +138,13 @@ describe('LWW Ordering Bug', () => {
138
138
  },
139
139
  {
140
140
  id: 'msg3',
141
- sessionId: 'alice',
141
+ client: 'alice',
142
142
  clock: { alice: 20, bob: 15 },
143
- lamportTime: 20,
144
- timestamp: 2000,
143
+ lt:20,
144
+ ts: 2000,
145
145
  ops: [
146
146
  {
147
- otype: 'string.set',
147
+ ot: 'string.set',
148
148
  key: 'cube',
149
149
  path: 'color',
150
150
  value: 'green',
@@ -164,7 +164,7 @@ describe('LWW Ordering Bug', () => {
164
164
  const coalescedBuggy = coalesceBuggy(messages);
165
165
  console.log('Buggy coalescing:');
166
166
  coalescedBuggy.forEach(msg => {
167
- console.log(` session=${msg.sessionId}, lamport=${msg.lamportTime}, ops=${msg.ops.length}`);
167
+ console.log(` session=${msg.client}, lamport=${msg.lt}, ops=${msg.ops.length}`);
168
168
  msg.ops.forEach(op => console.log(` ${JSON.stringify(op)}`));
169
169
  });
170
170
 
@@ -180,7 +180,7 @@ describe('LWW Ordering Bug', () => {
180
180
  const coalescedCorrect = coalesceCorrect(messages);
181
181
  console.log('\nCorrect coalescing:');
182
182
  coalescedCorrect.forEach(msg => {
183
- console.log(` session=${msg.sessionId}, lamport=${msg.lamportTime}, ops=${msg.ops.length}`);
183
+ console.log(` session=${msg.client}, lamport=${msg.lt}, ops=${msg.ops.length}`);
184
184
  msg.ops.forEach(op => console.log(` ${JSON.stringify(op)}`));
185
185
  });
186
186
 
@@ -201,37 +201,37 @@ describe('LWW Ordering Bug', () => {
201
201
  const messages: CRDTMessage[] = [
202
202
  {
203
203
  id: 'msg1',
204
- sessionId: 'alice',
204
+ client: 'alice',
205
205
  clock: { alice: 10 },
206
- lamportTime: 10,
207
- timestamp: 1000,
208
- ops: [{ otype: 'number.set', key: 'x', path: 'value', value: 1 }],
206
+ lt:10,
207
+ ts: 1000,
208
+ ops: [{ ot: 'number.set', key: 'x', path: 'value', value: 1 }],
209
209
  },
210
210
  {
211
211
  id: 'msg2',
212
- sessionId: 'bob',
212
+ client: 'bob',
213
213
  clock: { alice: 10, bob: 15 },
214
- lamportTime: 15,
215
- timestamp: 1500,
216
- ops: [{ otype: 'number.set', key: 'x', path: 'value', value: 2 }],
214
+ lt:15,
215
+ ts: 1500,
216
+ ops: [{ ot: 'number.set', key: 'x', path: 'value', value: 2 }],
217
217
  },
218
218
  {
219
219
  id: 'msg3',
220
- sessionId: 'alice',
220
+ client: 'alice',
221
221
  clock: { alice: 20, bob: 15 },
222
- lamportTime: 20,
223
- timestamp: 2000,
224
- ops: [{ otype: 'number.set', key: 'x', path: 'value', value: 3 }],
222
+ lt:20,
223
+ ts: 2000,
224
+ ops: [{ ot: 'number.set', key: 'x', path: 'value', value: 3 }],
225
225
  },
226
226
  ];
227
227
 
228
228
  const coalesced = coalesceBuggy(messages);
229
229
 
230
230
  // Find alice's coalesced message
231
- const aliceMsg = coalesced.find(m => m.sessionId === 'alice')!;
231
+ const aliceMsg = coalesced.find(m => m.client === 'alice')!;
232
232
 
233
233
  // Bug: ALL of Alice's ops inherit lamportTime=20 from msg3
234
- expect(aliceMsg.lamportTime).toBe(20);
234
+ expect(aliceMsg.lt).toBe(20);
235
235
  expect(aliceMsg.ops).toHaveLength(2); // Both set operations
236
236
 
237
237
  // When these ops are applied, they BOTH get lamportTime=20
@@ -239,7 +239,7 @@ describe('LWW Ordering Bug', () => {
239
239
  // instead of its original lamport=10
240
240
 
241
241
  console.log('Alice coalesced message:', {
242
- lamportTime: aliceMsg.lamportTime,
242
+ lt:aliceMsg.lt,
243
243
  ops: aliceMsg.ops,
244
244
  });
245
245