@vuer-ai/vuer-rtc-server 0.2.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/.env +1 -0
  2. package/S3_COMPRESSION_GUIDE.md +233 -0
  3. package/dist/archive/ArchivalService.d.ts +117 -0
  4. package/dist/archive/ArchivalService.d.ts.map +1 -0
  5. package/dist/archive/ArchivalService.js +181 -0
  6. package/dist/archive/ArchivalService.js.map +1 -0
  7. package/dist/broker/InMemoryBroker.d.ts +2 -0
  8. package/dist/broker/InMemoryBroker.d.ts.map +1 -1
  9. package/dist/broker/InMemoryBroker.js +4 -0
  10. package/dist/broker/InMemoryBroker.js.map +1 -1
  11. package/dist/compression/CompressionUtils.d.ts +57 -0
  12. package/dist/compression/CompressionUtils.d.ts.map +1 -0
  13. package/dist/compression/CompressionUtils.js +90 -0
  14. package/dist/compression/CompressionUtils.js.map +1 -0
  15. package/dist/compression/index.d.ts +7 -0
  16. package/dist/compression/index.d.ts.map +1 -0
  17. package/dist/compression/index.js +7 -0
  18. package/dist/compression/index.js.map +1 -0
  19. package/dist/journal/CoalescingService.d.ts +63 -0
  20. package/dist/journal/CoalescingService.d.ts.map +1 -0
  21. package/dist/journal/CoalescingService.js +507 -0
  22. package/dist/journal/CoalescingService.js.map +1 -0
  23. package/dist/journal/JournalRLE.d.ts +81 -0
  24. package/dist/journal/JournalRLE.d.ts.map +1 -0
  25. package/dist/journal/JournalRLE.js +199 -0
  26. package/dist/journal/JournalRLE.js.map +1 -0
  27. package/dist/journal/JournalService.d.ts +7 -3
  28. package/dist/journal/JournalService.d.ts.map +1 -1
  29. package/dist/journal/JournalService.js +152 -12
  30. package/dist/journal/JournalService.js.map +1 -1
  31. package/dist/journal/RLECompression.d.ts +73 -0
  32. package/dist/journal/RLECompression.d.ts.map +1 -0
  33. package/dist/journal/RLECompression.js +152 -0
  34. package/dist/journal/RLECompression.js.map +1 -0
  35. package/dist/journal/rle-demo.d.ts +8 -0
  36. package/dist/journal/rle-demo.d.ts.map +1 -0
  37. package/dist/journal/rle-demo.js +159 -0
  38. package/dist/journal/rle-demo.js.map +1 -0
  39. package/dist/persistence/S3ColdStorage.d.ts +62 -0
  40. package/dist/persistence/S3ColdStorage.d.ts.map +1 -0
  41. package/dist/persistence/S3ColdStorage.js +88 -0
  42. package/dist/persistence/S3ColdStorage.js.map +1 -0
  43. package/dist/persistence/S3ColdStorageIntegration.d.ts +78 -0
  44. package/dist/persistence/S3ColdStorageIntegration.d.ts.map +1 -0
  45. package/dist/persistence/S3ColdStorageIntegration.js +93 -0
  46. package/dist/persistence/S3ColdStorageIntegration.js.map +1 -0
  47. package/dist/serve.d.ts +2 -0
  48. package/dist/serve.d.ts.map +1 -1
  49. package/dist/serve.js +623 -15
  50. package/dist/serve.js.map +1 -1
  51. package/docs/RLE_COMPRESSION.md +397 -0
  52. package/examples/compression-example.ts +259 -0
  53. package/package.json +14 -14
  54. package/src/archive/ArchivalService.ts +250 -0
  55. package/src/broker/InMemoryBroker.ts +5 -0
  56. package/src/compression/CompressionUtils.ts +113 -0
  57. package/src/compression/index.ts +14 -0
  58. package/src/journal/COALESCING.md +267 -0
  59. package/src/journal/CoalescingService.ts +626 -0
  60. package/src/journal/JournalRLE.ts +265 -0
  61. package/src/journal/JournalService.ts +163 -11
  62. package/src/journal/RLECompression.ts +210 -0
  63. package/src/journal/rle-demo.ts +193 -0
  64. package/src/serve.ts +702 -15
  65. package/tests/benchmark/journal-optimization-benchmark.test.ts +482 -0
  66. package/tests/compression/compression.test.ts +343 -0
  67. package/tests/integration/repositories.test.ts +89 -0
  68. package/tests/journal/compaction-load-bug.test.ts +409 -0
  69. package/tests/journal/compaction.test.ts +42 -2
  70. package/tests/journal/journal-rle.test.ts +511 -0
  71. package/tests/journal/lww-ordering-bug.test.ts +248 -0
  72. package/tests/journal/multi-session-coalescing.test.ts +871 -0
  73. package/tests/journal/rle-compression.test.ts +526 -0
  74. package/tests/journal/text-coalescing.test.ts +210 -0
  75. package/tests/unit/s3-compression.test.ts +257 -0
  76. package/PHASE1_SUMMARY.md +0 -94
