@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.
- package/.env +1 -1
- package/README.md +56 -0
- package/dist/archive/ArchivalService.js +1 -1
- package/dist/archive/ArchivalService.js.map +1 -1
- package/dist/broker/InMemoryBroker.d.ts +2 -2
- package/dist/broker/InMemoryBroker.d.ts.map +1 -1
- package/dist/broker/InMemoryBroker.js +4 -4
- package/dist/broker/InMemoryBroker.js.map +1 -1
- package/dist/broker/types.d.ts +3 -3
- package/dist/broker/types.d.ts.map +1 -1
- package/dist/journal/CoalescingService.d.ts.map +1 -1
- package/dist/journal/CoalescingService.js +18 -208
- package/dist/journal/CoalescingService.js.map +1 -1
- package/dist/journal/GraphJournalService.d.ts +127 -0
- package/dist/journal/GraphJournalService.d.ts.map +1 -0
- package/dist/journal/GraphJournalService.js +491 -0
- package/dist/journal/GraphJournalService.js.map +1 -0
- package/dist/journal/JournalRLE.d.ts +2 -2
- package/dist/journal/JournalRLE.js +14 -14
- package/dist/journal/JournalRLE.js.map +1 -1
- package/dist/journal/JournalRepository.js +7 -7
- package/dist/journal/JournalRepository.js.map +1 -1
- package/dist/journal/JournalService.d.ts.map +1 -1
- package/dist/journal/JournalService.js +6 -40
- package/dist/journal/JournalService.js.map +1 -1
- package/dist/journal/RLECompression.d.ts +9 -9
- package/dist/journal/RLECompression.d.ts.map +1 -1
- package/dist/journal/RLECompression.js +22 -22
- package/dist/journal/RLECompression.js.map +1 -1
- package/dist/journal/TextJournalService.d.ts +98 -0
- package/dist/journal/TextJournalService.d.ts.map +1 -0
- package/dist/journal/TextJournalService.js +401 -0
- package/dist/journal/TextJournalService.js.map +1 -0
- package/dist/journal/index.d.ts +3 -1
- package/dist/journal/index.d.ts.map +1 -1
- package/dist/journal/index.js +4 -1
- package/dist/journal/index.js.map +1 -1
- package/dist/journal/rle-demo.js +11 -11
- package/dist/journal/rle-demo.js.map +1 -1
- package/dist/serve.d.ts +29 -11
- package/dist/serve.d.ts.map +1 -1
- package/dist/serve.js +558 -93
- package/dist/serve.js.map +1 -1
- package/dist/transport/RTCServer.d.ts +2 -2
- package/dist/transport/RTCServer.d.ts.map +1 -1
- package/dist/transport/RTCServer.js +22 -22
- package/dist/transport/RTCServer.js.map +1 -1
- package/docs/API.md +642 -0
- package/examples/compression-example.ts +3 -3
- package/package.json +2 -2
- package/prisma/schema.prisma +124 -6
- package/src/archive/ArchivalService.ts +1 -1
- package/src/broker/InMemoryBroker.ts +4 -4
- package/src/broker/types.ts +3 -3
- package/src/journal/CoalescingService.ts +18 -235
- package/src/journal/{JournalService.ts → GraphJournalService.ts} +34 -74
- package/src/journal/JournalRLE.ts +15 -15
- package/src/journal/JournalRepository.ts +7 -7
- package/src/journal/RLECompression.ts +24 -24
- package/src/journal/TextJournalService.ts +483 -0
- package/src/journal/index.ts +10 -2
- package/src/journal/rle-demo.ts +11 -11
- package/src/serve.ts +598 -94
- package/src/transport/RTCServer.ts +23 -23
- package/tests/benchmark/journal-optimization-benchmark.test.ts +14 -14
- package/tests/compression/compression.test.ts +8 -8
- package/tests/demo.ts +88 -88
- package/tests/e2e/convergence.test.ts +9 -9
- package/tests/e2e/helpers/assertions.ts +22 -0
- package/tests/e2e/helpers/createTestServer.ts +4 -4
- package/tests/e2e/latency.test.ts +47 -41
- package/tests/e2e/packet-loss.test.ts +6 -6
- package/tests/e2e/relay.test.ts +9 -9
- package/tests/e2e/sync-perf.test.ts +5 -5
- package/tests/e2e/sync-reconciliation.test.ts +6 -6
- package/tests/e2e/text-sync.test.ts +14 -14
- package/tests/e2e/tombstone-convergence.test.ts +22 -22
- 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/messages.jsonl +4 -4
- 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 +9 -9
- package/tests/integration/repositories.test.ts +8 -9
- package/tests/journal/compaction-load-bug.test.ts +31 -31
- package/tests/journal/compaction.test.ts +26 -26
- package/tests/journal/journal-rle.test.ts +38 -38
- package/tests/journal/journal-service.test.ts +13 -13
- package/tests/journal/lww-ordering-bug.test.ts +39 -39
- package/tests/journal/rle-compression.test.ts +71 -71
- package/tests/journal/text-coalescing.test.ts +34 -34
- package/tests/test-data/datatypes.ts +85 -85
- package/tests/test-data/operations-example.ts +62 -62
- package/tests/test-data/scene-example.ts +11 -11
- package/tests/unit/operations.test.ts +7 -7
- package/tests/unit/s3-compression.test.ts +5 -3
- package/tests/unit/vectorClock.test.ts +2 -2
- package/tests/journal/multi-session-coalescing.test.ts +0 -871
|
@@ -28,15 +28,15 @@ export const NumberType = {
|
|
|
28
28
|
*/
|
|
29
29
|
set: {
|
|
30
30
|
behavior: 'lww',
|
|
31
|
-
merge: (ops: Array<{ value: number;
|
|
31
|
+
merge: (ops: Array<{ value: number; lt: number }>) => {
|
|
32
32
|
// Last-Write-Wins by lamport time
|
|
33
|
-
const sorted = ops.sort((a, b) => b.
|
|
33
|
+
const sorted = ops.sort((a, b) => b.lt - a.lt);
|
|
34
34
|
return sorted[0].value;
|
|
35
35
|
},
|
|
36
36
|
example: {
|
|
37
37
|
ops: [
|
|
38
|
-
{ value: 42,
|
|
39
|
-
{ value: 99,
|
|
38
|
+
{ value: 42, lt: 100 },
|
|
39
|
+
{ value: 99, lt: 101 },
|
|
40
40
|
],
|
|
41
41
|
result: 99,
|
|
42
42
|
},
|
|
@@ -48,14 +48,14 @@ export const NumberType = {
|
|
|
48
48
|
*/
|
|
49
49
|
add: {
|
|
50
50
|
behavior: 'additive',
|
|
51
|
-
merge: (ops: Array<{ value: number;
|
|
51
|
+
merge: (ops: Array<{ value: number; lt: number }>) => {
|
|
52
52
|
// Sum all concurrent operations
|
|
53
53
|
return ops.reduce((sum, op) => sum + op.value, 0);
|
|
54
54
|
},
|
|
55
55
|
example: {
|
|
56
56
|
ops: [
|
|
57
|
-
{ value: 10,
|
|
58
|
-
{ value: 5,
|
|
57
|
+
{ value: 10, lt: 100 },
|
|
58
|
+
{ value: 5, lt: 101 },
|
|
59
59
|
],
|
|
60
60
|
result: 15,
|
|
61
61
|
},
|
|
@@ -67,13 +67,13 @@ export const NumberType = {
|
|
|
67
67
|
*/
|
|
68
68
|
multiply: {
|
|
69
69
|
behavior: 'multiplicative',
|
|
70
|
-
merge: (ops: Array<{ value: number;
|
|
70
|
+
merge: (ops: Array<{ value: number; lt: number }>) => {
|
|
71
71
|
return ops.reduce((product, op) => product * op.value, 1);
|
|
72
72
|
},
|
|
73
73
|
example: {
|
|
74
74
|
ops: [
|
|
75
|
-
{ value: 2,
|
|
76
|
-
{ value: 3,
|
|
75
|
+
{ value: 2, lt: 100 },
|
|
76
|
+
{ value: 3, lt: 101 },
|
|
77
77
|
],
|
|
78
78
|
result: 6,
|
|
79
79
|
},
|
|
@@ -85,13 +85,13 @@ export const NumberType = {
|
|
|
85
85
|
*/
|
|
86
86
|
min: {
|
|
87
87
|
behavior: 'min',
|
|
88
|
-
merge: (ops: Array<{ value: number;
|
|
88
|
+
merge: (ops: Array<{ value: number; lt: number }>) => {
|
|
89
89
|
return Math.min(...ops.map((op) => op.value));
|
|
90
90
|
},
|
|
91
91
|
example: {
|
|
92
92
|
ops: [
|
|
93
|
-
{ value: 720,
|
|
94
|
-
{ value: 1080,
|
|
93
|
+
{ value: 720, lt: 100 },
|
|
94
|
+
{ value: 1080, lt: 101 },
|
|
95
95
|
],
|
|
96
96
|
result: 720,
|
|
97
97
|
},
|
|
@@ -103,13 +103,13 @@ export const NumberType = {
|
|
|
103
103
|
*/
|
|
104
104
|
max: {
|
|
105
105
|
behavior: 'max',
|
|
106
|
-
merge: (ops: Array<{ value: number;
|
|
106
|
+
merge: (ops: Array<{ value: number; lt: number }>) => {
|
|
107
107
|
return Math.max(...ops.map((op) => op.value));
|
|
108
108
|
},
|
|
109
109
|
example: {
|
|
110
110
|
ops: [
|
|
111
|
-
{ value: 720,
|
|
112
|
-
{ value: 1080,
|
|
111
|
+
{ value: 720, lt: 100 },
|
|
112
|
+
{ value: 1080, lt: 101 },
|
|
113
113
|
],
|
|
114
114
|
result: 1080,
|
|
115
115
|
},
|
|
@@ -132,14 +132,14 @@ export const StringType = {
|
|
|
132
132
|
*/
|
|
133
133
|
set: {
|
|
134
134
|
behavior: 'lww',
|
|
135
|
-
merge: (ops: Array<{ value: string;
|
|
135
|
+
merge: (ops: Array<{ value: string; lt: number }>) => {
|
|
136
136
|
const sorted = ops.sort((a, b) => b.lamportTime - a.lamportTime);
|
|
137
137
|
return sorted[0].value;
|
|
138
138
|
},
|
|
139
139
|
example: {
|
|
140
140
|
ops: [
|
|
141
|
-
{ value: 'Cube',
|
|
142
|
-
{ value: 'Sphere',
|
|
141
|
+
{ value: 'Cube', lt: 100 },
|
|
142
|
+
{ value: 'Sphere', lt: 101 },
|
|
143
143
|
],
|
|
144
144
|
result: 'Sphere',
|
|
145
145
|
},
|
|
@@ -151,14 +151,14 @@ export const StringType = {
|
|
|
151
151
|
*/
|
|
152
152
|
concat: {
|
|
153
153
|
behavior: 'append',
|
|
154
|
-
merge: (ops: Array<{ value: string;
|
|
155
|
-
const sorted = ops.sort((a, b) => a.
|
|
154
|
+
merge: (ops: Array<{ value: string; lt: number }>, separator = '\n') => {
|
|
155
|
+
const sorted = ops.sort((a, b) => a.lt - b.lt);
|
|
156
156
|
return sorted.map((op) => op.value).join(separator);
|
|
157
157
|
},
|
|
158
158
|
example: {
|
|
159
159
|
ops: [
|
|
160
|
-
{ value: 'User A edited',
|
|
161
|
-
{ value: 'User B edited',
|
|
160
|
+
{ value: 'User A edited', lt: 100 },
|
|
161
|
+
{ value: 'User B edited', lt: 101 },
|
|
162
162
|
],
|
|
163
163
|
result: 'User A edited\nUser B edited',
|
|
164
164
|
},
|
|
@@ -181,14 +181,14 @@ export const BooleanType = {
|
|
|
181
181
|
*/
|
|
182
182
|
set: {
|
|
183
183
|
behavior: 'lww',
|
|
184
|
-
merge: (ops: Array<{ value: boolean;
|
|
184
|
+
merge: (ops: Array<{ value: boolean; lt: number }>) => {
|
|
185
185
|
const sorted = ops.sort((a, b) => b.lamportTime - a.lamportTime);
|
|
186
186
|
return sorted[0].value;
|
|
187
187
|
},
|
|
188
188
|
example: {
|
|
189
189
|
ops: [
|
|
190
|
-
{ value: true,
|
|
191
|
-
{ value: false,
|
|
190
|
+
{ value: true, lt: 100 },
|
|
191
|
+
{ value: false, lt: 101 },
|
|
192
192
|
],
|
|
193
193
|
result: false,
|
|
194
194
|
},
|
|
@@ -200,13 +200,13 @@ export const BooleanType = {
|
|
|
200
200
|
*/
|
|
201
201
|
or: {
|
|
202
202
|
behavior: 'or',
|
|
203
|
-
merge: (ops: Array<{ value: boolean;
|
|
203
|
+
merge: (ops: Array<{ value: boolean; lt: number }>) => {
|
|
204
204
|
return ops.some((op) => op.value === true);
|
|
205
205
|
},
|
|
206
206
|
example: {
|
|
207
207
|
ops: [
|
|
208
|
-
{ value: true,
|
|
209
|
-
{ value: false,
|
|
208
|
+
{ value: true, lt: 100 },
|
|
209
|
+
{ value: false, lt: 101 },
|
|
210
210
|
],
|
|
211
211
|
result: true,
|
|
212
212
|
},
|
|
@@ -218,13 +218,13 @@ export const BooleanType = {
|
|
|
218
218
|
*/
|
|
219
219
|
and: {
|
|
220
220
|
behavior: 'and',
|
|
221
|
-
merge: (ops: Array<{ value: boolean;
|
|
221
|
+
merge: (ops: Array<{ value: boolean; lt: number }>) => {
|
|
222
222
|
return ops.every((op) => op.value === true);
|
|
223
223
|
},
|
|
224
224
|
example: {
|
|
225
225
|
ops: [
|
|
226
|
-
{ value: true,
|
|
227
|
-
{ value: false,
|
|
226
|
+
{ value: true, lt: 100 },
|
|
227
|
+
{ value: false, lt: 101 },
|
|
228
228
|
],
|
|
229
229
|
result: false,
|
|
230
230
|
},
|
|
@@ -253,14 +253,14 @@ export const Vector3Type = {
|
|
|
253
253
|
*/
|
|
254
254
|
set: {
|
|
255
255
|
behavior: 'lww',
|
|
256
|
-
merge: (ops: Array<{ value: [number, number, number];
|
|
256
|
+
merge: (ops: Array<{ value: [number, number, number]; lt: number }>) => {
|
|
257
257
|
const sorted = ops.sort((a, b) => b.lamportTime - a.lamportTime);
|
|
258
258
|
return sorted[0].value;
|
|
259
259
|
},
|
|
260
260
|
example: {
|
|
261
261
|
ops: [
|
|
262
|
-
{ value: [5, 0, 0],
|
|
263
|
-
{ value: [0, 5, 0],
|
|
262
|
+
{ value: [5, 0, 0], lt: 100 },
|
|
263
|
+
{ value: [0, 5, 0], lt: 101 },
|
|
264
264
|
],
|
|
265
265
|
result: [0, 5, 0],
|
|
266
266
|
// NOT [5, 5, 0] - that would violate user intent!
|
|
@@ -273,7 +273,7 @@ export const Vector3Type = {
|
|
|
273
273
|
*/
|
|
274
274
|
add: {
|
|
275
275
|
behavior: 'additive',
|
|
276
|
-
merge: (ops: Array<{ value: [number, number, number];
|
|
276
|
+
merge: (ops: Array<{ value: [number, number, number]; lt: number }>) => {
|
|
277
277
|
const sum: [number, number, number] = [0, 0, 0];
|
|
278
278
|
for (const op of ops) {
|
|
279
279
|
sum[0] += op.value[0];
|
|
@@ -284,8 +284,8 @@ export const Vector3Type = {
|
|
|
284
284
|
},
|
|
285
285
|
example: {
|
|
286
286
|
ops: [
|
|
287
|
-
{ value: [1, 0, 0],
|
|
288
|
-
{ value: [0, 1, 0],
|
|
287
|
+
{ value: [1, 0, 0], lt: 100 },
|
|
288
|
+
{ value: [0, 1, 0], lt: 101 },
|
|
289
289
|
],
|
|
290
290
|
result: [1, 1, 0],
|
|
291
291
|
},
|
|
@@ -297,7 +297,7 @@ export const Vector3Type = {
|
|
|
297
297
|
*/
|
|
298
298
|
multiply: {
|
|
299
299
|
behavior: 'multiplicative',
|
|
300
|
-
merge: (ops: Array<{ value: [number, number, number];
|
|
300
|
+
merge: (ops: Array<{ value: [number, number, number]; lt: number }>) => {
|
|
301
301
|
const product: [number, number, number] = [1, 1, 1];
|
|
302
302
|
for (const op of ops) {
|
|
303
303
|
product[0] *= op.value[0];
|
|
@@ -308,8 +308,8 @@ export const Vector3Type = {
|
|
|
308
308
|
},
|
|
309
309
|
example: {
|
|
310
310
|
ops: [
|
|
311
|
-
{ value: [2, 1, 1],
|
|
312
|
-
{ value: [1, 3, 1],
|
|
311
|
+
{ value: [2, 1, 1], lt: 100 },
|
|
312
|
+
{ value: [1, 3, 1], lt: 101 },
|
|
313
313
|
],
|
|
314
314
|
result: [2, 3, 1],
|
|
315
315
|
},
|
|
@@ -324,24 +324,24 @@ export const Vector3Type = {
|
|
|
324
324
|
merge: (
|
|
325
325
|
ops: Array<{
|
|
326
326
|
value: Partial<{ x: number; y: number; z: number }>;
|
|
327
|
-
|
|
327
|
+
lt: number;
|
|
328
328
|
}>
|
|
329
329
|
) => {
|
|
330
330
|
const result = { x: 0, y: 0, z: 0 };
|
|
331
331
|
const latest = { x: -1, y: -1, z: -1 };
|
|
332
332
|
|
|
333
333
|
for (const op of ops) {
|
|
334
|
-
if (op.value.x !== undefined && op.
|
|
334
|
+
if (op.value.x !== undefined && op.lt > latest.x) {
|
|
335
335
|
result.x = op.value.x;
|
|
336
|
-
latest.x = op.
|
|
336
|
+
latest.x = op.lt;
|
|
337
337
|
}
|
|
338
|
-
if (op.value.y !== undefined && op.
|
|
338
|
+
if (op.value.y !== undefined && op.lt > latest.y) {
|
|
339
339
|
result.y = op.value.y;
|
|
340
|
-
latest.y = op.
|
|
340
|
+
latest.y = op.lt;
|
|
341
341
|
}
|
|
342
|
-
if (op.value.z !== undefined && op.
|
|
342
|
+
if (op.value.z !== undefined && op.lt > latest.z) {
|
|
343
343
|
result.z = op.value.z;
|
|
344
|
-
latest.z = op.
|
|
344
|
+
latest.z = op.lt;
|
|
345
345
|
}
|
|
346
346
|
}
|
|
347
347
|
|
|
@@ -349,8 +349,8 @@ export const Vector3Type = {
|
|
|
349
349
|
},
|
|
350
350
|
example: {
|
|
351
351
|
ops: [
|
|
352
|
-
{ value: { x: 5 },
|
|
353
|
-
{ value: { y: 3 },
|
|
352
|
+
{ value: { x: 5 }, lt: 100 },
|
|
353
|
+
{ value: { y: 3 }, lt: 101 },
|
|
354
354
|
],
|
|
355
355
|
result: [5, 3, 0],
|
|
356
356
|
},
|
|
@@ -374,14 +374,14 @@ export const QuaternionType = {
|
|
|
374
374
|
*/
|
|
375
375
|
set: {
|
|
376
376
|
behavior: 'lww',
|
|
377
|
-
merge: (ops: Array<{ value: [number, number, number, number];
|
|
377
|
+
merge: (ops: Array<{ value: [number, number, number, number]; lt: number }>) => {
|
|
378
378
|
const sorted = ops.sort((a, b) => b.lamportTime - a.lamportTime);
|
|
379
379
|
return sorted[0].value;
|
|
380
380
|
},
|
|
381
381
|
example: {
|
|
382
382
|
ops: [
|
|
383
|
-
{ value: [0, 0, 0, 1],
|
|
384
|
-
{ value: [0, 0.707, 0, 0.707],
|
|
383
|
+
{ value: [0, 0, 0, 1], lt: 100 }, // No rotation
|
|
384
|
+
{ value: [0, 0.707, 0, 0.707], lt: 101 }, // 90° around Y
|
|
385
385
|
],
|
|
386
386
|
result: [0, 0.707, 0, 0.707],
|
|
387
387
|
},
|
|
@@ -394,9 +394,9 @@ export const QuaternionType = {
|
|
|
394
394
|
*/
|
|
395
395
|
multiply: {
|
|
396
396
|
behavior: 'multiplicative',
|
|
397
|
-
merge: (ops: Array<{ value: [number, number, number, number];
|
|
397
|
+
merge: (ops: Array<{ value: [number, number, number, number]; lt: number }>) => {
|
|
398
398
|
// Sort by lamport time to get deterministic order
|
|
399
|
-
const sorted = ops.sort((a, b) => a.
|
|
399
|
+
const sorted = ops.sort((a, b) => a.lt - b.lt);
|
|
400
400
|
|
|
401
401
|
// Multiply quaternions in order
|
|
402
402
|
let result = sorted[0].value;
|
|
@@ -407,8 +407,8 @@ export const QuaternionType = {
|
|
|
407
407
|
},
|
|
408
408
|
example: {
|
|
409
409
|
ops: [
|
|
410
|
-
{ value: [0, 0.707, 0, 0.707],
|
|
411
|
-
{ value: [0.707, 0, 0, 0.707],
|
|
410
|
+
{ value: [0, 0.707, 0, 0.707], lt: 100 }, // 90° Y
|
|
411
|
+
{ value: [0.707, 0, 0, 0.707], lt: 101 }, // 90° X
|
|
412
412
|
],
|
|
413
413
|
result: [0.5, 0.5, 0.5, 0.5], // Combined rotation
|
|
414
414
|
},
|
|
@@ -449,14 +449,14 @@ export const ColorType = {
|
|
|
449
449
|
*/
|
|
450
450
|
set: {
|
|
451
451
|
behavior: 'lww',
|
|
452
|
-
merge: (ops: Array<{ value: string;
|
|
452
|
+
merge: (ops: Array<{ value: string; lt: number }>) => {
|
|
453
453
|
const sorted = ops.sort((a, b) => b.lamportTime - a.lamportTime);
|
|
454
454
|
return sorted[0].value;
|
|
455
455
|
},
|
|
456
456
|
example: {
|
|
457
457
|
ops: [
|
|
458
|
-
{ value: '#ff0000',
|
|
459
|
-
{ value: '#0000ff',
|
|
458
|
+
{ value: '#ff0000', lt: 100 },
|
|
459
|
+
{ value: '#0000ff', lt: 101 },
|
|
460
460
|
],
|
|
461
461
|
result: '#0000ff',
|
|
462
462
|
},
|
|
@@ -468,7 +468,7 @@ export const ColorType = {
|
|
|
468
468
|
*/
|
|
469
469
|
blend: {
|
|
470
470
|
behavior: 'blend',
|
|
471
|
-
merge: (ops: Array<{ value: string;
|
|
471
|
+
merge: (ops: Array<{ value: string; lt: number }>) => {
|
|
472
472
|
// Convert hex to RGB, average, convert back
|
|
473
473
|
const rgbs = ops.map((op) => hexToRgb(op.value));
|
|
474
474
|
const avgR = Math.round(rgbs.reduce((sum, rgb) => sum + rgb.r, 0) / rgbs.length);
|
|
@@ -478,8 +478,8 @@ export const ColorType = {
|
|
|
478
478
|
},
|
|
479
479
|
example: {
|
|
480
480
|
ops: [
|
|
481
|
-
{ value: '#ff0000',
|
|
482
|
-
{ value: '#0000ff',
|
|
481
|
+
{ value: '#ff0000', lt: 100 }, // Red
|
|
482
|
+
{ value: '#0000ff', lt: 101 }, // Blue
|
|
483
483
|
],
|
|
484
484
|
result: '#7f007f', // Purple
|
|
485
485
|
},
|
|
@@ -527,14 +527,14 @@ export const ArrayType = {
|
|
|
527
527
|
*/
|
|
528
528
|
set: {
|
|
529
529
|
behavior: 'lww',
|
|
530
|
-
merge: (ops: Array<{ value: any[];
|
|
530
|
+
merge: (ops: Array<{ value: any[]; lt: number }>) => {
|
|
531
531
|
const sorted = ops.sort((a, b) => b.lamportTime - a.lamportTime);
|
|
532
532
|
return sorted[0].value;
|
|
533
533
|
},
|
|
534
534
|
example: {
|
|
535
535
|
ops: [
|
|
536
|
-
{ value: ['a', 'b'],
|
|
537
|
-
{ value: ['c', 'd'],
|
|
536
|
+
{ value: ['a', 'b'], lt: 100 },
|
|
537
|
+
{ value: ['c', 'd'], lt: 101 },
|
|
538
538
|
],
|
|
539
539
|
result: ['c', 'd'],
|
|
540
540
|
},
|
|
@@ -546,14 +546,14 @@ export const ArrayType = {
|
|
|
546
546
|
*/
|
|
547
547
|
union: {
|
|
548
548
|
behavior: 'union',
|
|
549
|
-
merge: (ops: Array<{ value: any[];
|
|
549
|
+
merge: (ops: Array<{ value: any[]; lt: number }>) => {
|
|
550
550
|
const allElements = ops.flatMap((op) => op.value);
|
|
551
551
|
return [...new Set(allElements)];
|
|
552
552
|
},
|
|
553
553
|
example: {
|
|
554
554
|
ops: [
|
|
555
|
-
{ value: ['a', 'b'],
|
|
556
|
-
{ value: ['b', 'c'],
|
|
555
|
+
{ value: ['a', 'b'], lt: 100 },
|
|
556
|
+
{ value: ['b', 'c'], lt: 101 },
|
|
557
557
|
],
|
|
558
558
|
result: ['a', 'b', 'c'],
|
|
559
559
|
},
|
|
@@ -565,14 +565,14 @@ export const ArrayType = {
|
|
|
565
565
|
*/
|
|
566
566
|
append: {
|
|
567
567
|
behavior: 'append',
|
|
568
|
-
merge: (ops: Array<{ value: any[];
|
|
569
|
-
const sorted = ops.sort((a, b) => a.
|
|
568
|
+
merge: (ops: Array<{ value: any[]; lt: number }>) => {
|
|
569
|
+
const sorted = ops.sort((a, b) => a.lt - b.lt);
|
|
570
570
|
return sorted.flatMap((op) => op.value);
|
|
571
571
|
},
|
|
572
572
|
example: {
|
|
573
573
|
ops: [
|
|
574
|
-
{ value: ['edit1'],
|
|
575
|
-
{ value: ['edit2'],
|
|
574
|
+
{ value: ['edit1'], lt: 100 },
|
|
575
|
+
{ value: ['edit2'], lt: 101 },
|
|
576
576
|
],
|
|
577
577
|
result: ['edit1', 'edit2'],
|
|
578
578
|
},
|
|
@@ -585,10 +585,10 @@ export const ArrayType = {
|
|
|
585
585
|
rga: {
|
|
586
586
|
behavior: 'rga',
|
|
587
587
|
merge: (
|
|
588
|
-
ops: Array<{ action: 'insert' | 'delete'; index: number; value?: any;
|
|
588
|
+
ops: Array<{ action: 'insert' | 'delete'; index: number; value?: any; lt: number }>
|
|
589
589
|
) => {
|
|
590
590
|
// Simplified RGA - real implementation needs tombstones and references
|
|
591
|
-
const sorted = ops.sort((a, b) => a.
|
|
591
|
+
const sorted = ops.sort((a, b) => a.lt - b.lt);
|
|
592
592
|
const result: any[] = [];
|
|
593
593
|
|
|
594
594
|
for (const op of sorted) {
|
|
@@ -603,9 +603,9 @@ export const ArrayType = {
|
|
|
603
603
|
},
|
|
604
604
|
example: {
|
|
605
605
|
ops: [
|
|
606
|
-
{ action: 'insert', index: 0, value: 'a',
|
|
607
|
-
{ action: 'insert', index: 1, value: 'b',
|
|
608
|
-
{ action: 'insert', index: 1, value: 'X',
|
|
606
|
+
{ action: 'insert', index: 0, value: 'a', lt: 100 },
|
|
607
|
+
{ action: 'insert', index: 1, value: 'b', lt: 101 },
|
|
608
|
+
{ action: 'insert', index: 1, value: 'X', lt: 102 },
|
|
609
609
|
],
|
|
610
610
|
result: ['a', 'X', 'b'],
|
|
611
611
|
},
|
|
@@ -628,14 +628,14 @@ export const ObjectType = {
|
|
|
628
628
|
*/
|
|
629
629
|
set: {
|
|
630
630
|
behavior: 'lww',
|
|
631
|
-
merge: (ops: Array<{ value: Record<string, any>;
|
|
631
|
+
merge: (ops: Array<{ value: Record<string, any>; lt: number }>) => {
|
|
632
632
|
const sorted = ops.sort((a, b) => b.lamportTime - a.lamportTime);
|
|
633
633
|
return sorted[0].value;
|
|
634
634
|
},
|
|
635
635
|
example: {
|
|
636
636
|
ops: [
|
|
637
|
-
{ value: { a: 1 },
|
|
638
|
-
{ value: { b: 2 },
|
|
637
|
+
{ value: { a: 1 }, lt: 100 },
|
|
638
|
+
{ value: { b: 2 }, lt: 101 },
|
|
639
639
|
],
|
|
640
640
|
result: { b: 2 },
|
|
641
641
|
},
|
|
@@ -648,14 +648,14 @@ export const ObjectType = {
|
|
|
648
648
|
lwwPerKey: {
|
|
649
649
|
behavior: 'lww-per-key',
|
|
650
650
|
merge: (
|
|
651
|
-
ops: Array<{ value: Record<string, any>;
|
|
651
|
+
ops: Array<{ value: Record<string, any>; lt: number; perKeyLamport?: Record<string, number> }>
|
|
652
652
|
) => {
|
|
653
653
|
const result: Record<string, any> = {};
|
|
654
654
|
const latestTimes: Record<string, number> = {};
|
|
655
655
|
|
|
656
656
|
for (const op of ops) {
|
|
657
657
|
for (const [key, value] of Object.entries(op.value)) {
|
|
658
|
-
const keyLamport = op.perKeyLamport?.[key] ?? op.
|
|
658
|
+
const keyLamport = op.perKeyLamport?.[key] ?? op.lt;
|
|
659
659
|
if (!latestTimes[key] || keyLamport > latestTimes[key]) {
|
|
660
660
|
result[key] = value;
|
|
661
661
|
latestTimes[key] = keyLamport;
|
|
@@ -667,8 +667,8 @@ export const ObjectType = {
|
|
|
667
667
|
},
|
|
668
668
|
example: {
|
|
669
669
|
ops: [
|
|
670
|
-
{ value: { color: 'red', size: 10 },
|
|
671
|
-
{ value: { color: 'blue', opacity: 0.5 },
|
|
670
|
+
{ value: { color: 'red', size: 10 }, lt: 100 },
|
|
671
|
+
{ value: { color: 'blue', opacity: 0.5 }, lt: 101 },
|
|
672
672
|
],
|
|
673
673
|
result: { color: 'blue', size: 10, opacity: 0.5 },
|
|
674
674
|
},
|