@vuer-ai/vuer-rtc-server 0.1.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 -0
- package/PHASE1_SUMMARY.md +94 -0
- package/README.md +423 -0
- package/dist/broker/InMemoryBroker.d.ts +24 -0
- package/dist/broker/InMemoryBroker.d.ts.map +1 -0
- package/dist/broker/InMemoryBroker.js +65 -0
- package/dist/broker/InMemoryBroker.js.map +1 -0
- package/dist/broker/index.d.ts +3 -0
- package/dist/broker/index.d.ts.map +1 -0
- package/dist/broker/index.js +2 -0
- package/dist/broker/index.js.map +1 -0
- package/dist/broker/types.d.ts +47 -0
- package/dist/broker/types.d.ts.map +1 -0
- package/dist/broker/types.js +9 -0
- package/dist/broker/types.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +18 -0
- package/dist/index.js.map +1 -0
- package/dist/journal/JournalRepository.d.ts +39 -0
- package/dist/journal/JournalRepository.d.ts.map +1 -0
- package/dist/journal/JournalRepository.js +102 -0
- package/dist/journal/JournalRepository.js.map +1 -0
- package/dist/journal/JournalService.d.ts +69 -0
- package/dist/journal/JournalService.d.ts.map +1 -0
- package/dist/journal/JournalService.js +224 -0
- package/dist/journal/JournalService.js.map +1 -0
- package/dist/journal/index.d.ts +6 -0
- package/dist/journal/index.d.ts.map +1 -0
- package/dist/journal/index.js +6 -0
- package/dist/journal/index.js.map +1 -0
- package/dist/persistence/DocumentRepository.d.ts +22 -0
- package/dist/persistence/DocumentRepository.d.ts.map +1 -0
- package/dist/persistence/DocumentRepository.js +66 -0
- package/dist/persistence/DocumentRepository.js.map +1 -0
- package/dist/persistence/PrismaClient.d.ts +8 -0
- package/dist/persistence/PrismaClient.d.ts.map +1 -0
- package/dist/persistence/PrismaClient.js +21 -0
- package/dist/persistence/PrismaClient.js.map +1 -0
- package/dist/persistence/SessionRepository.d.ts +22 -0
- package/dist/persistence/SessionRepository.d.ts.map +1 -0
- package/dist/persistence/SessionRepository.js +103 -0
- package/dist/persistence/SessionRepository.js.map +1 -0
- package/dist/persistence/index.d.ts +7 -0
- package/dist/persistence/index.d.ts.map +1 -0
- package/dist/persistence/index.js +7 -0
- package/dist/persistence/index.js.map +1 -0
- package/dist/serve.d.ts +18 -0
- package/dist/serve.d.ts.map +1 -0
- package/dist/serve.js +211 -0
- package/dist/serve.js.map +1 -0
- package/dist/transport/RTCServer.d.ts +92 -0
- package/dist/transport/RTCServer.d.ts.map +1 -0
- package/dist/transport/RTCServer.js +273 -0
- package/dist/transport/RTCServer.js.map +1 -0
- package/dist/transport/index.d.ts +2 -0
- package/dist/transport/index.d.ts.map +1 -0
- package/dist/transport/index.js +2 -0
- package/dist/transport/index.js.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +2 -0
- package/dist/version.js.map +1 -0
- package/jest.config.js +36 -0
- package/package.json +56 -0
- package/prisma/schema.prisma +121 -0
- package/src/broker/InMemoryBroker.ts +81 -0
- package/src/broker/index.ts +2 -0
- package/src/broker/types.ts +60 -0
- package/src/index.ts +23 -0
- package/src/journal/JournalRepository.ts +119 -0
- package/src/journal/JournalService.ts +291 -0
- package/src/journal/index.ts +10 -0
- package/src/persistence/DocumentRepository.ts +76 -0
- package/src/persistence/PrismaClient.ts +24 -0
- package/src/persistence/SessionRepository.ts +114 -0
- package/src/persistence/index.ts +7 -0
- package/src/serve.ts +240 -0
- package/src/transport/RTCServer.ts +327 -0
- package/src/transport/index.ts +1 -0
- package/src/version.ts +1 -0
- package/tests/README.md +112 -0
- package/tests/demo.ts +555 -0
- package/tests/e2e/convergence.test.ts +221 -0
- package/tests/e2e/helpers/assertions.ts +158 -0
- package/tests/e2e/helpers/createTestServer.ts +220 -0
- package/tests/e2e/latency.test.ts +512 -0
- package/tests/e2e/packet-loss.test.ts +229 -0
- package/tests/e2e/relay.test.ts +255 -0
- package/tests/e2e/sync-perf.test.ts +365 -0
- package/tests/e2e/sync-reconciliation.test.ts +237 -0
- package/tests/e2e/text-sync.test.ts +199 -0
- package/tests/e2e/tombstone-convergence.test.ts +356 -0
- package/tests/fixtures/array-ops.jsonl +6 -0
- package/tests/fixtures/boolean-ops.jsonl +6 -0
- package/tests/fixtures/color-ops.jsonl +4 -0
- package/tests/fixtures/edit-buffer.jsonl +3 -0
- package/tests/fixtures/messages.jsonl +4 -0
- package/tests/fixtures/node-ops.jsonl +6 -0
- package/tests/fixtures/number-ops.jsonl +7 -0
- package/tests/fixtures/object-ops.jsonl +4 -0
- package/tests/fixtures/operations.jsonl +7 -0
- package/tests/fixtures/string-ops.jsonl +4 -0
- package/tests/fixtures/undo-redo.jsonl +3 -0
- package/tests/fixtures/vector-ops.jsonl +9 -0
- package/tests/integration/repositories.test.ts +320 -0
- package/tests/journal/journal-service.test.ts +185 -0
- package/tests/test-data/datatypes.ts +677 -0
- package/tests/test-data/operations-example.ts +306 -0
- package/tests/test-data/scene-example.ts +247 -0
- package/tests/unit/operations.test.ts +310 -0
- package/tests/unit/vectorClock.test.ts +281 -0
- package/tsconfig.json +19 -0
- package/tsconfig.test.json +8 -0
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RoomBroker — Room-scoped pub/sub, sequencing, and membership.
|
|
3
|
+
*
|
|
4
|
+
* The sync layer is data-type agnostic. It operates on CRDTMessage
|
|
5
|
+
* envelopes without inspecting ops[]. Swap InMemoryBroker for
|
|
6
|
+
* RedisBroker when scaling to multiple server instances.
|
|
7
|
+
*/
|
|
8
|
+
export {};
|
|
9
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/broker/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vuer RTC Server - Main exports
|
|
3
|
+
*
|
|
4
|
+
* Server-specific: RoomBroker, WebSocket transport, MongoDB persistence
|
|
5
|
+
* Core CRDT logic is in @vuer-ai/vuer-rtc
|
|
6
|
+
*/
|
|
7
|
+
export { VERSION } from './version.js';
|
|
8
|
+
export * from '@vuer-ai/vuer-rtc';
|
|
9
|
+
export * from './broker/index.js';
|
|
10
|
+
export * from './transport/index.js';
|
|
11
|
+
export * from './journal/index.js';
|
|
12
|
+
export * from './persistence/index.js';
|
|
13
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAGvC,cAAc,mBAAmB,CAAC;AAGlC,cAAc,mBAAmB,CAAC;AAGlC,cAAc,sBAAsB,CAAC;AAGrC,cAAc,oBAAoB,CAAC;AAGnC,cAAc,wBAAwB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vuer RTC Server - Main exports
|
|
3
|
+
*
|
|
4
|
+
* Server-specific: RoomBroker, WebSocket transport, MongoDB persistence
|
|
5
|
+
* Core CRDT logic is in @vuer-ai/vuer-rtc
|
|
6
|
+
*/
|
|
7
|
+
export { VERSION } from './version.js';
|
|
8
|
+
// Re-export core CRDT functionality from @vuer-ai/vuer-rtc
|
|
9
|
+
export * from '@vuer-ai/vuer-rtc';
|
|
10
|
+
// Broker (room-based pub/sub)
|
|
11
|
+
export * from './broker/index.js';
|
|
12
|
+
// Transport (WebSocket layer)
|
|
13
|
+
export * from './transport/index.js';
|
|
14
|
+
// Journal (server-side state management)
|
|
15
|
+
export * from './journal/index.js';
|
|
16
|
+
// Persistence (MongoDB via Prisma)
|
|
17
|
+
export * from './persistence/index.js';
|
|
18
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAEvC,2DAA2D;AAC3D,cAAc,mBAAmB,CAAC;AAElC,8BAA8B;AAC9B,cAAc,mBAAmB,CAAC;AAElC,8BAA8B;AAC9B,cAAc,sBAAsB,CAAC;AAErC,yCAAyC;AACzC,cAAc,oBAAoB,CAAC;AAEnC,mCAAmC;AACnC,cAAc,wBAAwB,CAAC"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Journal Repository - CRUD operations for journal entries
|
|
3
|
+
*
|
|
4
|
+
* Stores CRDTMessages in MongoDB with support for:
|
|
5
|
+
* - Append journal entries
|
|
6
|
+
* - Query entries since a given lamportTime
|
|
7
|
+
* - Compact (remove old entries after snapshot)
|
|
8
|
+
*/
|
|
9
|
+
import type { PrismaClient, JournalBatch } from '@prisma/client';
|
|
10
|
+
import type { CRDTMessage } from '@vuer-ai/vuer-rtc';
|
|
11
|
+
export declare class JournalRepository {
|
|
12
|
+
private prisma;
|
|
13
|
+
constructor(prisma: PrismaClient);
|
|
14
|
+
/**
|
|
15
|
+
* Append a message to the journal
|
|
16
|
+
*/
|
|
17
|
+
append(documentId: string, msg: CRDTMessage): Promise<JournalBatch>;
|
|
18
|
+
/**
|
|
19
|
+
* Get all journal entries for a document since a given lamportTime
|
|
20
|
+
*/
|
|
21
|
+
getSince(documentId: string, sinceLamportTime: number): Promise<CRDTMessage[]>;
|
|
22
|
+
/**
|
|
23
|
+
* Get full journal for a document
|
|
24
|
+
*/
|
|
25
|
+
getAll(documentId: string): Promise<JournalBatch[]>;
|
|
26
|
+
/**
|
|
27
|
+
* Delete journal entries before a given timestamp (for compaction)
|
|
28
|
+
*/
|
|
29
|
+
deleteBefore(documentId: string, beforeTimestamp: Date): Promise<number>;
|
|
30
|
+
/**
|
|
31
|
+
* Delete all journal entries for a document (for room-reset).
|
|
32
|
+
*/
|
|
33
|
+
deleteAll(documentId: string): Promise<number>;
|
|
34
|
+
/**
|
|
35
|
+
* Check if a message already exists (for deduplication)
|
|
36
|
+
*/
|
|
37
|
+
exists(documentId: string, msgId: string): Promise<boolean>;
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=JournalRepository.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"JournalRepository.d.ts","sourceRoot":"","sources":["../../src/journal/JournalRepository.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AACjE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAKrD,qBAAa,iBAAiB;IAChB,OAAO,CAAC,MAAM;gBAAN,MAAM,EAAE,YAAY;IAExC;;OAEG;IACG,MAAM,CACV,UAAU,EAAE,MAAM,EAClB,GAAG,EAAE,WAAW,GACf,OAAO,CAAC,YAAY,CAAC;IAaxB;;OAEG;IACG,QAAQ,CACZ,UAAU,EAAE,MAAM,EAClB,gBAAgB,EAAE,MAAM,GACvB,OAAO,CAAC,WAAW,EAAE,CAAC;IAuBzB;;OAEG;IACG,MAAM,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;IAOzD;;OAEG;IACG,YAAY,CAChB,UAAU,EAAE,MAAM,EAClB,eAAe,EAAE,IAAI,GACpB,OAAO,CAAC,MAAM,CAAC;IAYlB;;OAEG;IACG,SAAS,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAOpD;;OAEG;IACG,MAAM,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;CAWlE"}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Journal Repository - CRUD operations for journal entries
|
|
3
|
+
*
|
|
4
|
+
* Stores CRDTMessages in MongoDB with support for:
|
|
5
|
+
* - Append journal entries
|
|
6
|
+
* - Query entries since a given lamportTime
|
|
7
|
+
* - Compact (remove old entries after snapshot)
|
|
8
|
+
*/
|
|
9
|
+
// Note: JournalEntry is also defined in @vuer-ai/vuer-rtc client module
|
|
10
|
+
// This is the server-side version with slightly different fields
|
|
11
|
+
export class JournalRepository {
|
|
12
|
+
prisma;
|
|
13
|
+
constructor(prisma) {
|
|
14
|
+
this.prisma = prisma;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Append a message to the journal
|
|
18
|
+
*/
|
|
19
|
+
async append(documentId, msg) {
|
|
20
|
+
return this.prisma.journalBatch.create({
|
|
21
|
+
data: {
|
|
22
|
+
documentId,
|
|
23
|
+
sessionId: msg.sessionId,
|
|
24
|
+
batchId: msg.id,
|
|
25
|
+
operations: msg.ops,
|
|
26
|
+
startTime: new Date(msg.timestamp * 1000),
|
|
27
|
+
endTime: new Date(msg.timestamp * 1000),
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Get all journal entries for a document since a given lamportTime
|
|
33
|
+
*/
|
|
34
|
+
async getSince(documentId, sinceLamportTime) {
|
|
35
|
+
const batches = await this.prisma.journalBatch.findMany({
|
|
36
|
+
where: {
|
|
37
|
+
documentId,
|
|
38
|
+
},
|
|
39
|
+
orderBy: {
|
|
40
|
+
persistedAt: 'asc',
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
// Convert JournalBatch back to CRDTMessage
|
|
44
|
+
// Note: We need to store full CRDTMessage, not just operations
|
|
45
|
+
// For now, reconstruct what we can
|
|
46
|
+
return batches.map((batch) => ({
|
|
47
|
+
id: batch.batchId,
|
|
48
|
+
sessionId: batch.sessionId,
|
|
49
|
+
clock: {}, // TODO: Store in JournalBatch
|
|
50
|
+
lamportTime: 0, // TODO: Store in JournalBatch
|
|
51
|
+
timestamp: batch.startTime.getTime() / 1000,
|
|
52
|
+
ops: batch.operations,
|
|
53
|
+
}));
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Get full journal for a document
|
|
57
|
+
*/
|
|
58
|
+
async getAll(documentId) {
|
|
59
|
+
return this.prisma.journalBatch.findMany({
|
|
60
|
+
where: { documentId },
|
|
61
|
+
orderBy: { persistedAt: 'asc' },
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Delete journal entries before a given timestamp (for compaction)
|
|
66
|
+
*/
|
|
67
|
+
async deleteBefore(documentId, beforeTimestamp) {
|
|
68
|
+
const result = await this.prisma.journalBatch.deleteMany({
|
|
69
|
+
where: {
|
|
70
|
+
documentId,
|
|
71
|
+
persistedAt: {
|
|
72
|
+
lt: beforeTimestamp,
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
return result.count;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Delete all journal entries for a document (for room-reset).
|
|
80
|
+
*/
|
|
81
|
+
async deleteAll(documentId) {
|
|
82
|
+
const result = await this.prisma.journalBatch.deleteMany({
|
|
83
|
+
where: { documentId },
|
|
84
|
+
});
|
|
85
|
+
return result.count;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Check if a message already exists (for deduplication)
|
|
89
|
+
*/
|
|
90
|
+
async exists(documentId, msgId) {
|
|
91
|
+
const batch = await this.prisma.journalBatch.findUnique({
|
|
92
|
+
where: {
|
|
93
|
+
documentId_batchId: {
|
|
94
|
+
documentId,
|
|
95
|
+
batchId: msgId,
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
return batch !== null;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=JournalRepository.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"JournalRepository.js","sourceRoot":"","sources":["../../src/journal/JournalRepository.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAKH,wEAAwE;AACxE,iEAAiE;AAEjE,MAAM,OAAO,iBAAiB;IACR;IAApB,YAAoB,MAAoB;QAApB,WAAM,GAAN,MAAM,CAAc;IAAG,CAAC;IAE5C;;OAEG;IACH,KAAK,CAAC,MAAM,CACV,UAAkB,EAClB,GAAgB;QAEhB,OAAO,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,MAAM,CAAC;YACrC,IAAI,EAAE;gBACJ,UAAU;gBACV,SAAS,EAAE,GAAG,CAAC,SAAS;gBACxB,OAAO,EAAE,GAAG,CAAC,EAAE;gBACf,UAAU,EAAE,GAAG,CAAC,GAAY;gBAC5B,SAAS,EAAE,IAAI,IAAI,CAAC,GAAG,CAAC,SAAS,GAAG,IAAI,CAAC;gBACzC,OAAO,EAAE,IAAI,IAAI,CAAC,GAAG,CAAC,SAAS,GAAG,IAAI,CAAC;aACxC;SACF,CAAC,CAAC;IACL,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,QAAQ,CACZ,UAAkB,EAClB,gBAAwB;QAExB,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,QAAQ,CAAC;YACtD,KAAK,EAAE;gBACL,UAAU;aACX;YACD,OAAO,EAAE;gBACP,WAAW,EAAE,KAAK;aACnB;SACF,CAAC,CAAC;QAEH,2CAA2C;QAC3C,+DAA+D;QAC/D,mCAAmC;QACnC,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;YAC7B,EAAE,EAAE,KAAK,CAAC,OAAO;YACjB,SAAS,EAAE,KAAK,CAAC,SAAS;YAC1B,KAAK,EAAE,EAAE,EAAE,8BAA8B;YACzC,WAAW,EAAE,CAAC,EAAE,8BAA8B;YAC9C,SAAS,EAAE,KAAK,CAAC,SAAS,CAAC,OAAO,EAAE,GAAG,IAAI;YAC3C,GAAG,EAAE,KAAK,CAAC,UAAmB;SAC/B,CAAC,CAAC,CAAC;IACN,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,MAAM,CAAC,UAAkB;QAC7B,OAAO,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,QAAQ,CAAC;YACvC,KAAK,EAAE,EAAE,UAAU,EAAE;YACrB,OAAO,EAAE,EAAE,WAAW,EAAE,KAAK,EAAE;SAChC,CAAC,CAAC;IACL,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,YAAY,CAChB,UAAkB,EAClB,eAAqB;QAErB,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,UAAU,CAAC;YACvD,KAAK,EAAE;gBACL,UAAU;gBACV,WAAW,EAAE;oBACX,EAAE,EAAE,eAAe;iBACpB;aACF;SACF,CAAC,CAAC;QACH,OAAO,MAAM,CAAC,KAAK,CAAC;IACtB,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,SAAS,CAAC,UAAkB;QAChC,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,UAAU,CAAC;YACvD,KAAK,EAAE,EAAE,UAAU,EAAE;SACtB,CAAC,CAAC;QACH,OAAO,MAAM,CAAC,KAAK,CAAC;IACtB,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,MAAM,CAAC,UAAkB,EAAE,KAAa;QAC5C,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,UAAU,CAAC;YACtD,KAAK,EAAE;gBACL,kBAAkB,EAAE;oBAClB,UAAU;oBACV,OAAO,EAAE,KAAK;iBACf;aACF;SACF,CAAC,CAAC;QACH,OAAO,KAAK,KAAK,IAAI,CAAC;IACxB,CAAC;CACF"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Journal Service - Business logic for journal operations
|
|
3
|
+
*
|
|
4
|
+
* Handles:
|
|
5
|
+
* - Receiving and validating CRDTMessages
|
|
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 } 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 JournalService {
|
|
22
|
+
private journalRepo;
|
|
23
|
+
private documentRepo;
|
|
24
|
+
private validator;
|
|
25
|
+
private documentStates;
|
|
26
|
+
constructor(prisma: PrismaClient);
|
|
27
|
+
/**
|
|
28
|
+
* Load document state from database
|
|
29
|
+
*/
|
|
30
|
+
loadDocument(documentId: string): Promise<DocumentState | null>;
|
|
31
|
+
/**
|
|
32
|
+
* Process incoming message from client
|
|
33
|
+
*
|
|
34
|
+
* @returns Acknowledgement with processed message, or error
|
|
35
|
+
*/
|
|
36
|
+
processMessage(documentId: string, msg: CRDTMessage): Promise<{
|
|
37
|
+
success: boolean;
|
|
38
|
+
error?: string;
|
|
39
|
+
}>;
|
|
40
|
+
/**
|
|
41
|
+
* Get current graph state by replaying journal
|
|
42
|
+
*/
|
|
43
|
+
computeGraph(state: DocumentState): SceneGraph;
|
|
44
|
+
/**
|
|
45
|
+
* Get state for new client (snapshot + journal)
|
|
46
|
+
*/
|
|
47
|
+
getStateForClient(documentId: string): Promise<{
|
|
48
|
+
snapshot: Snapshot;
|
|
49
|
+
journal: CRDTMessage[];
|
|
50
|
+
} | null>;
|
|
51
|
+
/**
|
|
52
|
+
* Compact journal (create new snapshot from acknowledged entries)
|
|
53
|
+
*/
|
|
54
|
+
compact(documentId: string): Promise<void>;
|
|
55
|
+
/**
|
|
56
|
+
* Create a new document
|
|
57
|
+
*/
|
|
58
|
+
createDocument(name: string, ownerId: string): Promise<string>;
|
|
59
|
+
/**
|
|
60
|
+
* Unload document from memory
|
|
61
|
+
*/
|
|
62
|
+
unloadDocument(documentId: string): void;
|
|
63
|
+
/**
|
|
64
|
+
* Clear all journal data for a document, resetting it to empty state.
|
|
65
|
+
* The document record is kept (unlike delete). Used for room-reset.
|
|
66
|
+
*/
|
|
67
|
+
clearDocument(documentId: string): Promise<void>;
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=JournalService.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"JournalService.d.ts","sourceRoot":"","sources":["../../src/journal/JournalService.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAY,MAAM,gBAAgB,CAAC;AAC7D,OAAO,EACL,KAAK,WAAW,EAChB,KAAK,UAAU,EACf,KAAK,QAAQ,EAId,MAAM,mBAAmB,CAAC;AAK3B,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;AAED,qBAAa,cAAc;IACzB,OAAO,CAAC,WAAW,CAAoB;IACvC,OAAO,CAAC,YAAY,CAAqB;IACzC,OAAO,CAAC,SAAS,CAAqB;IAGtC,OAAO,CAAC,cAAc,CAAyC;gBAEnD,MAAM,EAAE,YAAY;IAMhC;;OAEG;IACG,YAAY,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC;IAyCrE;;;;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;IA6ChD;;OAEG;IACH,YAAY,CAAC,KAAK,EAAE,aAAa,GAAG,UAAU;IAkB9C;;OAEG;IACG,iBAAiB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC;QACnD,QAAQ,EAAE,QAAQ,CAAC;QACnB,OAAO,EAAE,WAAW,EAAE,CAAC;KACxB,GAAG,IAAI,CAAC;IAUT;;OAEG;IACG,OAAO,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAkDhD;;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,224 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Journal Service - Business logic for journal operations
|
|
3
|
+
*
|
|
4
|
+
* Handles:
|
|
5
|
+
* - Receiving and validating CRDTMessages
|
|
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, } from '@vuer-ai/vuer-rtc';
|
|
12
|
+
import { JournalRepository } from './JournalRepository.js';
|
|
13
|
+
import { DocumentRepository } from '../persistence/DocumentRepository.js';
|
|
14
|
+
export class JournalService {
|
|
15
|
+
journalRepo;
|
|
16
|
+
documentRepo;
|
|
17
|
+
validator;
|
|
18
|
+
// In-memory state per document (for fast access)
|
|
19
|
+
documentStates = new Map();
|
|
20
|
+
constructor(prisma) {
|
|
21
|
+
this.journalRepo = new JournalRepository(prisma);
|
|
22
|
+
this.documentRepo = new DocumentRepository(prisma);
|
|
23
|
+
this.validator = new OperationValidator();
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Load document state from database
|
|
27
|
+
*/
|
|
28
|
+
async loadDocument(documentId) {
|
|
29
|
+
// Check in-memory cache first
|
|
30
|
+
if (this.documentStates.has(documentId)) {
|
|
31
|
+
return this.documentStates.get(documentId);
|
|
32
|
+
}
|
|
33
|
+
// Load from database
|
|
34
|
+
const doc = await this.documentRepo.findById(documentId);
|
|
35
|
+
if (!doc)
|
|
36
|
+
return null;
|
|
37
|
+
const batches = await this.journalRepo.getAll(documentId);
|
|
38
|
+
// Reconstruct journal entries
|
|
39
|
+
const journal = batches.map((batch) => ({
|
|
40
|
+
msg: {
|
|
41
|
+
id: batch.batchId,
|
|
42
|
+
sessionId: batch.sessionId,
|
|
43
|
+
clock: {}, // TODO: Store in schema
|
|
44
|
+
lamportTime: 0, // TODO: Store in schema
|
|
45
|
+
timestamp: batch.startTime.getTime(),
|
|
46
|
+
ops: batch.operations,
|
|
47
|
+
},
|
|
48
|
+
deletedAt: undefined, // TODO: Store in schema
|
|
49
|
+
}));
|
|
50
|
+
// Parse snapshot from currentState
|
|
51
|
+
const snapshotGraph = doc.currentState?.graph || createEmptyGraph();
|
|
52
|
+
const state = {
|
|
53
|
+
snapshot: {
|
|
54
|
+
graph: snapshotGraph,
|
|
55
|
+
vectorClock: doc.currentState?.vectorClock || {},
|
|
56
|
+
lamportTime: doc.currentState?.lamportTime || 0,
|
|
57
|
+
journalIndex: doc.currentState?.journalIndex || 0,
|
|
58
|
+
},
|
|
59
|
+
journal,
|
|
60
|
+
};
|
|
61
|
+
this.documentStates.set(documentId, state);
|
|
62
|
+
return state;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Process incoming message from client
|
|
66
|
+
*
|
|
67
|
+
* @returns Acknowledgement with processed message, or error
|
|
68
|
+
*/
|
|
69
|
+
async processMessage(documentId, msg) {
|
|
70
|
+
// Validate message
|
|
71
|
+
const validation = this.validator.validateMessage(msg);
|
|
72
|
+
if (!validation.valid) {
|
|
73
|
+
return { success: false, error: validation.errors?.join(', ') };
|
|
74
|
+
}
|
|
75
|
+
// Load state
|
|
76
|
+
const state = await this.loadDocument(documentId);
|
|
77
|
+
if (!state) {
|
|
78
|
+
return { success: false, error: 'Document not found' };
|
|
79
|
+
}
|
|
80
|
+
// Check for duplicate
|
|
81
|
+
const exists = await this.journalRepo.exists(documentId, msg.id);
|
|
82
|
+
if (exists) {
|
|
83
|
+
return { success: true }; // Already processed, idempotent success
|
|
84
|
+
}
|
|
85
|
+
// Process meta operations (undo/redo)
|
|
86
|
+
for (const op of msg.ops) {
|
|
87
|
+
if (op.otype === 'meta.undo') {
|
|
88
|
+
const targetId = op.targetMsgId;
|
|
89
|
+
const target = state.journal.find((e) => e.msg.id === targetId);
|
|
90
|
+
if (target) {
|
|
91
|
+
target.deletedAt = msg.timestamp;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
else if (op.otype === 'meta.redo') {
|
|
95
|
+
const targetId = op.targetMsgId;
|
|
96
|
+
const target = state.journal.find((e) => e.msg.id === targetId);
|
|
97
|
+
if (target) {
|
|
98
|
+
delete target.deletedAt;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
// Add to journal
|
|
103
|
+
state.journal.push({ msg });
|
|
104
|
+
// Persist to database
|
|
105
|
+
await this.journalRepo.append(documentId, msg);
|
|
106
|
+
return { success: true };
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Get current graph state by replaying journal
|
|
110
|
+
*/
|
|
111
|
+
computeGraph(state) {
|
|
112
|
+
let graph = state.snapshot.graph;
|
|
113
|
+
for (const entry of state.journal) {
|
|
114
|
+
if (entry.deletedAt)
|
|
115
|
+
continue;
|
|
116
|
+
// Filter out meta ops for graph computation
|
|
117
|
+
const realOps = entry.msg.ops.filter((op) => !op.otype.startsWith('meta.'));
|
|
118
|
+
if (realOps.length > 0) {
|
|
119
|
+
graph = applyMessage(graph, { ...entry.msg, ops: realOps });
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return graph;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Get state for new client (snapshot + journal)
|
|
126
|
+
*/
|
|
127
|
+
async getStateForClient(documentId) {
|
|
128
|
+
const state = await this.loadDocument(documentId);
|
|
129
|
+
if (!state)
|
|
130
|
+
return null;
|
|
131
|
+
return {
|
|
132
|
+
snapshot: state.snapshot,
|
|
133
|
+
journal: state.journal.map((e) => e.msg),
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Compact journal (create new snapshot from acknowledged entries)
|
|
138
|
+
*/
|
|
139
|
+
async compact(documentId) {
|
|
140
|
+
const state = await this.loadDocument(documentId);
|
|
141
|
+
if (!state)
|
|
142
|
+
return;
|
|
143
|
+
// Compute new snapshot graph
|
|
144
|
+
const newGraph = this.computeGraph(state);
|
|
145
|
+
// Get max lamport time
|
|
146
|
+
let maxLamport = state.snapshot.journalIndex;
|
|
147
|
+
for (const entry of state.journal) {
|
|
148
|
+
maxLamport = Math.max(maxLamport, entry.msg.lamportTime);
|
|
149
|
+
}
|
|
150
|
+
// Merge vector clocks
|
|
151
|
+
let mergedClock = { ...state.snapshot.vectorClock };
|
|
152
|
+
for (const entry of state.journal) {
|
|
153
|
+
for (const [sessionId, time] of Object.entries(entry.msg.clock)) {
|
|
154
|
+
mergedClock[sessionId] = Math.max(mergedClock[sessionId] || 0, time);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
// Update snapshot
|
|
158
|
+
state.snapshot = {
|
|
159
|
+
graph: newGraph,
|
|
160
|
+
vectorClock: mergedClock,
|
|
161
|
+
lamportTime: maxLamport,
|
|
162
|
+
journalIndex: maxLamport,
|
|
163
|
+
};
|
|
164
|
+
// Clear journal
|
|
165
|
+
const oldJournal = state.journal;
|
|
166
|
+
state.journal = [];
|
|
167
|
+
// Persist snapshot
|
|
168
|
+
await this.documentRepo.update(documentId, {
|
|
169
|
+
currentState: state.snapshot,
|
|
170
|
+
});
|
|
171
|
+
// Delete old journal entries
|
|
172
|
+
if (oldJournal.length > 0) {
|
|
173
|
+
const lastTimestamp = new Date(oldJournal[oldJournal.length - 1].msg.timestamp);
|
|
174
|
+
await this.journalRepo.deleteBefore(documentId, lastTimestamp);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Create a new document
|
|
179
|
+
*/
|
|
180
|
+
async createDocument(name, ownerId) {
|
|
181
|
+
const doc = await this.documentRepo.create({ name, ownerId });
|
|
182
|
+
// Initialize in-memory state
|
|
183
|
+
this.documentStates.set(doc.id, {
|
|
184
|
+
snapshot: {
|
|
185
|
+
graph: createEmptyGraph(),
|
|
186
|
+
vectorClock: {},
|
|
187
|
+
lamportTime: 0,
|
|
188
|
+
journalIndex: 0,
|
|
189
|
+
},
|
|
190
|
+
journal: [],
|
|
191
|
+
});
|
|
192
|
+
return doc.id;
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Unload document from memory
|
|
196
|
+
*/
|
|
197
|
+
unloadDocument(documentId) {
|
|
198
|
+
this.documentStates.delete(documentId);
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Clear all journal data for a document, resetting it to empty state.
|
|
202
|
+
* The document record is kept (unlike delete). Used for room-reset.
|
|
203
|
+
*/
|
|
204
|
+
async clearDocument(documentId) {
|
|
205
|
+
// Reset in-memory state to empty
|
|
206
|
+
this.documentStates.set(documentId, {
|
|
207
|
+
snapshot: {
|
|
208
|
+
graph: createEmptyGraph(),
|
|
209
|
+
vectorClock: {},
|
|
210
|
+
lamportTime: 0,
|
|
211
|
+
journalIndex: 0,
|
|
212
|
+
},
|
|
213
|
+
journal: [],
|
|
214
|
+
});
|
|
215
|
+
// Delete all journal batches from DB
|
|
216
|
+
await this.journalRepo.deleteAll(documentId);
|
|
217
|
+
// Reset the document's currentState in DB
|
|
218
|
+
await this.documentRepo.update(documentId, {
|
|
219
|
+
currentState: {},
|
|
220
|
+
version: 0,
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
//# sourceMappingURL=JournalService.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"JournalService.js","sourceRoot":"","sources":["../../src/journal/JournalService.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/journal/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EACL,cAAc,EACd,KAAK,YAAY,IAAI,kBAAkB,EACvC,KAAK,aAAa,GACnB,MAAM,qBAAqB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/journal/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EACL,cAAc,GAGf,MAAM,qBAAqB,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Document Repository - CRUD operations for documents
|
|
3
|
+
*/
|
|
4
|
+
import type { PrismaClient, Document } from '@prisma/client';
|
|
5
|
+
export declare class DocumentRepository {
|
|
6
|
+
private prisma;
|
|
7
|
+
constructor(prisma: PrismaClient);
|
|
8
|
+
create(data: {
|
|
9
|
+
name: string;
|
|
10
|
+
ownerId: string;
|
|
11
|
+
}): Promise<Document>;
|
|
12
|
+
findById(id: string): Promise<Document | null>;
|
|
13
|
+
findByOwner(ownerId: string): Promise<Document[]>;
|
|
14
|
+
update(id: string, data: Partial<{
|
|
15
|
+
name: string;
|
|
16
|
+
currentState: any;
|
|
17
|
+
version: number;
|
|
18
|
+
}>): Promise<Document>;
|
|
19
|
+
delete(id: string): Promise<void>;
|
|
20
|
+
incrementVersion(id: string): Promise<Document>;
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=DocumentRepository.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DocumentRepository.d.ts","sourceRoot":"","sources":["../../src/persistence/DocumentRepository.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAE7D,qBAAa,kBAAkB;IACjB,OAAO,CAAC,MAAM;gBAAN,MAAM,EAAE,YAAY;IAElC,MAAM,CAAC,IAAI,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,QAAQ,CAAC;IAWlE,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC;IAW9C,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC;IAOjD,MAAM,CACV,EAAE,EAAE,MAAM,EACV,IAAI,EAAE,OAAO,CAAC;QACZ,IAAI,EAAE,MAAM,CAAC;QACb,YAAY,EAAE,GAAG,CAAC;QAClB,OAAO,EAAE,MAAM,CAAC;KACjB,CAAC,GACD,OAAO,CAAC,QAAQ,CAAC;IAOd,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAajC,gBAAgB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC;CAUtD"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Document Repository - CRUD operations for documents
|
|
3
|
+
*/
|
|
4
|
+
export class DocumentRepository {
|
|
5
|
+
prisma;
|
|
6
|
+
constructor(prisma) {
|
|
7
|
+
this.prisma = prisma;
|
|
8
|
+
}
|
|
9
|
+
async create(data) {
|
|
10
|
+
return this.prisma.document.create({
|
|
11
|
+
data: {
|
|
12
|
+
name: data.name,
|
|
13
|
+
ownerId: data.ownerId,
|
|
14
|
+
currentState: {}, // Empty initial state
|
|
15
|
+
version: 0,
|
|
16
|
+
},
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
async findById(id) {
|
|
20
|
+
try {
|
|
21
|
+
return await this.prisma.document.findUnique({
|
|
22
|
+
where: { id },
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
// Invalid ObjectId format
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
async findByOwner(ownerId) {
|
|
31
|
+
return this.prisma.document.findMany({
|
|
32
|
+
where: { ownerId },
|
|
33
|
+
orderBy: { updatedAt: 'desc' },
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
async update(id, data) {
|
|
37
|
+
return this.prisma.document.update({
|
|
38
|
+
where: { id },
|
|
39
|
+
data,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
async delete(id) {
|
|
43
|
+
try {
|
|
44
|
+
await this.prisma.document.delete({
|
|
45
|
+
where: { id },
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
// Ignore "record not found" errors (P2025)
|
|
50
|
+
if (error?.code !== 'P2025') {
|
|
51
|
+
throw error;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
async incrementVersion(id) {
|
|
56
|
+
return this.prisma.document.update({
|
|
57
|
+
where: { id },
|
|
58
|
+
data: {
|
|
59
|
+
version: {
|
|
60
|
+
increment: 1,
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=DocumentRepository.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DocumentRepository.js","sourceRoot":"","sources":["../../src/persistence/DocumentRepository.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,MAAM,OAAO,kBAAkB;IACT;IAApB,YAAoB,MAAoB;QAApB,WAAM,GAAN,MAAM,CAAc;IAAG,CAAC;IAE5C,KAAK,CAAC,MAAM,CAAC,IAAuC;QAClD,OAAO,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC;YACjC,IAAI,EAAE;gBACJ,IAAI,EAAE,IAAI,CAAC,IAAI;gBACf,OAAO,EAAE,IAAI,CAAC,OAAO;gBACrB,YAAY,EAAE,EAAE,EAAE,sBAAsB;gBACxC,OAAO,EAAE,CAAC;aACX;SACF,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,QAAQ,CAAC,EAAU;QACvB,IAAI,CAAC;YACH,OAAO,MAAM,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAC;gBAC3C,KAAK,EAAE,EAAE,EAAE,EAAE;aACd,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,0BAA0B;YAC1B,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IAED,KAAK,CAAC,WAAW,CAAC,OAAe;QAC/B,OAAO,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC;YACnC,KAAK,EAAE,EAAE,OAAO,EAAE;YAClB,OAAO,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE;SAC/B,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,MAAM,CACV,EAAU,EACV,IAIE;QAEF,OAAO,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC;YACjC,KAAK,EAAE,EAAE,EAAE,EAAE;YACb,IAAI;SACL,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,EAAU;QACrB,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC;gBAChC,KAAK,EAAE,EAAE,EAAE,EAAE;aACd,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,KAAU,EAAE,CAAC;YACpB,2CAA2C;YAC3C,IAAI,KAAK,EAAE,IAAI,KAAK,OAAO,EAAE,CAAC;gBAC5B,MAAM,KAAK,CAAC;YACd,CAAC;QACH,CAAC;IACH,CAAC;IAED,KAAK,CAAC,gBAAgB,CAAC,EAAU;QAC/B,OAAO,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC;YACjC,KAAK,EAAE,EAAE,EAAE,EAAE;YACb,IAAI,EAAE;gBACJ,OAAO,EAAE;oBACP,SAAS,EAAE,CAAC;iBACb;aACF;SACF,CAAC,CAAC;IACL,CAAC;CACF"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prisma Client singleton
|
|
3
|
+
* Ensures only one Prisma Client instance is created
|
|
4
|
+
*/
|
|
5
|
+
import { PrismaClient } from '@prisma/client';
|
|
6
|
+
export declare function createPrismaClient(): PrismaClient;
|
|
7
|
+
export declare function disconnectPrisma(): Promise<void>;
|
|
8
|
+
//# sourceMappingURL=PrismaClient.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PrismaClient.d.ts","sourceRoot":"","sources":["../../src/persistence/PrismaClient.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAI9C,wBAAgB,kBAAkB,IAAI,YAAY,CAOjD;AAED,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC,CAKtD"}
|