@vuer-ai/vuer-rtc-server 0.1.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 (114) hide show
  1. package/.env +1 -0
  2. package/PHASE1_SUMMARY.md +94 -0
  3. package/README.md +423 -0
  4. package/dist/broker/InMemoryBroker.d.ts +24 -0
  5. package/dist/broker/InMemoryBroker.d.ts.map +1 -0
  6. package/dist/broker/InMemoryBroker.js +65 -0
  7. package/dist/broker/InMemoryBroker.js.map +1 -0
  8. package/dist/broker/index.d.ts +3 -0
  9. package/dist/broker/index.d.ts.map +1 -0
  10. package/dist/broker/index.js +2 -0
  11. package/dist/broker/index.js.map +1 -0
  12. package/dist/broker/types.d.ts +47 -0
  13. package/dist/broker/types.d.ts.map +1 -0
  14. package/dist/broker/types.js +9 -0
  15. package/dist/broker/types.js.map +1 -0
  16. package/dist/index.d.ts +13 -0
  17. package/dist/index.d.ts.map +1 -0
  18. package/dist/index.js +18 -0
  19. package/dist/index.js.map +1 -0
  20. package/dist/journal/JournalRepository.d.ts +39 -0
  21. package/dist/journal/JournalRepository.d.ts.map +1 -0
  22. package/dist/journal/JournalRepository.js +102 -0
  23. package/dist/journal/JournalRepository.js.map +1 -0
  24. package/dist/journal/JournalService.d.ts +69 -0
  25. package/dist/journal/JournalService.d.ts.map +1 -0
  26. package/dist/journal/JournalService.js +224 -0
  27. package/dist/journal/JournalService.js.map +1 -0
  28. package/dist/journal/index.d.ts +6 -0
  29. package/dist/journal/index.d.ts.map +1 -0
  30. package/dist/journal/index.js +6 -0
  31. package/dist/journal/index.js.map +1 -0
  32. package/dist/persistence/DocumentRepository.d.ts +22 -0
  33. package/dist/persistence/DocumentRepository.d.ts.map +1 -0
  34. package/dist/persistence/DocumentRepository.js +66 -0
  35. package/dist/persistence/DocumentRepository.js.map +1 -0
  36. package/dist/persistence/PrismaClient.d.ts +8 -0
  37. package/dist/persistence/PrismaClient.d.ts.map +1 -0
  38. package/dist/persistence/PrismaClient.js +21 -0
  39. package/dist/persistence/PrismaClient.js.map +1 -0
  40. package/dist/persistence/SessionRepository.d.ts +22 -0
  41. package/dist/persistence/SessionRepository.d.ts.map +1 -0
  42. package/dist/persistence/SessionRepository.js +103 -0
  43. package/dist/persistence/SessionRepository.js.map +1 -0
  44. package/dist/persistence/index.d.ts +7 -0
  45. package/dist/persistence/index.d.ts.map +1 -0
  46. package/dist/persistence/index.js +7 -0
  47. package/dist/persistence/index.js.map +1 -0
  48. package/dist/serve.d.ts +18 -0
  49. package/dist/serve.d.ts.map +1 -0
  50. package/dist/serve.js +211 -0
  51. package/dist/serve.js.map +1 -0
  52. package/dist/transport/RTCServer.d.ts +92 -0
  53. package/dist/transport/RTCServer.d.ts.map +1 -0
  54. package/dist/transport/RTCServer.js +273 -0
  55. package/dist/transport/RTCServer.js.map +1 -0
  56. package/dist/transport/index.d.ts +2 -0
  57. package/dist/transport/index.d.ts.map +1 -0
  58. package/dist/transport/index.js +2 -0
  59. package/dist/transport/index.js.map +1 -0
  60. package/dist/version.d.ts +2 -0
  61. package/dist/version.d.ts.map +1 -0
  62. package/dist/version.js +2 -0
  63. package/dist/version.js.map +1 -0
  64. package/jest.config.js +36 -0
  65. package/package.json +56 -0
  66. package/prisma/schema.prisma +121 -0
  67. package/src/broker/InMemoryBroker.ts +81 -0
  68. package/src/broker/index.ts +2 -0
  69. package/src/broker/types.ts +60 -0
  70. package/src/index.ts +23 -0
  71. package/src/journal/JournalRepository.ts +119 -0
  72. package/src/journal/JournalService.ts +291 -0
  73. package/src/journal/index.ts +10 -0
  74. package/src/persistence/DocumentRepository.ts +76 -0
  75. package/src/persistence/PrismaClient.ts +24 -0
  76. package/src/persistence/SessionRepository.ts +114 -0
  77. package/src/persistence/index.ts +7 -0
  78. package/src/serve.ts +240 -0
  79. package/src/transport/RTCServer.ts +327 -0
  80. package/src/transport/index.ts +1 -0
  81. package/src/version.ts +1 -0
  82. package/tests/README.md +112 -0
  83. package/tests/demo.ts +555 -0
  84. package/tests/e2e/convergence.test.ts +221 -0
  85. package/tests/e2e/helpers/assertions.ts +158 -0
  86. package/tests/e2e/helpers/createTestServer.ts +220 -0
  87. package/tests/e2e/latency.test.ts +512 -0
  88. package/tests/e2e/packet-loss.test.ts +229 -0
  89. package/tests/e2e/relay.test.ts +255 -0
  90. package/tests/e2e/sync-perf.test.ts +365 -0
  91. package/tests/e2e/sync-reconciliation.test.ts +237 -0
  92. package/tests/e2e/text-sync.test.ts +199 -0
  93. package/tests/e2e/tombstone-convergence.test.ts +356 -0
  94. package/tests/fixtures/array-ops.jsonl +6 -0
  95. package/tests/fixtures/boolean-ops.jsonl +6 -0
  96. package/tests/fixtures/color-ops.jsonl +4 -0
  97. package/tests/fixtures/edit-buffer.jsonl +3 -0
  98. package/tests/fixtures/messages.jsonl +4 -0
  99. package/tests/fixtures/node-ops.jsonl +6 -0
  100. package/tests/fixtures/number-ops.jsonl +7 -0
  101. package/tests/fixtures/object-ops.jsonl +4 -0
  102. package/tests/fixtures/operations.jsonl +7 -0
  103. package/tests/fixtures/string-ops.jsonl +4 -0
  104. package/tests/fixtures/undo-redo.jsonl +3 -0
  105. package/tests/fixtures/vector-ops.jsonl +9 -0
  106. package/tests/integration/repositories.test.ts +320 -0
  107. package/tests/journal/journal-service.test.ts +185 -0
  108. package/tests/test-data/datatypes.ts +677 -0
  109. package/tests/test-data/operations-example.ts +306 -0
  110. package/tests/test-data/scene-example.ts +247 -0
  111. package/tests/unit/operations.test.ts +310 -0
  112. package/tests/unit/vectorClock.test.ts +281 -0
  113. package/tsconfig.json +19 -0
  114. package/tsconfig.test.json +8 -0
