@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
package/docs/API.md
ADDED
|
@@ -0,0 +1,642 @@
|
|
|
1
|
+
# Vuer RTC Server API Documentation
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
The Vuer RTC Server provides two types of collaborative documents:
|
|
6
|
+
|
|
7
|
+
1. **Graph Documents** - Collaborative 3D scene graphs with nodes, transforms, and properties
|
|
8
|
+
2. **Text Documents** - Collaborative text documents using CRDT (TextRope)
|
|
9
|
+
|
|
10
|
+
Each type has dedicated REST and WebSocket APIs.
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## WebSocket API
|
|
15
|
+
|
|
16
|
+
### Graph Documents
|
|
17
|
+
|
|
18
|
+
**URL**: `ws://server/ws/graph/{roomId}?sessionId={sessionId}`
|
|
19
|
+
|
|
20
|
+
**Lifecycle**:
|
|
21
|
+
```
|
|
22
|
+
1. Client connects → Server sends initial state
|
|
23
|
+
← { type: 'init', snapshot: {...}, journal: [...] }
|
|
24
|
+
|
|
25
|
+
2. Client sends operations
|
|
26
|
+
→ { ops: [{ otype: 'node.insert', ... }], clock: {...}, lamportTime: 1 }
|
|
27
|
+
|
|
28
|
+
3. Server broadcasts to all clients
|
|
29
|
+
← { ops: [...], clock: {...}, lamportTime: 1 }
|
|
30
|
+
|
|
31
|
+
4. Server sends acknowledgment
|
|
32
|
+
← { type: 'ack', clock: {...}, lamportTime: 1 }
|
|
33
|
+
|
|
34
|
+
5. Client disconnects → Server removes from room
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
**Message Types**:
|
|
38
|
+
- `init` - Initial state snapshot + journal
|
|
39
|
+
- `message` - CRDT operations broadcast
|
|
40
|
+
- `ack` - Server acknowledgment of client messages
|
|
41
|
+
- `error` - Error notification
|
|
42
|
+
|
|
43
|
+
### Text Documents
|
|
44
|
+
|
|
45
|
+
**URL**: `ws://server/ws/text/{docId}?sessionId={sessionId}`
|
|
46
|
+
|
|
47
|
+
**Lifecycle**:
|
|
48
|
+
```
|
|
49
|
+
1. Client connects → Server sends initial text state
|
|
50
|
+
← { type: 'init', snapshot: { text: "..." }, journal: [...] }
|
|
51
|
+
|
|
52
|
+
2. Client sends text operations
|
|
53
|
+
→ { ops: [{ otype: 'text.insert', position: 0, value: 'Hello' }], clock: {...}, lamportTime: 1 }
|
|
54
|
+
|
|
55
|
+
3. Server broadcasts to all clients
|
|
56
|
+
← { ops: [...], clock: {...}, lamportTime: 1 }
|
|
57
|
+
|
|
58
|
+
4. Server sends acknowledgment
|
|
59
|
+
← { type: 'ack', clock: {...}, lamportTime: 1 }
|
|
60
|
+
|
|
61
|
+
5. Client disconnects → Server removes from document
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
**Text Operation Types**:
|
|
65
|
+
- `text.insert` - Insert text at position
|
|
66
|
+
- `text.delete` - Delete text at position
|
|
67
|
+
- `text.replace` - Replace text (atomic delete + insert)
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## REST API
|
|
72
|
+
|
|
73
|
+
### Statistics
|
|
74
|
+
|
|
75
|
+
#### `GET /api/stats`
|
|
76
|
+
Get database statistics
|
|
77
|
+
|
|
78
|
+
**Response**:
|
|
79
|
+
```json
|
|
80
|
+
{
|
|
81
|
+
"documents": 10,
|
|
82
|
+
"textDocuments": 5,
|
|
83
|
+
"journalBatches": 150,
|
|
84
|
+
"operations": 2500,
|
|
85
|
+
"sessions": 8
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
#### `GET /api/stats/sizes`
|
|
90
|
+
Get detailed size breakdown
|
|
91
|
+
|
|
92
|
+
**Response**:
|
|
93
|
+
```json
|
|
94
|
+
{
|
|
95
|
+
"totalDocStateSize": 524288,
|
|
96
|
+
"totalJournalSize": 1048576,
|
|
97
|
+
"avgDocStateSize": 52428,
|
|
98
|
+
"avgJournalSize": 6990,
|
|
99
|
+
"documents": [...]
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## Graph Documents API
|
|
106
|
+
|
|
107
|
+
### List Graph Documents
|
|
108
|
+
|
|
109
|
+
#### `GET /api/graph`
|
|
110
|
+
Get all graph documents
|
|
111
|
+
|
|
112
|
+
**Query Parameters**:
|
|
113
|
+
- `limit` (optional) - Number of documents to return
|
|
114
|
+
- `offset` (optional) - Pagination offset
|
|
115
|
+
|
|
116
|
+
**Response**:
|
|
117
|
+
```json
|
|
118
|
+
{
|
|
119
|
+
"documents": [
|
|
120
|
+
{
|
|
121
|
+
"id": "507f1f77bcf86cd799439011",
|
|
122
|
+
"name": "my-scene",
|
|
123
|
+
"ownerId": "user-123",
|
|
124
|
+
"version": 5,
|
|
125
|
+
"createdAt": "2024-01-01T00:00:00Z",
|
|
126
|
+
"updatedAt": "2024-01-01T12:00:00Z"
|
|
127
|
+
}
|
|
128
|
+
],
|
|
129
|
+
"total": 10
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Get Graph Document
|
|
134
|
+
|
|
135
|
+
#### `GET /api/graph/:id`
|
|
136
|
+
Get graph document state
|
|
137
|
+
|
|
138
|
+
**Response**:
|
|
139
|
+
```json
|
|
140
|
+
{
|
|
141
|
+
"id": "507f1f77bcf86cd799439011",
|
|
142
|
+
"name": "my-scene",
|
|
143
|
+
"currentState": {
|
|
144
|
+
"graph": {
|
|
145
|
+
"nodes": {...},
|
|
146
|
+
"rootKey": "scene-root",
|
|
147
|
+
"lww": {},
|
|
148
|
+
"tombstones": {}
|
|
149
|
+
},
|
|
150
|
+
"vectorClock": {...},
|
|
151
|
+
"lamportTime": 100
|
|
152
|
+
},
|
|
153
|
+
"version": 5
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### Get Graph Journal
|
|
158
|
+
|
|
159
|
+
#### `GET /api/graph/:id/journal`
|
|
160
|
+
Get operation journal (write-ahead log)
|
|
161
|
+
|
|
162
|
+
**Query Parameters**:
|
|
163
|
+
- `limit` (optional) - Number of batches to return
|
|
164
|
+
- `offset` (optional) - Pagination offset
|
|
165
|
+
|
|
166
|
+
**Response**:
|
|
167
|
+
```json
|
|
168
|
+
{
|
|
169
|
+
"batches": [
|
|
170
|
+
{
|
|
171
|
+
"id": "507f...",
|
|
172
|
+
"batchId": "batch-uuid",
|
|
173
|
+
"operations": [...],
|
|
174
|
+
"vectorClock": {...},
|
|
175
|
+
"lamportTime": 100,
|
|
176
|
+
"persistedAt": "2024-01-01T00:00:00Z"
|
|
177
|
+
}
|
|
178
|
+
],
|
|
179
|
+
"total": 50
|
|
180
|
+
}
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### Get Graph Operations
|
|
184
|
+
|
|
185
|
+
#### `GET /api/graph/:id/operations`
|
|
186
|
+
Get all operations (expanded from journal batches)
|
|
187
|
+
|
|
188
|
+
**Query Parameters**:
|
|
189
|
+
- `limit` (optional) - Number of operations to return
|
|
190
|
+
- `offset` (optional) - Pagination offset
|
|
191
|
+
|
|
192
|
+
**Response**:
|
|
193
|
+
```json
|
|
194
|
+
{
|
|
195
|
+
"operations": [
|
|
196
|
+
{
|
|
197
|
+
"id": "507f...",
|
|
198
|
+
"opId": "op-uuid",
|
|
199
|
+
"type": "node.insert",
|
|
200
|
+
"data": {...},
|
|
201
|
+
"vectorClock": {...},
|
|
202
|
+
"lamportTime": 100,
|
|
203
|
+
"createdAt": "2024-01-01T00:00:00Z"
|
|
204
|
+
}
|
|
205
|
+
],
|
|
206
|
+
"total": 500
|
|
207
|
+
}
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
### Get Graph Sessions
|
|
211
|
+
|
|
212
|
+
#### `GET /api/graph/:id/sessions`
|
|
213
|
+
Get active and recent sessions
|
|
214
|
+
|
|
215
|
+
**Response**:
|
|
216
|
+
```json
|
|
217
|
+
{
|
|
218
|
+
"sessions": [
|
|
219
|
+
{
|
|
220
|
+
"id": "507f...",
|
|
221
|
+
"userId": "user-123",
|
|
222
|
+
"clientId": "client-abc",
|
|
223
|
+
"connected": true,
|
|
224
|
+
"connectedAt": "2024-01-01T00:00:00Z",
|
|
225
|
+
"lastSeenAt": "2024-01-01T12:00:00Z"
|
|
226
|
+
}
|
|
227
|
+
]
|
|
228
|
+
}
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
### Compact Graph Journal
|
|
232
|
+
|
|
233
|
+
#### `POST /api/graph/:id/compact`
|
|
234
|
+
Trigger journal compaction
|
|
235
|
+
|
|
236
|
+
Folds acknowledged journal entries into snapshot and removes them from the journal.
|
|
237
|
+
|
|
238
|
+
**Response**:
|
|
239
|
+
```json
|
|
240
|
+
{
|
|
241
|
+
"message": "Compaction triggered",
|
|
242
|
+
"before": { "journal": 150, "snapshot": 50 },
|
|
243
|
+
"after": { "journal": 0, "snapshot": 200 }
|
|
244
|
+
}
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
### Coalesce Graph Operations
|
|
248
|
+
|
|
249
|
+
#### `POST /api/graph/:id/coalesce`
|
|
250
|
+
Trigger operation coalescing
|
|
251
|
+
|
|
252
|
+
Combines redundant operations (e.g., multiple updates to the same property).
|
|
253
|
+
|
|
254
|
+
**Response**:
|
|
255
|
+
```json
|
|
256
|
+
{
|
|
257
|
+
"message": "Coalescing triggered",
|
|
258
|
+
"coalescedOps": 50,
|
|
259
|
+
"resultingOps": 25
|
|
260
|
+
}
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
### Delete Graph Document
|
|
264
|
+
|
|
265
|
+
#### `DELETE /api/graph/:id`
|
|
266
|
+
Permanently delete a graph document
|
|
267
|
+
|
|
268
|
+
**Response**:
|
|
269
|
+
```json
|
|
270
|
+
{
|
|
271
|
+
"message": "Document deleted",
|
|
272
|
+
"id": "507f1f77bcf86cd799439011"
|
|
273
|
+
}
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
---
|
|
277
|
+
|
|
278
|
+
## Text Documents API
|
|
279
|
+
|
|
280
|
+
### List Text Documents
|
|
281
|
+
|
|
282
|
+
#### `GET /api/text`
|
|
283
|
+
Get all text documents
|
|
284
|
+
|
|
285
|
+
**Query Parameters**:
|
|
286
|
+
- `limit` (optional) - Number of documents to return
|
|
287
|
+
- `offset` (optional) - Pagination offset
|
|
288
|
+
|
|
289
|
+
**Response**:
|
|
290
|
+
```json
|
|
291
|
+
{
|
|
292
|
+
"documents": [
|
|
293
|
+
{
|
|
294
|
+
"id": "507f1f77bcf86cd799439011",
|
|
295
|
+
"name": "my-doc",
|
|
296
|
+
"ownerId": "user-123",
|
|
297
|
+
"version": 3,
|
|
298
|
+
"createdAt": "2024-01-01T00:00:00Z",
|
|
299
|
+
"updatedAt": "2024-01-01T12:00:00Z"
|
|
300
|
+
}
|
|
301
|
+
],
|
|
302
|
+
"total": 5
|
|
303
|
+
}
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
### Get Text Document
|
|
307
|
+
|
|
308
|
+
#### `GET /api/text/:id`
|
|
309
|
+
Get text document content
|
|
310
|
+
|
|
311
|
+
**Response**:
|
|
312
|
+
```json
|
|
313
|
+
{
|
|
314
|
+
"id": "507f1f77bcf86cd799439011",
|
|
315
|
+
"name": "my-doc",
|
|
316
|
+
"currentText": "Hello, world!",
|
|
317
|
+
"version": 3
|
|
318
|
+
}
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
### Get Text Journal
|
|
322
|
+
|
|
323
|
+
#### `GET /api/text/:id/journal`
|
|
324
|
+
Get operation journal for text document
|
|
325
|
+
|
|
326
|
+
**Query Parameters**:
|
|
327
|
+
- `limit` (optional) - Number of batches to return
|
|
328
|
+
- `offset` (optional) - Pagination offset
|
|
329
|
+
|
|
330
|
+
**Response**:
|
|
331
|
+
```json
|
|
332
|
+
{
|
|
333
|
+
"batches": [
|
|
334
|
+
{
|
|
335
|
+
"id": "507f...",
|
|
336
|
+
"batchId": "batch-uuid",
|
|
337
|
+
"operations": [
|
|
338
|
+
{
|
|
339
|
+
"otype": "text.insert",
|
|
340
|
+
"position": 0,
|
|
341
|
+
"value": "Hello",
|
|
342
|
+
"id": "session-1-5",
|
|
343
|
+
"content": "Hello",
|
|
344
|
+
"seq": 1
|
|
345
|
+
}
|
|
346
|
+
],
|
|
347
|
+
"vectorClock": { "session-1": 5 },
|
|
348
|
+
"lamportTime": 5,
|
|
349
|
+
"persistedAt": "2024-01-01T00:00:00Z"
|
|
350
|
+
}
|
|
351
|
+
],
|
|
352
|
+
"total": 20
|
|
353
|
+
}
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
### Get Text Operations
|
|
357
|
+
|
|
358
|
+
#### `GET /api/text/:id/operations`
|
|
359
|
+
Get all text operations
|
|
360
|
+
|
|
361
|
+
**Query Parameters**:
|
|
362
|
+
- `limit` (optional) - Number of operations to return
|
|
363
|
+
- `offset` (optional) - Pagination offset
|
|
364
|
+
|
|
365
|
+
**Response**:
|
|
366
|
+
```json
|
|
367
|
+
{
|
|
368
|
+
"operations": [
|
|
369
|
+
{
|
|
370
|
+
"id": "507f...",
|
|
371
|
+
"opId": "op-uuid",
|
|
372
|
+
"type": "text.insert",
|
|
373
|
+
"data": {
|
|
374
|
+
"position": 0,
|
|
375
|
+
"value": "Hello",
|
|
376
|
+
"id": "session-1-5",
|
|
377
|
+
"content": "Hello",
|
|
378
|
+
"seq": 1
|
|
379
|
+
},
|
|
380
|
+
"vectorClock": { "session-1": 5 },
|
|
381
|
+
"lamportTime": 5,
|
|
382
|
+
"createdAt": "2024-01-01T00:00:00Z"
|
|
383
|
+
}
|
|
384
|
+
],
|
|
385
|
+
"total": 100
|
|
386
|
+
}
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
### Get Text Sessions
|
|
390
|
+
|
|
391
|
+
#### `GET /api/text/:id/sessions`
|
|
392
|
+
Get active and recent sessions for text document
|
|
393
|
+
|
|
394
|
+
**Response**:
|
|
395
|
+
```json
|
|
396
|
+
{
|
|
397
|
+
"sessions": [
|
|
398
|
+
{
|
|
399
|
+
"id": "507f...",
|
|
400
|
+
"userId": "user-123",
|
|
401
|
+
"clientId": "client-abc",
|
|
402
|
+
"connected": true,
|
|
403
|
+
"connectedAt": "2024-01-01T00:00:00Z",
|
|
404
|
+
"lastSeenAt": "2024-01-01T12:00:00Z",
|
|
405
|
+
"presence": {
|
|
406
|
+
"cursor": { "position": 10 },
|
|
407
|
+
"selection": { "start": 5, "end": 15 }
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
]
|
|
411
|
+
}
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
### Compact Text Journal
|
|
415
|
+
|
|
416
|
+
#### `POST /api/text/:id/compact`
|
|
417
|
+
Trigger journal compaction for text document
|
|
418
|
+
|
|
419
|
+
**Response**:
|
|
420
|
+
```json
|
|
421
|
+
{
|
|
422
|
+
"message": "Compaction triggered",
|
|
423
|
+
"before": { "journal": 50, "text": "..." },
|
|
424
|
+
"after": { "journal": 0, "text": "..." }
|
|
425
|
+
}
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
### Coalesce Text Operations
|
|
429
|
+
|
|
430
|
+
#### `POST /api/text/:id/coalesce`
|
|
431
|
+
Trigger operation coalescing for text document
|
|
432
|
+
|
|
433
|
+
**Response**:
|
|
434
|
+
```json
|
|
435
|
+
{
|
|
436
|
+
"message": "Coalescing triggered",
|
|
437
|
+
"coalescedOps": 20,
|
|
438
|
+
"resultingOps": 10
|
|
439
|
+
}
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
### Delete Text Document
|
|
443
|
+
|
|
444
|
+
#### `DELETE /api/text/:id`
|
|
445
|
+
Permanently delete a text document
|
|
446
|
+
|
|
447
|
+
**Response**:
|
|
448
|
+
```json
|
|
449
|
+
{
|
|
450
|
+
"message": "Text document deleted",
|
|
451
|
+
"id": "507f1f77bcf86cd799439011"
|
|
452
|
+
}
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
---
|
|
456
|
+
|
|
457
|
+
## Legacy Endpoints (Deprecated)
|
|
458
|
+
|
|
459
|
+
For backwards compatibility, the following endpoints redirect to `/api/graph/`:
|
|
460
|
+
|
|
461
|
+
- `GET /api/documents` → `GET /api/graph`
|
|
462
|
+
- `GET /api/documents/:id` → `GET /api/graph/:id`
|
|
463
|
+
- `GET /api/rooms/:roomId/state` → `GET /api/graph/:id/state`
|
|
464
|
+
- `DELETE /api/rooms/:roomId` → `DELETE /api/graph/:id`
|
|
465
|
+
|
|
466
|
+
**Note**: These will be removed in a future version. Please migrate to the new endpoints.
|
|
467
|
+
|
|
468
|
+
---
|
|
469
|
+
|
|
470
|
+
## Error Responses
|
|
471
|
+
|
|
472
|
+
All endpoints return errors in the following format:
|
|
473
|
+
|
|
474
|
+
```json
|
|
475
|
+
{
|
|
476
|
+
"error": "Error message",
|
|
477
|
+
"code": "ERROR_CODE",
|
|
478
|
+
"details": {...}
|
|
479
|
+
}
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
**Common Error Codes**:
|
|
483
|
+
- `404` - Document not found
|
|
484
|
+
- `400` - Invalid request
|
|
485
|
+
- `500` - Internal server error
|
|
486
|
+
- `403` - Forbidden (authorization failed)
|
|
487
|
+
|
|
488
|
+
---
|
|
489
|
+
|
|
490
|
+
## CRDT Concepts
|
|
491
|
+
|
|
492
|
+
### Vector Clock
|
|
493
|
+
Causal ordering mechanism - tracks the logical time for each session.
|
|
494
|
+
|
|
495
|
+
```json
|
|
496
|
+
{
|
|
497
|
+
"session-1": 5,
|
|
498
|
+
"session-2": 3,
|
|
499
|
+
"session-3": 7
|
|
500
|
+
}
|
|
501
|
+
```
|
|
502
|
+
|
|
503
|
+
### Lamport Time
|
|
504
|
+
Global logical clock for deterministic tie-breaking.
|
|
505
|
+
|
|
506
|
+
### Journal
|
|
507
|
+
Write-ahead log of all operations. Provides:
|
|
508
|
+
- Reliability (operations persisted before acknowledgment)
|
|
509
|
+
- Undo/redo support
|
|
510
|
+
- Audit trail
|
|
511
|
+
- Efficient sync (send only new operations)
|
|
512
|
+
|
|
513
|
+
### Compaction
|
|
514
|
+
Folding acknowledged operations into snapshot to reduce journal size.
|
|
515
|
+
|
|
516
|
+
---
|
|
517
|
+
|
|
518
|
+
## Rate Limiting
|
|
519
|
+
|
|
520
|
+
- WebSocket connections: 100 per IP
|
|
521
|
+
- REST API: 1000 requests/minute per IP
|
|
522
|
+
- Journal compaction: Max 1 per document per minute
|
|
523
|
+
|
|
524
|
+
---
|
|
525
|
+
|
|
526
|
+
## Examples
|
|
527
|
+
|
|
528
|
+
### Connecting to a Graph Document
|
|
529
|
+
|
|
530
|
+
```javascript
|
|
531
|
+
const ws = new WebSocket('ws://localhost:8080/ws/graph/my-room?sessionId=client-123');
|
|
532
|
+
|
|
533
|
+
ws.onmessage = (event) => {
|
|
534
|
+
const msg = JSON.parse(event.data);
|
|
535
|
+
|
|
536
|
+
if (msg.type === 'init') {
|
|
537
|
+
console.log('Initial state:', msg.snapshot);
|
|
538
|
+
console.log('Journal:', msg.journal);
|
|
539
|
+
} else if (msg.type === 'message') {
|
|
540
|
+
console.log('Operations:', msg.ops);
|
|
541
|
+
}
|
|
542
|
+
};
|
|
543
|
+
|
|
544
|
+
// Send an operation
|
|
545
|
+
ws.send(JSON.stringify({
|
|
546
|
+
ops: [{
|
|
547
|
+
otype: 'node.insert',
|
|
548
|
+
key: 'root',
|
|
549
|
+
path: 'children',
|
|
550
|
+
value: { key: 'cube-1', tag: 'Box' }
|
|
551
|
+
}],
|
|
552
|
+
clock: { 'client-123': 1 },
|
|
553
|
+
lamportTime: 1
|
|
554
|
+
}));
|
|
555
|
+
```
|
|
556
|
+
|
|
557
|
+
### Connecting to a Text Document
|
|
558
|
+
|
|
559
|
+
```javascript
|
|
560
|
+
const ws = new WebSocket('ws://localhost:8080/ws/text/my-doc?sessionId=client-456');
|
|
561
|
+
|
|
562
|
+
ws.onmessage = (event) => {
|
|
563
|
+
const msg = JSON.parse(event.data);
|
|
564
|
+
|
|
565
|
+
if (msg.type === 'init') {
|
|
566
|
+
console.log('Initial text:', msg.snapshot.text);
|
|
567
|
+
} else if (msg.type === 'message') {
|
|
568
|
+
console.log('Text operations:', msg.ops);
|
|
569
|
+
}
|
|
570
|
+
};
|
|
571
|
+
|
|
572
|
+
// Send a text insert
|
|
573
|
+
ws.send(JSON.stringify({
|
|
574
|
+
ops: [{
|
|
575
|
+
otype: 'text.insert',
|
|
576
|
+
position: 0,
|
|
577
|
+
value: 'Hello, world!'
|
|
578
|
+
}],
|
|
579
|
+
clock: { 'client-456': 1 },
|
|
580
|
+
lamportTime: 1
|
|
581
|
+
}));
|
|
582
|
+
```
|
|
583
|
+
|
|
584
|
+
---
|
|
585
|
+
|
|
586
|
+
## Client Libraries
|
|
587
|
+
|
|
588
|
+
### JavaScript/TypeScript
|
|
589
|
+
|
|
590
|
+
```bash
|
|
591
|
+
npm install @vuer-ai/vuer-rtc
|
|
592
|
+
```
|
|
593
|
+
|
|
594
|
+
**Graph Documents**:
|
|
595
|
+
```typescript
|
|
596
|
+
import { createGraph } from '@vuer-ai/vuer-rtc/client';
|
|
597
|
+
|
|
598
|
+
const graph = createGraph({
|
|
599
|
+
sessionId: 'my-session',
|
|
600
|
+
onSend: (msg) => websocket.send(msg)
|
|
601
|
+
});
|
|
602
|
+
|
|
603
|
+
graph.onEdit({
|
|
604
|
+
key: 'root',
|
|
605
|
+
otype: 'node.insert',
|
|
606
|
+
path: 'children',
|
|
607
|
+
value: { key: 'cube-1', tag: 'Box' }
|
|
608
|
+
});
|
|
609
|
+
|
|
610
|
+
graph.commit();
|
|
611
|
+
```
|
|
612
|
+
|
|
613
|
+
**Text Documents**:
|
|
614
|
+
```typescript
|
|
615
|
+
import { createTextDocument } from '@vuer-ai/vuer-rtc/client';
|
|
616
|
+
|
|
617
|
+
const doc = createTextDocument({
|
|
618
|
+
sessionId: 'my-session',
|
|
619
|
+
onSend: (msg) => websocket.send(msg)
|
|
620
|
+
});
|
|
621
|
+
|
|
622
|
+
doc.insert(0, 'Hello, world!');
|
|
623
|
+
doc.commit();
|
|
624
|
+
```
|
|
625
|
+
|
|
626
|
+
### Python
|
|
627
|
+
|
|
628
|
+
```bash
|
|
629
|
+
pip install vuer-rtc
|
|
630
|
+
```
|
|
631
|
+
|
|
632
|
+
```python
|
|
633
|
+
from vuer_rtc import GraphClient, TextClient
|
|
634
|
+
|
|
635
|
+
# Graph document
|
|
636
|
+
graph = GraphClient(url='ws://localhost:8080/ws/graph/my-room', session_id='py-client')
|
|
637
|
+
graph.insert_node('root', 'children', {'key': 'cube-1', 'tag': 'Box'})
|
|
638
|
+
|
|
639
|
+
# Text document
|
|
640
|
+
text = TextClient(url='ws://localhost:8080/ws/text/my-doc', session_id='py-client')
|
|
641
|
+
text.insert(0, 'Hello, world!')
|
|
642
|
+
```
|
|
@@ -55,7 +55,7 @@ async function exampleBasicCompression() {
|
|
|
55
55
|
rootKey: 'root',
|
|
56
56
|
},
|
|
57
57
|
vectorClock: { session1: 100, session2: 50 },
|
|
58
|
-
|
|
58
|
+
lt: 100,
|
|
59
59
|
journalIndex: 100,
|
|
60
60
|
};
|
|
61
61
|
|
|
@@ -94,7 +94,7 @@ async function exampleVerifyIntegrity() {
|
|
|
94
94
|
rootKey: 'root',
|
|
95
95
|
},
|
|
96
96
|
vectorClock: { session1: 42 },
|
|
97
|
-
|
|
97
|
+
lt: 42,
|
|
98
98
|
journalIndex: 42,
|
|
99
99
|
};
|
|
100
100
|
|
|
@@ -142,7 +142,7 @@ async function exampleCompressionStats() {
|
|
|
142
142
|
vectorClock: Object.fromEntries(
|
|
143
143
|
Array.from({ length: 10 }, (_, i) => [`session${i}`, i * 10])
|
|
144
144
|
),
|
|
145
|
-
|
|
145
|
+
lt: 100,
|
|
146
146
|
journalIndex: 100,
|
|
147
147
|
};
|
|
148
148
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vuer-ai/vuer-rtc-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Vuer RTC Server — RoomBroker, WebSocket transport, MongoDB persistence",
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"@prisma/client": "^5.22.0",
|
|
42
42
|
"prisma": "^5.22.0",
|
|
43
43
|
"ws": "^8.19.0",
|
|
44
|
-
"@vuer-ai/vuer-rtc": "^0.
|
|
44
|
+
"@vuer-ai/vuer-rtc": "^0.8.6"
|
|
45
45
|
},
|
|
46
46
|
"scripts": {
|
|
47
47
|
"build": "prisma generate && tsc",
|