@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
@@ -0,0 +1,127 @@
1
+ /**
2
+ * Graph Journal Service - Business logic for graph document journal operations
3
+ *
4
+ * Handles:
5
+ * - Receiving and validating CRDTMessages for graph documents
6
+ * - Storing messages in journal
7
+ * - Processing meta.undo/meta.redo operations
8
+ * - Computing current graph state
9
+ * - Providing snapshot + journal for new clients
10
+ */
11
+ import type { PrismaClient } from '@prisma/client';
12
+ import { type CRDTMessage, type SceneGraph, type Snapshot, type VectorClock } from '@vuer-ai/vuer-rtc';
13
+ export interface JournalEntry {
14
+ msg: CRDTMessage;
15
+ deletedAt?: number;
16
+ }
17
+ export interface DocumentState {
18
+ snapshot: Snapshot;
19
+ journal: JournalEntry[];
20
+ }
21
+ export declare class GraphJournalService {
22
+ private journalRepo;
23
+ private documentRepo;
24
+ private validator;
25
+ private documentStates;
26
+ /** Handle returned by setInterval for the compaction loop. */
27
+ private compactionTimer;
28
+ /** Mutex set to prevent concurrent compact + processMessage races. */
29
+ private compactionLocks;
30
+ /** Callback for fetching connected member vector clocks per document. */
31
+ private memberClockProvider;
32
+ constructor(prisma: PrismaClient);
33
+ /**
34
+ * Start the periodic compaction loop.
35
+ * Should be called once after the service is created.
36
+ */
37
+ startCompactionLoop(): void;
38
+ /**
39
+ * Stop the periodic compaction loop (for graceful shutdown).
40
+ */
41
+ stopCompactionLoop(): void;
42
+ /**
43
+ * Store a callback for fetching connected member vector clocks per document.
44
+ * Used by the compaction loop to compute safe watermarks.
45
+ */
46
+ setMemberClockProvider(fn: (docId: string) => Promise<VectorClock[]>): void;
47
+ /**
48
+ * Compute safe compaction point: the component-wise minimum of all
49
+ * connected clients' vector clocks. Entries whose vector clock is
50
+ * dominated by this watermark have been seen by all clients and can
51
+ * be safely folded into the snapshot.
52
+ *
53
+ * If memberClocks is empty, returns {} (no safe compaction point).
54
+ */
55
+ computeCompactionWatermark(memberClocks: VectorClock[]): VectorClock;
56
+ /**
57
+ * Check if a message's vector clock is dominated by the watermark.
58
+ * A message is dominated if for every session S in the message's clock,
59
+ * watermark[S] >= clock[S].
60
+ */
61
+ isDominatedByWatermark(msgClock: VectorClock, watermark: VectorClock): boolean;
62
+ /**
63
+ * Iterate all in-memory document states and compact those whose
64
+ * journal exceeds COMPACTION_THRESHOLD_ENTRIES.
65
+ * Uses the member clock provider (if set) to compute a safe watermark
66
+ * so we only compact entries all connected clients have seen.
67
+ */
68
+ private compactAllLoadedDocuments;
69
+ /**
70
+ * Load document state from database.
71
+ *
72
+ * Uses getSince() with the snapshot's lamportTime so we only load
73
+ * post-snapshot journal entries from the DB — not the entire history.
74
+ */
75
+ loadDocument(documentId: string): Promise<DocumentState | null>;
76
+ /**
77
+ * Process incoming message from client
78
+ *
79
+ * @returns Acknowledgement with processed message, or error
80
+ */
81
+ processMessage(documentId: string, msg: CRDTMessage): Promise<{
82
+ success: boolean;
83
+ error?: string;
84
+ }>;
85
+ /**
86
+ * Get current graph state by replaying journal
87
+ */
88
+ computeGraph(state: DocumentState): SceneGraph;
89
+ /**
90
+ * Get state for new client (snapshot + only post-snapshot journal entries).
91
+ *
92
+ * After compaction, we need to filter journal entries that are already
93
+ * baked into the snapshot. We use vector clock comparison (not lamportTime)
94
+ * to correctly handle out-of-order or delayed messages.
95
+ *
96
+ * A message is included if ANY component of its vector clock is greater
97
+ * than the corresponding component in the snapshot's vector clock.
98
+ * This matches the client-side filtering logic in initFromServer().
99
+ */
100
+ getStateForClient(documentId: string): Promise<{
101
+ snapshot: Snapshot;
102
+ journal: CRDTMessage[];
103
+ } | null>;
104
+ /**
105
+ * Compact journal (create new snapshot from acknowledged entries).
106
+ * Guarded by a per-document mutex to prevent races with processMessage.
107
+ *
108
+ * When watermark is provided, only compact entries whose vector clock
109
+ * is dominated by the watermark (i.e. all connected clients have seen them).
110
+ * When no watermark is provided (no connected clients), compact everything.
111
+ */
112
+ compact(documentId: string, watermark?: VectorClock): Promise<void>;
113
+ /**
114
+ * Create a new document
115
+ */
116
+ createDocument(name: string, ownerId: string): Promise<string>;
117
+ /**
118
+ * Unload document from memory
119
+ */
120
+ unloadDocument(documentId: string): void;
121
+ /**
122
+ * Clear all journal data for a document, resetting it to empty state.
123
+ * The document record is kept (unlike delete). Used for room-reset.
124
+ */
125
+ clearDocument(documentId: string): Promise<void>;
126
+ }
127
+ //# sourceMappingURL=GraphJournalService.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"GraphJournalService.d.ts","sourceRoot":"","sources":["../../src/journal/GraphJournalService.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAY,MAAM,gBAAgB,CAAC;AAC7D,OAAO,EACL,KAAK,WAAW,EAChB,KAAK,UAAU,EAEf,KAAK,QAAQ,EACb,KAAK,WAAW,EAMjB,MAAM,mBAAmB,CAAC;AA8B3B,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,WAAW,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,QAAQ,CAAC;IACnB,OAAO,EAAE,YAAY,EAAE,CAAC;CACzB;AA8DD,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,WAAW,CAAoB;IACvC,OAAO,CAAC,YAAY,CAAqB;IACzC,OAAO,CAAC,SAAS,CAAqB;IAGtC,OAAO,CAAC,cAAc,CAAyC;IAE/D,8DAA8D;IAC9D,OAAO,CAAC,eAAe,CAA+C;IAEtE,sEAAsE;IACtE,OAAO,CAAC,eAAe,CAAqB;IAE5C,yEAAyE;IACzE,OAAO,CAAC,mBAAmB,CAA4D;gBAE3E,MAAM,EAAE,YAAY;IAQhC;;;OAGG;IACH,mBAAmB,IAAI,IAAI;IAS3B;;OAEG;IACH,kBAAkB,IAAI,IAAI;IAO1B;;;OAGG;IACH,sBAAsB,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,WAAW,EAAE,CAAC,GAAG,IAAI;IAI3E;;;;;;;OAOG;IACH,0BAA0B,CAAC,YAAY,EAAE,WAAW,EAAE,GAAG,WAAW;IAoBpE;;;;OAIG;IACH,sBAAsB,CAAC,QAAQ,EAAE,WAAW,EAAE,SAAS,EAAE,WAAW,GAAG,OAAO;IAS9E;;;;;OAKG;YACW,yBAAyB;IAsBvC;;;;;OAKG;IACG,YAAY,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC;IA8BrE;;;;OAIG;IACG,cAAc,CAClB,UAAU,EAAE,MAAM,EAClB,GAAG,EAAE,WAAW,GACf,OAAO,CAAC;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAqDhD;;OAEG;IACH,YAAY,CAAC,KAAK,EAAE,aAAa,GAAG,UAAU;IAkB9C;;;;;;;;;;OAUG;IACG,iBAAiB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC;QACnD,QAAQ,EAAE,QAAQ,CAAC;QACnB,OAAO,EAAE,WAAW,EAAE,CAAC;KACxB,GAAG,IAAI,CAAC;IA4BT;;;;;;;OAOG;IACG,OAAO,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAoHzE;;OAEG;IACG,cAAc,CAClB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,MAAM,CAAC;IAiBlB;;OAEG;IACH,cAAc,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;IAIxC;;;OAGG;IACG,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAqBvD"}
@@ -0,0 +1,491 @@
1
+ /**
2
+ * Graph Journal Service - Business logic for graph document journal operations
3
+ *
4
+ * Handles:
5
+ * - Receiving and validating CRDTMessages for graph documents
6
+ * - Storing messages in journal
7
+ * - Processing meta.undo/meta.redo operations
8
+ * - Computing current graph state
9
+ * - Providing snapshot + journal for new clients
10
+ */
11
+ import { applyMessage, createEmptyGraph, OperationValidator, TextRope, compactRope, } from '@vuer-ai/vuer-rtc';
12
+ import { JournalRepository } from './JournalRepository.js';
13
+ import { DocumentRepository } from '../persistence/DocumentRepository.js';
14
+ /**
15
+ * Safely serialize an object, handling circular references by removing them.
16
+ * Also strips 'parent' references which cause cycles in tree structures.
17
+ * TextRope instances are automatically serialized to strings via toJSON().
18
+ */
19
+ function safeSerialize(obj) {
20
+ const seen = new WeakSet();
21
+ return JSON.parse(JSON.stringify(obj, (key, value) => {
22
+ // Skip parent references which cause cycles
23
+ if (key === 'parent')
24
+ return undefined;
25
+ if (typeof value === 'object' && value !== null) {
26
+ if (seen.has(value))
27
+ return undefined; // Circular reference
28
+ seen.add(value);
29
+ }
30
+ return value;
31
+ }));
32
+ }
33
+ /** How often the compaction loop runs (ms). */
34
+ const COMPACTION_INTERVAL_MS = 30_000;
35
+ /** Number of journal entries that triggers compaction for a document. */
36
+ const COMPACTION_THRESHOLD_ENTRIES = 500;
37
+ /**
38
+ * Safely parse a Document.currentState (Json) into a Snapshot,
39
+ * providing defaults for any missing fields.
40
+ * Text properties are stored as plain strings and lazy-upgraded to TextRope on edit.
41
+ */
42
+ function parseSnapshot(currentState) {
43
+ const raw = (currentState ?? {});
44
+ const graph = raw.graph || createEmptyGraph();
45
+ return {
46
+ graph,
47
+ vectorClock: raw.vectorClock || {},
48
+ lt: (typeof raw.lt === 'number' ? raw.lt : 0),
49
+ journalIndex: (typeof raw.journalIndex === 'number' ? raw.journalIndex : 0),
50
+ };
51
+ }
52
+ /**
53
+ * Compact all TextRope instances in a SceneGraph by stripping tombstones
54
+ * and merging adjacent spans from the same agent. Returns a new graph
55
+ * with compacted ropes (the original is not mutated).
56
+ *
57
+ * This is critical for preventing B-tree depth explosion from single-char inserts.
58
+ */
59
+ function compactTextRopes(graph) {
60
+ const nodes = {};
61
+ let anyChanged = false;
62
+ for (const key of Object.keys(graph.nodes)) {
63
+ const node = graph.nodes[key];
64
+ let nodeChanged = false;
65
+ let cloned = null;
66
+ for (const prop of Object.keys(node)) {
67
+ if (node[prop] instanceof TextRope) {
68
+ if (!cloned) {
69
+ cloned = {
70
+ ...node,
71
+ children: [...(node.children ?? [])],
72
+ };
73
+ }
74
+ cloned[prop] = compactRope(node[prop]);
75
+ nodeChanged = true;
76
+ }
77
+ }
78
+ if (nodeChanged && cloned) {
79
+ nodes[key] = cloned;
80
+ anyChanged = true;
81
+ }
82
+ else {
83
+ nodes[key] = node;
84
+ }
85
+ }
86
+ if (!anyChanged)
87
+ return graph;
88
+ return { ...graph, nodes };
89
+ }
90
+ export class GraphJournalService {
91
+ journalRepo;
92
+ documentRepo;
93
+ validator;
94
+ // In-memory state per document (for fast access)
95
+ documentStates = new Map();
96
+ /** Handle returned by setInterval for the compaction loop. */
97
+ compactionTimer = null;
98
+ /** Mutex set to prevent concurrent compact + processMessage races. */
99
+ compactionLocks = new Set();
100
+ /** Callback for fetching connected member vector clocks per document. */
101
+ memberClockProvider = null;
102
+ constructor(prisma) {
103
+ this.journalRepo = new JournalRepository(prisma);
104
+ this.documentRepo = new DocumentRepository(prisma);
105
+ this.validator = new OperationValidator();
106
+ }
107
+ // ── Compaction loop ──────────────────────────────────────────────
108
+ /**
109
+ * Start the periodic compaction loop.
110
+ * Should be called once after the service is created.
111
+ */
112
+ startCompactionLoop() {
113
+ if (this.compactionTimer)
114
+ return; // already running
115
+ this.compactionTimer = setInterval(() => {
116
+ this.compactAllLoadedDocuments().catch((err) => console.error('[compaction-loop]', err));
117
+ }, COMPACTION_INTERVAL_MS);
118
+ }
119
+ /**
120
+ * Stop the periodic compaction loop (for graceful shutdown).
121
+ */
122
+ stopCompactionLoop() {
123
+ if (this.compactionTimer) {
124
+ clearInterval(this.compactionTimer);
125
+ this.compactionTimer = null;
126
+ }
127
+ }
128
+ /**
129
+ * Store a callback for fetching connected member vector clocks per document.
130
+ * Used by the compaction loop to compute safe watermarks.
131
+ */
132
+ setMemberClockProvider(fn) {
133
+ this.memberClockProvider = fn;
134
+ }
135
+ /**
136
+ * Compute safe compaction point: the component-wise minimum of all
137
+ * connected clients' vector clocks. Entries whose vector clock is
138
+ * dominated by this watermark have been seen by all clients and can
139
+ * be safely folded into the snapshot.
140
+ *
141
+ * If memberClocks is empty, returns {} (no safe compaction point).
142
+ */
143
+ computeCompactionWatermark(memberClocks) {
144
+ if (memberClocks.length === 0)
145
+ return {};
146
+ // Start with the first clock, then take component-wise minimums
147
+ const result = { ...memberClocks[0] };
148
+ for (let i = 1; i < memberClocks.length; i++) {
149
+ const clock = memberClocks[i];
150
+ // For each session in result, take min with this clock
151
+ for (const client of Object.keys(result)) {
152
+ result[client] = Math.min(result[client], clock[client] ?? 0);
153
+ }
154
+ // Sessions not in result but in clock are implicitly 0 in result,
155
+ // so they stay at 0 (already the minimum) — no action needed.
156
+ }
157
+ return result;
158
+ }
159
+ /**
160
+ * Check if a message's vector clock is dominated by the watermark.
161
+ * A message is dominated if for every session S in the message's clock,
162
+ * watermark[S] >= clock[S].
163
+ */
164
+ isDominatedByWatermark(msgClock, watermark) {
165
+ for (const [client, time] of Object.entries(msgClock)) {
166
+ if ((watermark[client] ?? 0) < time) {
167
+ return false;
168
+ }
169
+ }
170
+ return true;
171
+ }
172
+ /**
173
+ * Iterate all in-memory document states and compact those whose
174
+ * journal exceeds COMPACTION_THRESHOLD_ENTRIES.
175
+ * Uses the member clock provider (if set) to compute a safe watermark
176
+ * so we only compact entries all connected clients have seen.
177
+ */
178
+ async compactAllLoadedDocuments() {
179
+ for (const [documentId, state] of this.documentStates) {
180
+ if (state.journal.length >= COMPACTION_THRESHOLD_ENTRIES) {
181
+ try {
182
+ let watermark;
183
+ if (this.memberClockProvider) {
184
+ const clocks = await this.memberClockProvider(documentId);
185
+ if (clocks.length > 0) {
186
+ watermark = this.computeCompactionWatermark(clocks);
187
+ }
188
+ // If no connected clients, watermark stays undefined -> compact all
189
+ }
190
+ await this.compact(documentId, watermark);
191
+ }
192
+ catch (err) {
193
+ console.error(`[compaction] failed for ${documentId}:`, err);
194
+ }
195
+ }
196
+ }
197
+ }
198
+ // ── Document loading ─────────────────────────────────────────────
199
+ /**
200
+ * Load document state from database.
201
+ *
202
+ * Uses getSince() with the snapshot's lamportTime so we only load
203
+ * post-snapshot journal entries from the DB — not the entire history.
204
+ */
205
+ async loadDocument(documentId) {
206
+ // Check in-memory cache first
207
+ if (this.documentStates.has(documentId)) {
208
+ return this.documentStates.get(documentId);
209
+ }
210
+ // Load from database
211
+ const doc = await this.documentRepo.findById(documentId);
212
+ if (!doc)
213
+ return null;
214
+ // Parse snapshot from currentState, with safe defaults
215
+ const snapshot = parseSnapshot(doc.currentState);
216
+ // Only load journal entries AFTER the snapshot's lamport time.
217
+ // getSince() now returns PersistedJournalEntry with deletedAt from DB.
218
+ const persistedEntries = await this.journalRepo.getSince(documentId, snapshot.lt);
219
+ const journal = persistedEntries.map((entry) => ({
220
+ msg: entry.msg,
221
+ deletedAt: entry.deletedAt,
222
+ }));
223
+ const state = { snapshot, journal };
224
+ this.documentStates.set(documentId, state);
225
+ return state;
226
+ }
227
+ /**
228
+ * Process incoming message from client
229
+ *
230
+ * @returns Acknowledgement with processed message, or error
231
+ */
232
+ async processMessage(documentId, msg) {
233
+ // Validate message
234
+ const validation = this.validator.validateMessage(msg);
235
+ if (!validation.valid) {
236
+ return { success: false, error: validation.errors?.join(', ') };
237
+ }
238
+ // Load state
239
+ const state = await this.loadDocument(documentId);
240
+ if (!state) {
241
+ return { success: false, error: 'Document not found' };
242
+ }
243
+ // Check for duplicate
244
+ const exists = await this.journalRepo.exists(documentId, msg.id);
245
+ if (exists) {
246
+ return { success: true }; // Already processed, idempotent success
247
+ }
248
+ // Process meta operations (undo/redo)
249
+ for (const op of msg.ops) {
250
+ if (op.ot === 'meta.undo') {
251
+ const targetId = op.targetMsgId;
252
+ const target = state.journal.find((e) => e.msg.id === targetId);
253
+ if (target) {
254
+ target.deletedAt = msg.ts;
255
+ // Persist deletedAt to DB (ts is seconds, DB stores as Date in ms)
256
+ await this.journalRepo.updateDeletedAt(documentId, targetId, new Date(msg.ts * 1000));
257
+ }
258
+ }
259
+ else if (op.ot === 'meta.redo') {
260
+ const targetId = op.targetMsgId;
261
+ const target = state.journal.find((e) => e.msg.id === targetId);
262
+ if (target) {
263
+ delete target.deletedAt;
264
+ // Clear deletedAt in DB
265
+ await this.journalRepo.updateDeletedAt(documentId, targetId, null);
266
+ }
267
+ }
268
+ }
269
+ // Add to journal
270
+ state.journal.push({ msg });
271
+ // Persist to database
272
+ await this.journalRepo.append(documentId, msg);
273
+ return { success: true };
274
+ }
275
+ /**
276
+ * Get current graph state by replaying journal
277
+ */
278
+ computeGraph(state) {
279
+ let graph = state.snapshot.graph;
280
+ for (const entry of state.journal) {
281
+ if (entry.deletedAt)
282
+ continue;
283
+ // Filter out meta ops for graph computation
284
+ const realOps = entry.msg.ops.filter((op) => !op.ot.startsWith('meta.'));
285
+ if (realOps.length > 0) {
286
+ graph = applyMessage(graph, { ...entry.msg, ops: realOps });
287
+ }
288
+ }
289
+ return graph;
290
+ }
291
+ /**
292
+ * Get state for new client (snapshot + only post-snapshot journal entries).
293
+ *
294
+ * After compaction, we need to filter journal entries that are already
295
+ * baked into the snapshot. We use vector clock comparison (not lamportTime)
296
+ * to correctly handle out-of-order or delayed messages.
297
+ *
298
+ * A message is included if ANY component of its vector clock is greater
299
+ * than the corresponding component in the snapshot's vector clock.
300
+ * This matches the client-side filtering logic in initFromServer().
301
+ */
302
+ async getStateForClient(documentId) {
303
+ const state = await this.loadDocument(documentId);
304
+ if (!state)
305
+ return null;
306
+ // Filter journal entries using vector clock comparison (not lamportTime)
307
+ // to handle out-of-order messages correctly after compaction
308
+ const postSnapshotJournal = state.journal
309
+ .filter((e) => {
310
+ // Include message if ANY client in its clock is ahead of snapshot
311
+ for (const [client, time] of Object.entries(e.msg.clock)) {
312
+ if (time > (state.snapshot.vectorClock[client] ?? 0)) {
313
+ return true;
314
+ }
315
+ }
316
+ return false; // All clock components <= snapshot, already applied
317
+ })
318
+ .map((e) => e.msg);
319
+ // Serialize snapshot before sending to client. TextRope instances (if any)
320
+ // are automatically converted to plain strings via toJSON().
321
+ const serializedSnapshot = safeSerialize(state.snapshot);
322
+ return {
323
+ snapshot: serializedSnapshot,
324
+ journal: postSnapshotJournal,
325
+ };
326
+ }
327
+ /**
328
+ * Compact journal (create new snapshot from acknowledged entries).
329
+ * Guarded by a per-document mutex to prevent races with processMessage.
330
+ *
331
+ * When watermark is provided, only compact entries whose vector clock
332
+ * is dominated by the watermark (i.e. all connected clients have seen them).
333
+ * When no watermark is provided (no connected clients), compact everything.
334
+ */
335
+ async compact(documentId, watermark) {
336
+ // Acquire compaction lock (skip if already compacting)
337
+ if (this.compactionLocks.has(documentId))
338
+ return;
339
+ this.compactionLocks.add(documentId);
340
+ try {
341
+ const state = await this.loadDocument(documentId);
342
+ if (!state || state.journal.length === 0)
343
+ return;
344
+ // Determine compaction boundary
345
+ let compactUpToIndex = -1;
346
+ if (watermark) {
347
+ // Compact only entries dominated by the watermark
348
+ for (let i = 0; i < state.journal.length; i++) {
349
+ if (this.isDominatedByWatermark(state.journal[i].msg.clock, watermark)) {
350
+ compactUpToIndex = i;
351
+ }
352
+ else {
353
+ break; // Journal is ordered; once we hit a non-dominated entry, stop
354
+ }
355
+ }
356
+ }
357
+ else {
358
+ // No watermark (no connected clients): compact everything
359
+ compactUpToIndex = state.journal.length - 1;
360
+ }
361
+ if (compactUpToIndex < 0)
362
+ return; // Nothing to compact
363
+ // Build new snapshot from entries [0..compactUpToIndex]
364
+ let newGraph = state.snapshot.graph;
365
+ let mergedClock = { ...state.snapshot.vectorClock };
366
+ let maxLamport = state.snapshot.lt;
367
+ for (let i = 0; i <= compactUpToIndex; i++) {
368
+ const entry = state.journal[i];
369
+ if (!entry.deletedAt) {
370
+ // Filter out meta ops for graph computation
371
+ const realOps = entry.msg.ops.filter((op) => !op.ot.startsWith('meta.'));
372
+ if (realOps.length > 0) {
373
+ newGraph = applyMessage(newGraph, { ...entry.msg, ops: realOps });
374
+ }
375
+ }
376
+ // Merge clock and lamport regardless of deletedAt
377
+ for (const [client, time] of Object.entries(entry.msg.clock)) {
378
+ mergedClock[client] = Math.max(mergedClock[client] || 0, time);
379
+ }
380
+ maxLamport = Math.max(maxLamport, entry.msg.lt);
381
+ }
382
+ // Collect batchIds of compacted entries before mutating state
383
+ const compactedBatchIds = state.journal
384
+ .slice(0, compactUpToIndex + 1)
385
+ .map((e) => e.msg.id);
386
+ // Compact TextRope instances before creating snapshot
387
+ // This merges single-char items into multi-char spans, preventing B-tree depth explosion
388
+ try {
389
+ newGraph = compactTextRopes(newGraph);
390
+ }
391
+ catch (err) {
392
+ console.error(`[compact] TextRope compaction failed for doc ${documentId}:`, err);
393
+ throw new Error(`TextRope compaction failed: ${err.message}`);
394
+ }
395
+ // Update snapshot
396
+ state.snapshot = {
397
+ graph: newGraph,
398
+ vectorClock: mergedClock,
399
+ lt: maxLamport,
400
+ journalIndex: maxLamport,
401
+ };
402
+ // Remove compacted entries from in-memory journal
403
+ state.journal = state.journal.slice(compactUpToIndex + 1);
404
+ // Persist snapshot (sanitize to break circular refs from parent pointers)
405
+ let sanitizedSnapshot;
406
+ try {
407
+ sanitizedSnapshot = safeSerialize(state.snapshot);
408
+ }
409
+ catch (err) {
410
+ console.error(`[compact] Snapshot serialization failed for doc ${documentId}:`, err);
411
+ throw new Error(`Snapshot serialization failed: ${err.message}`);
412
+ }
413
+ try {
414
+ await this.documentRepo.update(documentId, {
415
+ currentState: sanitizedSnapshot,
416
+ });
417
+ }
418
+ catch (err) {
419
+ console.error(`[compact] Document update failed for doc ${documentId}:`, err);
420
+ // If document was deleted, this is not a fatal error - just clean up journal
421
+ if (err?.code === 'P2025') {
422
+ console.warn(`[compact] Document ${documentId} not found, skipping snapshot update`);
423
+ }
424
+ else {
425
+ throw new Error(`Document update failed: ${err.message}`);
426
+ }
427
+ }
428
+ // Delete compacted entries from DB by their batchIds
429
+ if (compactedBatchIds.length > 0) {
430
+ try {
431
+ const deletedCount = await this.journalRepo.deleteByIds(documentId, compactedBatchIds);
432
+ console.log(`[compact] Deleted ${deletedCount} journal batches for doc ${documentId}`);
433
+ }
434
+ catch (err) {
435
+ console.error(`[compact] Journal batch deletion failed for doc ${documentId}:`, err);
436
+ throw new Error(`Journal batch deletion failed: ${err.message}`);
437
+ }
438
+ }
439
+ }
440
+ finally {
441
+ this.compactionLocks.delete(documentId);
442
+ }
443
+ }
444
+ /**
445
+ * Create a new document
446
+ */
447
+ async createDocument(name, ownerId) {
448
+ const doc = await this.documentRepo.create({ name, ownerId });
449
+ // Initialize in-memory state
450
+ this.documentStates.set(doc.id, {
451
+ snapshot: {
452
+ graph: createEmptyGraph(),
453
+ vectorClock: {},
454
+ lt: 0,
455
+ journalIndex: 0,
456
+ },
457
+ journal: [],
458
+ });
459
+ return doc.id;
460
+ }
461
+ /**
462
+ * Unload document from memory
463
+ */
464
+ unloadDocument(documentId) {
465
+ this.documentStates.delete(documentId);
466
+ }
467
+ /**
468
+ * Clear all journal data for a document, resetting it to empty state.
469
+ * The document record is kept (unlike delete). Used for room-reset.
470
+ */
471
+ async clearDocument(documentId) {
472
+ // Reset in-memory state to empty
473
+ this.documentStates.set(documentId, {
474
+ snapshot: {
475
+ graph: createEmptyGraph(),
476
+ vectorClock: {},
477
+ lt: 0,
478
+ journalIndex: 0,
479
+ },
480
+ journal: [],
481
+ });
482
+ // Delete all journal batches from DB
483
+ await this.journalRepo.deleteAll(documentId);
484
+ // Reset the document's currentState in DB
485
+ await this.documentRepo.update(documentId, {
486
+ currentState: {},
487
+ version: 0,
488
+ });
489
+ }
490
+ }
491
+ //# sourceMappingURL=GraphJournalService.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"GraphJournalService.js","sourceRoot":"","sources":["../../src/journal/GraphJournalService.ts"],"names":[],"mappings":"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"}
@@ -36,8 +36,8 @@ export interface RLEEncodedJournal {
36
36
  /**
37
37
  * Encode a sequence of CRDTMessages using RLE
38
38
  *
39
- * Groups consecutive messages from the same sessionId into segments.
40
- * Each segment stores the sessionId once and all messages in that run.
39
+ * Groups consecutive messages from the same client into segments.
40
+ * Each segment stores the client once and all messages in that run.
41
41
  */
42
42
  export declare function encodeJournalRLE(messages: CRDTMessage[]): RLEEncodedJournal;
43
43
  /**