@@ -0,0 +1,677 @@
1
+ /**
2
+ * Data Type Definitions - Complete operation sets for all data types
3
+ *
4
+ * This file defines:
5
+ * 1. What operations each data type supports
6
+ * 2. How those operations work
7
+ * 3. Conflict resolution semantics
8
+ *
9
+ * See docs/TYPE_BEHAVIORS.md for detailed behavior descriptions.
10
+ */
11
+
12
+ // ============================================
13
+ // PRIMITIVE DATA TYPES
14
+ // ============================================
15
+
16
+ /**
17
+ * Number Data Type
18
+ *
19
+ * dtype: 'number'
20
+ * Supported operations: set, add, multiply, min, max
21
+ */
22
+ export const NumberType = {
23
+ dtype: 'number',
24
+ operations: {
25
+ /**
26
+ * SET - Replace value (LWW)
27
+ * Usage: Absolute values, IDs, measurements
28
+ */
29
+ set: {
30
+ behavior: 'lww',
31
+ merge: (ops: Array<{ value: number; lamportTime: number }>) => {
32
+ // Last-Write-Wins by lamport time
33
+ const sorted = ops.sort((a, b) => b.lamportTime - a.lamportTime);
34
+ return sorted[0].value;
35
+ },
36
+ example: {
37
+ ops: [
38
+ { value: 42, lamportTime: 100 },
39
+ { value: 99, lamportTime: 101 },
40
+ ],
41
+ result: 99,
42
+ },
43
+ },
44
+
45
+ /**
46
+ * ADD - Accumulate values
47
+ * Usage: Counters, scores, deltas
48
+ */
49
+ add: {
50
+ behavior: 'additive',
51
+ merge: (ops: Array<{ value: number; lamportTime: number }>) => {
52
+ // Sum all concurrent operations
53
+ return ops.reduce((sum, op) => sum + op.value, 0);
54
+ },
55
+ example: {
56
+ ops: [
57
+ { value: 10, lamportTime: 100 },
58
+ { value: 5, lamportTime: 101 },
59
+ ],
60
+ result: 15,
61
+ },
62
+ },
63
+
64
+ /**
65
+ * MULTIPLY - Multiply values
66
+ * Usage: Scale factors, percentages
67
+ */
68
+ multiply: {
69
+ behavior: 'multiplicative',
70
+ merge: (ops: Array<{ value: number; lamportTime: number }>) => {
71
+ return ops.reduce((product, op) => product * op.value, 1);
72
+ },
73
+ example: {
74
+ ops: [
75
+ { value: 2, lamportTime: 100 },
76
+ { value: 3, lamportTime: 101 },
77
+ ],
78
+ result: 6,
79
+ },
80
+ },
81
+
82
+ /**
83
+ * MIN - Take minimum value
84
+ * Usage: Lower bounds, quality settings
85
+ */
86
+ min: {
87
+ behavior: 'min',
88
+ merge: (ops: Array<{ value: number; lamportTime: number }>) => {
89
+ return Math.min(...ops.map((op) => op.value));
90
+ },
91
+ example: {
92
+ ops: [
93
+ { value: 720, lamportTime: 100 },
94
+ { value: 1080, lamportTime: 101 },
95
+ ],
96
+ result: 720,
97
+ },
98
+ },
99
+
100
+ /**
101
+ * MAX - Take maximum value
102
+ * Usage: Upper bounds, quality settings
103
+ */
104
+ max: {
105
+ behavior: 'max',
106
+ merge: (ops: Array<{ value: number; lamportTime: number }>) => {
107
+ return Math.max(...ops.map((op) => op.value));
108
+ },
109
+ example: {
110
+ ops: [
111
+ { value: 720, lamportTime: 100 },
112
+ { value: 1080, lamportTime: 101 },
113
+ ],
114
+ result: 1080,
115
+ },
116
+ },
117
+ },
118
+ };
119
+
120
+ /**
121
+ * String Data Type
122
+ *
123
+ * dtype: 'string'
124
+ * Supported operations: set, concat
125
+ */
126
+ export const StringType = {
127
+ dtype: 'string',
128
+ operations: {
129
+ /**
130
+ * SET - Replace value (LWW)
131
+ * Usage: Names, labels, single values
132
+ */
133
+ set: {
134
+ behavior: 'lww',
135
+ merge: (ops: Array<{ value: string; lamportTime: number }>) => {
136
+ const sorted = ops.sort((a, b) => b.lamportTime - a.lamportTime);
137
+ return sorted[0].value;
138
+ },
139
+ example: {
140
+ ops: [
141
+ { value: 'Cube', lamportTime: 100 },
142
+ { value: 'Sphere', lamportTime: 101 },
143
+ ],
144
+ result: 'Sphere',
145
+ },
146
+ },
147
+
148
+ /**
149
+ * CONCAT - Concatenate values
150
+ * Usage: Logs, append-only strings
151
+ */
152
+ concat: {
153
+ behavior: 'append',
154
+ merge: (ops: Array<{ value: string; lamportTime: number }>, separator = '\n') => {
155
+ const sorted = ops.sort((a, b) => a.lamportTime - b.lamportTime);
156
+ return sorted.map((op) => op.value).join(separator);
157
+ },
158
+ example: {
159
+ ops: [
160
+ { value: 'User A edited', lamportTime: 100 },
161
+ { value: 'User B edited', lamportTime: 101 },
162
+ ],
163
+ result: 'User A edited\nUser B edited',
164
+ },
165
+ },
166
+ },
167
+ };
168
+
169
+ /**
170
+ * Boolean Data Type
171
+ *
172
+ * dtype: 'boolean'
173
+ * Supported operations: set, or, and
174
+ */
175
+ export const BooleanType = {
176
+ dtype: 'boolean',
177
+ operations: {
178
+ /**
179
+ * SET - Replace value (LWW)
180
+ * Usage: Flags, toggles
181
+ */
182
+ set: {
183
+ behavior: 'lww',
184
+ merge: (ops: Array<{ value: boolean; lamportTime: number }>) => {
185
+ const sorted = ops.sort((a, b) => b.lamportTime - a.lamportTime);
186
+ return sorted[0].value;
187
+ },
188
+ example: {
189
+ ops: [
190
+ { value: true, lamportTime: 100 },
191
+ { value: false, lamportTime: 101 },
192
+ ],
193
+ result: false,
194
+ },
195
+ },
196
+
197
+ /**
198
+ * OR - Logical OR (enable wins)
199
+ * Usage: Feature flags where enabling takes precedence
200
+ */
201
+ or: {
202
+ behavior: 'or',
203
+ merge: (ops: Array<{ value: boolean; lamportTime: number }>) => {
204
+ return ops.some((op) => op.value === true);
205
+ },
206
+ example: {
207
+ ops: [
208
+ { value: true, lamportTime: 100 },
209
+ { value: false, lamportTime: 101 },
210
+ ],
211
+ result: true,
212
+ },
213
+ },
214
+
215
+ /**
216
+ * AND - Logical AND (disable wins)
217
+ * Usage: Permissions where disabling takes precedence
218
+ */
219
+ and: {
220
+ behavior: 'and',
221
+ merge: (ops: Array<{ value: boolean; lamportTime: number }>) => {
222
+ return ops.every((op) => op.value === true);
223
+ },
224
+ example: {
225
+ ops: [
226
+ { value: true, lamportTime: 100 },
227
+ { value: false, lamportTime: 101 },
228
+ ],
229
+ result: false,
230
+ },
231
+ },
232
+ },
233
+ };
234
+
235
+ // ============================================
236
+ // COMPOUND DATA TYPES (3D GRAPHICS)
237
+ // ============================================
238
+
239
+ /**
240
+ * Vector3 Data Type - [x, y, z]
241
+ *
242
+ * dtype: 'vector3'
243
+ * Supported operations: set, add, multiply, lww-per-component
244
+ * Used for: position, scale, velocity
245
+ */
246
+ export const Vector3Type = {
247
+ dtype: 'vector3',
248
+ operations: {
249
+ /**
250
+ * SET - Replace entire vector (LWW)
251
+ * Usage: Absolute position/scale
252
+ * Important: Preserves user intent by replacing whole vector
253
+ */
254
+ set: {
255
+ behavior: 'lww',
256
+ merge: (ops: Array<{ value: [number, number, number]; lamportTime: number }>) => {
257
+ const sorted = ops.sort((a, b) => b.lamportTime - a.lamportTime);
258
+ return sorted[0].value;
259
+ },
260
+ example: {
261
+ ops: [
262
+ { value: [5, 0, 0], lamportTime: 100 },
263
+ { value: [0, 5, 0], lamportTime: 101 },
264
+ ],
265
+ result: [0, 5, 0],
266
+ // NOT [5, 5, 0] - that would violate user intent!
267
+ },
268
+ },
269
+
270
+ /**
271
+ * ADD - Component-wise addition
272
+ * Usage: Velocity, forces, deltas
273
+ */
274
+ add: {
275
+ behavior: 'additive',
276
+ merge: (ops: Array<{ value: [number, number, number]; lamportTime: number }>) => {
277
+ const sum: [number, number, number] = [0, 0, 0];
278
+ for (const op of ops) {
279
+ sum[0] += op.value[0];
280
+ sum[1] += op.value[1];
281
+ sum[2] += op.value[2];
282
+ }
283
+ return sum;
284
+ },
285
+ example: {
286
+ ops: [
287
+ { value: [1, 0, 0], lamportTime: 100 },
288
+ { value: [0, 1, 0], lamportTime: 101 },
289
+ ],
290
+ result: [1, 1, 0],
291
+ },
292
+ },
293
+
294
+ /**
295
+ * MULTIPLY - Component-wise multiplication
296
+ * Usage: Scale composition
297
+ */
298
+ multiply: {
299
+ behavior: 'multiplicative',
300
+ merge: (ops: Array<{ value: [number, number, number]; lamportTime: number }>) => {
301
+ const product: [number, number, number] = [1, 1, 1];
302
+ for (const op of ops) {
303
+ product[0] *= op.value[0];
304
+ product[1] *= op.value[1];
305
+ product[2] *= op.value[2];
306
+ }
307
+ return product;
308
+ },
309
+ example: {
310
+ ops: [
311
+ { value: [2, 1, 1], lamportTime: 100 },
312
+ { value: [1, 3, 1], lamportTime: 101 },
313
+ ],
314
+ result: [2, 3, 1],
315
+ },
316
+ },
317
+
318
+ /**
319
+ * LWW_PER_COMPONENT - LWW per x/y/z
320
+ * Usage: Mixed semantics (rare)
321
+ */
322
+ lwwPerComponent: {
323
+ behavior: 'lww-per-component',
324
+ merge: (
325
+ ops: Array<{
326
+ value: Partial<{ x: number; y: number; z: number }>;
327
+ lamportTime: number;
328
+ }>
329
+ ) => {
330
+ const result = { x: 0, y: 0, z: 0 };
331
+ const latest = { x: -1, y: -1, z: -1 };
332
+
333
+ for (const op of ops) {
334
+ if (op.value.x !== undefined && op.lamportTime > latest.x) {
335
+ result.x = op.value.x;
336
+ latest.x = op.lamportTime;
337
+ }
338
+ if (op.value.y !== undefined && op.lamportTime > latest.y) {
339
+ result.y = op.value.y;
340
+ latest.y = op.lamportTime;
341
+ }
342
+ if (op.value.z !== undefined && op.lamportTime > latest.z) {
343
+ result.z = op.value.z;
344
+ latest.z = op.lamportTime;
345
+ }
346
+ }
347
+
348
+ return [result.x, result.y, result.z] as [number, number, number];
349
+ },
350
+ example: {
351
+ ops: [
352
+ { value: { x: 5 }, lamportTime: 100 },
353
+ { value: { y: 3 }, lamportTime: 101 },
354
+ ],
355
+ result: [5, 3, 0],
356
+ },
357
+ },
358
+ },
359
+ };
360
+
361
+ /**
362
+ * Quaternion Data Type - [x, y, z, w]
363
+ *
364
+ * dtype: 'quaternion'
365
+ * Supported operations: set, multiply
366
+ * Used for: rotation
367
+ */
368
+ export const QuaternionType = {
369
+ dtype: 'quaternion',
370
+ operations: {
371
+ /**
372
+ * SET - Replace entire quaternion (LWW)
373
+ * Usage: Absolute rotation (most common)
374
+ */
375
+ set: {
376
+ behavior: 'lww',
377
+ merge: (ops: Array<{ value: [number, number, number, number]; lamportTime: number }>) => {
378
+ const sorted = ops.sort((a, b) => b.lamportTime - a.lamportTime);
379
+ return sorted[0].value;
380
+ },
381
+ example: {
382
+ ops: [
383
+ { value: [0, 0, 0, 1], lamportTime: 100 }, // No rotation
384
+ { value: [0, 0.707, 0, 0.707], lamportTime: 101 }, // 90° around Y
385
+ ],
386
+ result: [0, 0.707, 0, 0.707],
387
+ },
388
+ },
389
+
390
+ /**
391
+ * MULTIPLY - Quaternion multiplication (composition)
392
+ * Usage: Rotation composition (advanced)
393
+ * Warning: Order matters! Result may be unpredictable for users
394
+ */
395
+ multiply: {
396
+ behavior: 'multiplicative',
397
+ merge: (ops: Array<{ value: [number, number, number, number]; lamportTime: number }>) => {
398
+ // Sort by lamport time to get deterministic order
399
+ const sorted = ops.sort((a, b) => a.lamportTime - b.lamportTime);
400
+
401
+ // Multiply quaternions in order
402
+ let result = sorted[0].value;
403
+ for (let i = 1; i < sorted.length; i++) {
404
+ result = multiplyQuaternions(result, sorted[i].value);
405
+ }
406
+ return result;
407
+ },
408
+ example: {
409
+ ops: [
410
+ { value: [0, 0.707, 0, 0.707], lamportTime: 100 }, // 90° Y
411
+ { value: [0.707, 0, 0, 0.707], lamportTime: 101 }, // 90° X
412
+ ],
413
+ result: [0.5, 0.5, 0.5, 0.5], // Combined rotation
414
+ },
415
+ },
416
+ },
417
+ };
418
+
419
+ /**
420
+ * Helper: Quaternion multiplication
421
+ */
422
+ function multiplyQuaternions(
423
+ a: [number, number, number, number],
424
+ b: [number, number, number, number]
425
+ ): [number, number, number, number] {
426
+ const [ax, ay, az, aw] = a;
427
+ const [bx, by, bz, bw] = b;
428
+
429
+ return [
430
+ aw * bx + ax * bw + ay * bz - az * by,
431
+ aw * by - ax * bz + ay * bw + az * bx,
432
+ aw * bz + ax * by - ay * bx + az * bw,
433
+ aw * bw - ax * bx - ay * by - az * bz,
434
+ ];
435
+ }
436
+
437
+ /**
438
+ * Color Data Type - RGB hex string or object
439
+ *
440
+ * dtype: 'color'
441
+ * Supported operations: set, blend
442
+ */
443
+ export const ColorType = {
444
+ dtype: 'color',
445
+ operations: {
446
+ /**
447
+ * SET - Replace color (LWW)
448
+ * Usage: Object colors (most common)
449
+ */
450
+ set: {
451
+ behavior: 'lww',
452
+ merge: (ops: Array<{ value: string; lamportTime: number }>) => {
453
+ const sorted = ops.sort((a, b) => b.lamportTime - a.lamportTime);
454
+ return sorted[0].value;
455
+ },
456
+ example: {
457
+ ops: [
458
+ { value: '#ff0000', lamportTime: 100 },
459
+ { value: '#0000ff', lamportTime: 101 },
460
+ ],
461
+ result: '#0000ff',
462
+ },
463
+ },
464
+
465
+ /**
466
+ * BLEND - Average colors
467
+ * Usage: Lighting (advanced)
468
+ */
469
+ blend: {
470
+ behavior: 'blend',
471
+ merge: (ops: Array<{ value: string; lamportTime: number }>) => {
472
+ // Convert hex to RGB, average, convert back
473
+ const rgbs = ops.map((op) => hexToRgb(op.value));
474
+ const avgR = Math.round(rgbs.reduce((sum, rgb) => sum + rgb.r, 0) / rgbs.length);
475
+ const avgG = Math.round(rgbs.reduce((sum, rgb) => sum + rgb.g, 0) / rgbs.length);
476
+ const avgB = Math.round(rgbs.reduce((sum, rgb) => sum + rgb.b, 0) / rgbs.length);
477
+ return rgbToHex(avgR, avgG, avgB);
478
+ },
479
+ example: {
480
+ ops: [
481
+ { value: '#ff0000', lamportTime: 100 }, // Red
482
+ { value: '#0000ff', lamportTime: 101 }, // Blue
483
+ ],
484
+ result: '#7f007f', // Purple
485
+ },
486
+ },
487
+ },
488
+ };
489
+
490
+ /**
491
+ * Helper: Hex to RGB
492
+ */
493
+ function hexToRgb(hex: string): { r: number; g: number; b: number } {
494
+ const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
495
+ return result
496
+ ? {
497
+ r: parseInt(result[1], 16),
498
+ g: parseInt(result[2], 16),
499
+ b: parseInt(result[3], 16),
500
+ }
501
+ : { r: 0, g: 0, b: 0 };
502
+ }
503
+
504
+ /**
505
+ * Helper: RGB to Hex
506
+ */
507
+ function rgbToHex(r: number, g: number, b: number): string {
508
+ return '#' + [r, g, b].map((x) => x.toString(16).padStart(2, '0')).join('');
509
+ }
510
+
511
+ // ============================================
512
+ // COLLECTION DATA TYPES
513
+ // ============================================
514
+
515
+ /**
516
+ * Array Data Type
517
+ *
518
+ * dtype: 'array'
519
+ * Supported operations: set, union, append, rga
520
+ */
521
+ export const ArrayType = {
522
+ dtype: 'array',
523
+ operations: {
524
+ /**
525
+ * SET - Replace entire array (LWW)
526
+ * Usage: Replace whole list
527
+ */
528
+ set: {
529
+ behavior: 'lww',
530
+ merge: (ops: Array<{ value: any[]; lamportTime: number }>) => {
531
+ const sorted = ops.sort((a, b) => b.lamportTime - a.lamportTime);
532
+ return sorted[0].value;
533
+ },
534
+ example: {
535
+ ops: [
536
+ { value: ['a', 'b'], lamportTime: 100 },
537
+ { value: ['c', 'd'], lamportTime: 101 },
538
+ ],
539
+ result: ['c', 'd'],
540
+ },
541
+ },
542
+
543
+ /**
544
+ * UNION - Merge all unique elements
545
+ * Usage: Tags, sets
546
+ */
547
+ union: {
548
+ behavior: 'union',
549
+ merge: (ops: Array<{ value: any[]; lamportTime: number }>) => {
550
+ const allElements = ops.flatMap((op) => op.value);
551
+ return [...new Set(allElements)];
552
+ },
553
+ example: {
554
+ ops: [
555
+ { value: ['a', 'b'], lamportTime: 100 },
556
+ { value: ['b', 'c'], lamportTime: 101 },
557
+ ],
558
+ result: ['a', 'b', 'c'],
559
+ },
560
+ },
561
+
562
+ /**
563
+ * APPEND - Append all (ordered by lamport)
564
+ * Usage: Logs, history
565
+ */
566
+ append: {
567
+ behavior: 'append',
568
+ merge: (ops: Array<{ value: any[]; lamportTime: number }>) => {
569
+ const sorted = ops.sort((a, b) => a.lamportTime - b.lamportTime);
570
+ return sorted.flatMap((op) => op.value);
571
+ },
572
+ example: {
573
+ ops: [
574
+ { value: ['edit1'], lamportTime: 100 },
575
+ { value: ['edit2'], lamportTime: 101 },
576
+ ],
577
+ result: ['edit1', 'edit2'],
578
+ },
579
+ },
580
+
581
+ /**
582
+ * RGA - Replicated Growable Array (CRDT)
583
+ * Usage: Text editing, ordered lists (Phase 3)
584
+ */
585
+ rga: {
586
+ behavior: 'rga',
587
+ merge: (
588
+ ops: Array<{ action: 'insert' | 'delete'; index: number; value?: any; lamportTime: number }>
589
+ ) => {
590
+ // Simplified RGA - real implementation needs tombstones and references
591
+ const sorted = ops.sort((a, b) => a.lamportTime - b.lamportTime);
592
+ const result: any[] = [];
593
+
594
+ for (const op of sorted) {
595
+ if (op.action === 'insert') {
596
+ result.splice(op.index, 0, op.value);
597
+ } else if (op.action === 'delete') {
598
+ result.splice(op.index, 1);
599
+ }
600
+ }
601
+
602
+ return result;
603
+ },
604
+ example: {
605
+ ops: [
606
+ { action: 'insert', index: 0, value: 'a', lamportTime: 100 },
607
+ { action: 'insert', index: 1, value: 'b', lamportTime: 101 },
608
+ { action: 'insert', index: 1, value: 'X', lamportTime: 102 },
609
+ ],
610
+ result: ['a', 'X', 'b'],
611
+ },
612
+ },
613
+ },
614
+ };
615
+
616
+ /**
617
+ * Object/Map Data Type
618
+ *
619
+ * dtype: 'object'
620
+ * Supported operations: set, lww-per-key
621
+ */
622
+ export const ObjectType = {
623
+ dtype: 'object',
624
+ operations: {
625
+ /**
626
+ * SET - Replace entire object (LWW)
627
+ * Usage: Replace whole map
628
+ */
629
+ set: {
630
+ behavior: 'lww',
631
+ merge: (ops: Array<{ value: Record<string, any>; lamportTime: number }>) => {
632
+ const sorted = ops.sort((a, b) => b.lamportTime - a.lamportTime);
633
+ return sorted[0].value;
634
+ },
635
+ example: {
636
+ ops: [
637
+ { value: { a: 1 }, lamportTime: 100 },
638
+ { value: { b: 2 }, lamportTime: 101 },
639
+ ],
640
+ result: { b: 2 },
641
+ },
642
+ },
643
+
644
+ /**
645
+ * LWW_PER_KEY - LWW per object key
646
+ * Usage: Properties (most common)
647
+ */
648
+ lwwPerKey: {
649
+ behavior: 'lww-per-key',
650
+ merge: (
651
+ ops: Array<{ value: Record<string, any>; lamportTime: number; perKeyLamport?: Record<string, number> }>
652
+ ) => {
653
+ const result: Record<string, any> = {};
654
+ const latestTimes: Record<string, number> = {};
655
+
656
+ for (const op of ops) {
657
+ for (const [key, value] of Object.entries(op.value)) {
658
+ const keyLamport = op.perKeyLamport?.[key] ?? op.lamportTime;
659
+ if (!latestTimes[key] || keyLamport > latestTimes[key]) {
660
+ result[key] = value;
661
+ latestTimes[key] = keyLamport;
662
+ }
663
+ }
664
+ }
665
+
666
+ return result;
667
+ },
668
+ example: {
669
+ ops: [
670
+ { value: { color: 'red', size: 10 }, lamportTime: 100 },
671
+ { value: { color: 'blue', opacity: 0.5 }, lamportTime: 101 },
672
+ ],
673
+ result: { color: 'blue', size: 10, opacity: 0.5 },
674
+ },
675
+ },
676
+ },
677
+ };