@@ -0,0 +1,267 @@
1
+ # Operation Coalescing Design
2
+
3
+ ## Overview
4
+
5
+ Coalescing reduces journal size by merging compatible operations. This improves:
6
+ - Storage efficiency (fewer operations stored)
7
+ - Replay performance (fewer operations to apply)
8
+ - State serialization depth (fewer items in B-trees)
9
+
10
+ ## Session Boundaries and Causality Preservation
11
+
12
+ **Critical constraint**: Coalescing MUST preserve the **interleaved order** of operations across sessions to maintain causality.
13
+
14
+ ### The Problem
15
+
16
+ Given messages in timestamp order:
17
+ ```
18
+ msg1: sessionA [op1, op2]
19
+ msg2: sessionA [op3]
20
+ msg3: sessionB [op4]
21
+ msg4: sessionA [op5]
22
+ ```
23
+
24
+ **WRONG** - Grouping all operations by session:
25
+ ```
26
+ coalesced-A: [op1, op2, op3, op5] // ❌ Lost causality
27
+ coalesced-B: [op4]
28
+ ```
29
+ This violates causality because `op5` happened after `op4` was received. If `op5` was created in response to seeing `op4`, reordering them breaks the happened-before relation.
30
+
31
+ **CORRECT** - Preserve interleaved order, coalesce consecutive runs:
32
+ ```
33
+ run1-A: [op1, op2, op3] // Consecutive messages from A
34
+ run2-B: [op4] // Session switch to B
35
+ run3-A: [op5] // Back to A (preserves that op5 came AFTER op4)
36
+ ```
37
+
38
+ ### Implementation Strategy
39
+
40
+ 1. Process messages in timestamp order
41
+ 2. Detect "session switches" (when `msg[i].sessionId !== msg[i-1].sessionId`)
42
+ 3. Coalesce each consecutive run from the same session
43
+ 4. Emit coalesced messages in the same order as the runs
44
+
45
+ This approach preserves Lamport's "happened-before" relation while still achieving compression benefits.
46
+
47
+ ## Types of Coalescing
48
+
49
+ ### 1. Client-Side Edit Batching (Optional, UI-Level)
50
+
51
+ **Status**: Optional feature controlled by UI toggle
52
+
53
+ **Current behavior**: Operations are batched with a timer (default 300ms), but each edit creates a separate operation in the batch.
54
+
55
+ **Purpose**: Reduces network traffic by delaying commits during rapid editing.
56
+
57
+ **Implementation location**: `packages/vuer-rtc/src/client/createGraph.ts`
58
+
59
+ ### 2. Server-Side TextRope Coalescing (Automatic)
60
+
61
+ **Status**: ✅ **Implemented** (automatically runs during compaction)
62
+
63
+ **How it works**: When the server compacts the journal, it merges single-character TextRope items into multi-character spans, preventing B-tree depth explosion.
64
+
65
+ **Performance impact**: Up to 5000x speedup on real editing traces (based on "CRDTs Go Brrr")
66
+
67
+ **Implementation location**: `packages/vuer-rtc-server/src/journal/JournalService.ts` (compactTextRopes function)
68
+
69
+ ### 3. Server-Side Operation Coalescing (Journal-Level)
70
+
71
+ **Status**: Partially implemented via CoalescingService
72
+
73
+ Merges historical operations in the journal. Called via `POST /api/documents/:id/coalesce`.
74
+
75
+ ## Coalescing Rules
76
+
77
+ ### Rule 1: Text Insert Coalescing
78
+
79
+ Consecutive `text.insert` operations from the same session, where each insert's position follows the previous insert, are merged into a single multi-character insert.
80
+
81
+ ```
82
+ Before:
83
+ { otype: "text.insert", position: 0, value: "a" }
84
+ { otype: "text.insert", position: 1, value: "b" }
85
+ { otype: "text.insert", position: 2, value: "c" }
86
+
87
+ After:
88
+ { otype: "text.insert", position: 0, value: "abc" }
89
+ ```
90
+
91
+ **Constraints**:
92
+ - Same session ID
93
+ - Positions are sequential (insert_n.position === insert_n-1.position + len(insert_n-1.value))
94
+ - Same target node and path
95
+
96
+ ### Rule 1b: Text Delete Coalescing ✅ **NEW**
97
+
98
+ Consecutive `text.delete` operations are merged based on deletion direction:
99
+
100
+ **Forward deletion (Delete key)** - Same position:
101
+ ```
102
+ Before:
103
+ { otype: "text.delete", position: 10, length: 1 }
104
+ { otype: "text.delete", position: 10, length: 1 }
105
+ { otype: "text.delete", position: 10, length: 1 }
106
+
107
+ After:
108
+ { otype: "text.delete", position: 10, length: 3 }
109
+ ```
110
+
111
+ **Backward deletion (Backspace)** - Position moves left:
112
+ ```
113
+ Before:
114
+ { otype: "text.delete", position: 10, length: 1 }
115
+ { otype: "text.delete", position: 9, length: 1 }
116
+ { otype: "text.delete", position: 8, length: 1 }
117
+
118
+ After:
119
+ { otype: "text.delete", position: 8, length: 3 }
120
+ ```
121
+
122
+ **Constraints**:
123
+ - Same session ID, key, and path
124
+ - Either: same position (forward) OR position decreases by previous length (backward)
125
+
126
+ ### Rule 2: Set Operation Coalescing (with Time Threshold)
127
+
128
+ Multiple `set` operations on the same `(key, path)` are merged, keeping only the latest value.
129
+
130
+ **Time Threshold**: If the gap between two operations exceeds `SET_COALESCE_THRESHOLD_MS` (default: 5000ms), they are NOT coalesced. This preserves meaningful state changes while coalescing rapid updates (like dragging a slider).
131
+
132
+ ```
133
+ Before (within threshold):
134
+ { otype: "set", key: "cube", path: "opacity", value: 0.5, ts: 1000 }
135
+ { otype: "set", key: "cube", path: "opacity", value: 0.6, ts: 1200 }
136
+ { otype: "set", key: "cube", path: "opacity", value: 0.7, ts: 1400 }
137
+
138
+ After:
139
+ { otype: "set", key: "cube", path: "opacity", value: 0.7, ts: 1400 }
140
+
141
+ Before (exceeds threshold):
142
+ { otype: "set", key: "cube", path: "opacity", value: 0.5, ts: 1000 }
143
+ { otype: "set", key: "cube", path: "opacity", value: 0.9, ts: 7000 } // Gap > 5s
144
+
145
+ After (no coalescing):
146
+ { otype: "set", key: "cube", path: "opacity", value: 0.5, ts: 1000 }
147
+ { otype: "set", key: "cube", path: "opacity", value: 0.9, ts: 7000 }
148
+ ```
149
+
150
+ ### Rule 3: Vector Add Coalescing
151
+
152
+ Multiple `vector3.add` operations on the same `(key, path)` are summed.
153
+
154
+ ```
155
+ Before:
156
+ { otype: "vector3.add", key: "cube", path: "position", value: [1, 0, 0] }
157
+ { otype: "vector3.add", key: "cube", path: "position", value: [0, 1, 0] }
158
+
159
+ After:
160
+ { otype: "vector3.add", key: "cube", path: "position", value: [1, 1, 0] }
161
+ ```
162
+
163
+ **Constraints**: Same time threshold as `set` operations.
164
+
165
+ ## Configuration
166
+
167
+ ```typescript
168
+ interface CoalescingConfig {
169
+ // Time threshold for set/vector operations (ms)
170
+ setThresholdMs: number; // default: 5000
171
+
172
+ // Enable/disable specific rules
173
+ enableTextCoalesce: boolean; // default: true
174
+ enableSetCoalesce: boolean; // default: true
175
+ enableVectorCoalesce: boolean; // default: true
176
+ }
177
+ ```
178
+
179
+ ## API
180
+
181
+ ### Client-Side
182
+
183
+ Already exists in `createGraph`:
184
+ ```typescript
185
+ store.setCoalescingEnabled(true);
186
+ store.setCoalescingDelay(300); // Batch window in ms
187
+ ```
188
+
189
+ **Enhancement needed**: Actually merge operations in the edit buffer, not just batch them.
190
+
191
+ ### Server-Side
192
+
193
+ ```http
194
+ POST /api/documents/:id/coalesce
195
+ Content-Type: application/json
196
+
197
+ {
198
+ "setThresholdMs": 5000,
199
+ "dryRun": false
200
+ }
201
+
202
+ Response:
203
+ {
204
+ "ok": true,
205
+ "before": { "journalBatches": 100, "operations": 5000 },
206
+ "after": { "journalBatches": 50, "operations": 1200 },
207
+ "reduction": { "journalBatches": 50, "operations": 3800 }
208
+ }
209
+ ```
210
+
211
+ ## Implementation Plan
212
+
213
+ 1. **Client-side op merging** (`vuer-rtc/src/client/actions.ts`)
214
+ - Add `mergeOperations()` function that merges compatible ops in edit buffer
215
+ - Call before `commitEdits()` when coalescing is enabled
216
+
217
+ 2. **Server-side coalescing** (`vuer-rtc-server/src/journal/CoalescingService.ts`)
218
+ - New service that processes journal batches
219
+ - Merges operations within each batch
220
+ - Re-computes snapshot after coalescing
221
+ - Updates database atomically
222
+
223
+ 3. **Text CRDT depth fix** (`vuer-rtc/src/crdt/Rope.ts`)
224
+ - Multi-char inserts create fewer B-tree nodes
225
+ - Each insert operation adds one item, not N items for N characters
226
+
227
+ ## Metrics & Debugging
228
+
229
+ Add logging for coalescing effectiveness:
230
+ ```
231
+ [coalesce] document=demo-room ops_before=5000 ops_after=1200 reduction=76%
232
+ ```
233
+
234
+ ## Known Issues
235
+
236
+ ### B-Tree Depth Explosion
237
+
238
+ The text CRDT B-tree (`_textRope.content._tree`) grows very deep with many single-char inserts. Each character becomes a separate node in the tree. With thousands of characters, the tree exceeds msgpack's depth limit.
239
+
240
+ **Root cause**: Each `text.insert` with single char creates a separate item in the B-tree.
241
+
242
+ **Solution**:
243
+ 1. Short-term: Increase msgpack `maxDepth` (done: 100 → 500, need more)
244
+ 2. Long-term: Coalesce inserts client-side to create multi-char operations
245
+ 3. Alternative: Refactor B-tree serialization to use flat structure
246
+
247
+ ## Testing
248
+
249
+ 1. Type rapidly in text CRDT demo
250
+ 2. Verify message log shows fewer, larger operations
251
+ 3. Verify journal in DB Viewer shows coalesced operations
252
+ 4. Test undo/redo still works after coalescing
253
+ 5. Test multi-client conflict resolution after coalescing
254
+
255
+ ## References
256
+
257
+ ### Causality and Operation Ordering
258
+
259
+ - [Peritext: A CRDT for Collaborative Rich Text Editing](https://dspace.mit.edu/bitstream/handle/1721.1/147641/3555644.pdf?sequence=1&isAllowed=y) - MIT research on collaborative text editing with CRDTs
260
+ - [Real-time collaborative editing using CRDTs](http://www.diva-portal.org/smash/get/diva2:1304659/FULLTEXT01.pdf) - Comprehensive study on CRDT-based collaborative editing
261
+ - [Geometry-Aware CRDTs for Efficient Collaborative Geospatial Editing](https://www.mdpi.com/2220-9964/14/12/468) - Research on preserving both temporal causality and spatial dependencies, includes discussion of operation batching and causality preservation
262
+
263
+ ### Key Concepts
264
+
265
+ - **Lamport's "happened-before" relation**: Operations must maintain causality - if op B was created after seeing op A, that ordering must be preserved
266
+ - **Interleaving**: When operations from different sessions are interspersed, coalescing must not reorder them
267
+ - **Vector clocks**: Track causality across distributed sessions