@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
|
@@ -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
|
|
40
|
-
* Each segment stores the
|
|
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
|
/**
|