document-drive 1.19.0 → 1.20.0
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/README.md +4 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +18 -0
- package/dist/src/cache/memory.d.ts +10 -0
- package/dist/src/cache/memory.d.ts.map +1 -0
- package/dist/src/cache/memory.js +26 -0
- package/dist/src/cache/redis.d.ts +14 -0
- package/dist/src/cache/redis.d.ts.map +1 -0
- package/dist/src/cache/redis.js +40 -0
- package/dist/src/cache/types.d.ts +7 -0
- package/dist/src/cache/types.d.ts.map +1 -0
- package/dist/src/cache/types.js +1 -0
- package/dist/src/drive-document-model/constants.d.ts +2 -0
- package/dist/src/drive-document-model/constants.d.ts.map +1 -0
- package/dist/src/drive-document-model/constants.js +1 -0
- package/dist/src/drive-document-model/gen/actions.d.ts +7 -0
- package/dist/src/drive-document-model/gen/actions.d.ts.map +1 -0
- package/dist/src/drive-document-model/gen/actions.js +2 -0
- package/dist/src/drive-document-model/gen/constants.d.ts +7 -0
- package/dist/src/drive-document-model/gen/constants.d.ts.map +1 -0
- package/dist/src/drive-document-model/gen/constants.js +16 -0
- package/dist/src/drive-document-model/gen/creators.d.ts +3 -0
- package/dist/src/drive-document-model/gen/creators.d.ts.map +1 -0
- package/dist/src/drive-document-model/gen/creators.js +2 -0
- package/dist/src/drive-document-model/gen/document-model.d.ts +3 -0
- package/dist/src/drive-document-model/gen/document-model.d.ts.map +1 -0
- package/dist/src/drive-document-model/gen/document-model.js +210 -0
- package/dist/src/drive-document-model/gen/drive/actions.d.ts +12 -0
- package/dist/src/drive-document-model/gen/drive/actions.d.ts.map +1 -0
- package/dist/src/drive-document-model/gen/drive/actions.js +1 -0
- package/dist/src/drive-document-model/gen/drive/creators.d.ts +11 -0
- package/dist/src/drive-document-model/gen/drive/creators.d.ts.map +1 -0
- package/dist/src/drive-document-model/gen/drive/creators.js +10 -0
- package/dist/src/drive-document-model/gen/drive/error.d.ts +2 -0
- package/dist/src/drive-document-model/gen/drive/error.d.ts.map +1 -0
- package/dist/src/drive-document-model/gen/drive/error.js +1 -0
- package/dist/src/drive-document-model/gen/drive/object.d.ts +14 -0
- package/dist/src/drive-document-model/gen/drive/object.d.ts.map +1 -0
- package/dist/src/drive-document-model/gen/drive/object.js +28 -0
- package/dist/src/drive-document-model/gen/drive/operations.d.ts +14 -0
- package/dist/src/drive-document-model/gen/drive/operations.d.ts.map +1 -0
- package/dist/src/drive-document-model/gen/drive/operations.js +1 -0
- package/dist/src/drive-document-model/gen/node/actions.d.ts +11 -0
- package/dist/src/drive-document-model/gen/node/actions.d.ts.map +1 -0
- package/dist/src/drive-document-model/gen/node/actions.js +1 -0
- package/dist/src/drive-document-model/gen/node/creators.d.ts +10 -0
- package/dist/src/drive-document-model/gen/node/creators.d.ts.map +1 -0
- package/dist/src/drive-document-model/gen/node/creators.js +9 -0
- package/dist/src/drive-document-model/gen/node/error.d.ts +2 -0
- package/dist/src/drive-document-model/gen/node/error.d.ts.map +1 -0
- package/dist/src/drive-document-model/gen/node/error.js +1 -0
- package/dist/src/drive-document-model/gen/node/object.d.ts +13 -0
- package/dist/src/drive-document-model/gen/node/object.d.ts.map +1 -0
- package/dist/src/drive-document-model/gen/node/object.js +25 -0
- package/dist/src/drive-document-model/gen/node/operations.d.ts +13 -0
- package/dist/src/drive-document-model/gen/node/operations.d.ts.map +1 -0
- package/dist/src/drive-document-model/gen/node/operations.js +1 -0
- package/dist/src/drive-document-model/gen/object.d.ts +21 -0
- package/dist/src/drive-document-model/gen/object.d.ts.map +1 -0
- package/dist/src/drive-document-model/gen/object.js +28 -0
- package/dist/src/drive-document-model/gen/reducer.d.ts +4 -0
- package/dist/src/drive-document-model/gen/reducer.d.ts.map +1 -0
- package/dist/src/drive-document-model/gen/reducer.js +74 -0
- package/dist/src/drive-document-model/gen/schema/types.d.ts +176 -0
- package/dist/src/drive-document-model/gen/schema/types.d.ts.map +1 -0
- package/dist/src/drive-document-model/gen/schema/types.js +1 -0
- package/dist/src/drive-document-model/gen/schema/zod.d.ts +87 -0
- package/dist/src/drive-document-model/gen/schema/zod.d.ts.map +1 -0
- package/dist/src/drive-document-model/gen/schema/zod.js +203 -0
- package/dist/src/drive-document-model/gen/types.d.ts +9 -0
- package/dist/src/drive-document-model/gen/types.d.ts.map +1 -0
- package/dist/src/drive-document-model/gen/types.js +1 -0
- package/dist/src/drive-document-model/gen/utils.d.ts +10 -0
- package/dist/src/drive-document-model/gen/utils.d.ts.map +1 -0
- package/dist/src/drive-document-model/gen/utils.js +27 -0
- package/dist/src/drive-document-model/index.d.ts +2 -0
- package/dist/src/drive-document-model/index.d.ts.map +1 -0
- package/dist/src/drive-document-model/index.js +1 -0
- package/dist/src/drive-document-model/module.d.ts +3 -0
- package/dist/src/drive-document-model/module.d.ts.map +1 -0
- package/dist/src/drive-document-model/module.js +12 -0
- package/dist/src/drive-document-model/src/reducers/drive.d.ts +8 -0
- package/dist/src/drive-document-model/src/reducers/drive.d.ts.map +1 -0
- package/dist/src/drive-document-model/src/reducers/drive.js +37 -0
- package/dist/src/drive-document-model/src/reducers/node.d.ts +8 -0
- package/dist/src/drive-document-model/src/reducers/node.d.ts.map +1 -0
- package/dist/src/drive-document-model/src/reducers/node.js +185 -0
- package/dist/src/drive-document-model/src/utils.d.ts +34 -0
- package/dist/src/drive-document-model/src/utils.d.ts.map +1 -0
- package/dist/src/drive-document-model/src/utils.js +146 -0
- package/dist/src/queue/base.d.ts +43 -0
- package/dist/src/queue/base.d.ts.map +1 -0
- package/dist/src/queue/base.js +241 -0
- package/dist/src/queue/redis.d.ts +28 -0
- package/dist/src/queue/redis.d.ts.map +1 -0
- package/dist/src/queue/redis.js +110 -0
- package/dist/src/queue/types.d.ts +55 -0
- package/dist/src/queue/types.d.ts.map +1 -0
- package/dist/src/queue/types.js +6 -0
- package/dist/src/read-mode/errors.d.ts +12 -0
- package/dist/src/read-mode/errors.d.ts.map +1 -0
- package/dist/src/read-mode/errors.js +17 -0
- package/dist/src/read-mode/server.d.ts +4 -0
- package/dist/src/read-mode/server.d.ts.map +1 -0
- package/dist/src/read-mode/server.js +78 -0
- package/dist/src/read-mode/service.d.ts +18 -0
- package/dist/src/read-mode/service.d.ts.map +1 -0
- package/dist/src/read-mode/service.js +112 -0
- package/dist/src/read-mode/types.d.ts +35 -0
- package/dist/src/read-mode/types.d.ts.map +1 -0
- package/dist/src/read-mode/types.js +1 -0
- package/dist/src/server/base-server.d.ts +112 -0
- package/dist/src/server/base-server.d.ts.map +1 -0
- package/dist/src/server/base-server.js +1280 -0
- package/dist/src/server/builder.d.ts +30 -0
- package/dist/src/server/builder.d.ts.map +1 -0
- package/dist/src/server/builder.js +89 -0
- package/dist/src/server/constants.d.ts +2 -0
- package/dist/src/server/constants.d.ts.map +1 -0
- package/dist/src/server/constants.js +1 -0
- package/dist/src/server/error.d.ts +30 -0
- package/dist/src/server/error.d.ts.map +1 -0
- package/dist/src/server/error.js +47 -0
- package/dist/src/server/event-emitter.d.ts +8 -0
- package/dist/src/server/event-emitter.d.ts.map +1 -0
- package/dist/src/server/event-emitter.js +10 -0
- package/dist/src/server/listener/index.d.ts +2 -0
- package/dist/src/server/listener/index.d.ts.map +1 -0
- package/dist/src/server/listener/index.js +1 -0
- package/dist/src/server/listener/listener-manager.d.ts +27 -0
- package/dist/src/server/listener/listener-manager.d.ts.map +1 -0
- package/dist/src/server/listener/listener-manager.js +401 -0
- package/dist/src/server/listener/transmitter/factory.d.ts +8 -0
- package/dist/src/server/listener/transmitter/factory.d.ts.map +1 -0
- package/dist/src/server/listener/transmitter/factory.js +25 -0
- package/dist/src/server/listener/transmitter/internal.d.ts +34 -0
- package/dist/src/server/listener/transmitter/internal.d.ts.map +1 -0
- package/dist/src/server/listener/transmitter/internal.js +87 -0
- package/dist/src/server/listener/transmitter/pull-responder.d.ts +38 -0
- package/dist/src/server/listener/transmitter/pull-responder.d.ts.map +1 -0
- package/dist/src/server/listener/transmitter/pull-responder.js +256 -0
- package/dist/src/server/listener/transmitter/switchboard-push.d.ts +9 -0
- package/dist/src/server/listener/transmitter/switchboard-push.d.ts.map +1 -0
- package/dist/src/server/listener/transmitter/switchboard-push.js +77 -0
- package/dist/src/server/listener/transmitter/types.d.ts +20 -0
- package/dist/src/server/listener/transmitter/types.d.ts.map +1 -0
- package/dist/src/server/listener/transmitter/types.js +1 -0
- package/dist/src/server/listener/util.d.ts +2 -0
- package/dist/src/server/listener/util.d.ts.map +1 -0
- package/dist/src/server/listener/util.js +22 -0
- package/dist/src/server/sync-manager.d.ts +30 -0
- package/dist/src/server/sync-manager.d.ts.map +1 -0
- package/dist/src/server/sync-manager.js +287 -0
- package/dist/src/server/types.d.ts +308 -0
- package/dist/src/server/types.d.ts.map +1 -0
- package/dist/src/server/types.js +12 -0
- package/dist/src/server/utils.d.ts +8 -0
- package/dist/src/server/utils.d.ts.map +1 -0
- package/dist/src/server/utils.js +47 -0
- package/dist/src/storage/base.d.ts +36 -0
- package/dist/src/storage/base.d.ts.map +1 -0
- package/dist/src/storage/base.js +4 -0
- package/dist/src/storage/browser.d.ts +36 -0
- package/dist/src/storage/browser.d.ts.map +1 -0
- package/dist/src/storage/browser.js +155 -0
- package/dist/src/storage/filesystem.d.ts +33 -0
- package/dist/src/storage/filesystem.d.ts.map +1 -0
- package/dist/src/storage/filesystem.js +197 -0
- package/dist/src/storage/memory.d.ts +33 -0
- package/dist/src/storage/memory.d.ts.map +1 -0
- package/dist/src/storage/memory.js +139 -0
- package/dist/src/storage/prisma.d.ts +67 -0
- package/dist/src/storage/prisma.d.ts.map +1 -0
- package/dist/src/storage/prisma.js +445 -0
- package/dist/src/storage/sequelize.d.ts +32 -0
- package/dist/src/storage/sequelize.d.ts.map +1 -0
- package/dist/src/storage/sequelize.js +373 -0
- package/dist/src/storage/types.d.ts +43 -0
- package/dist/src/storage/types.d.ts.map +1 -0
- package/dist/src/storage/types.js +1 -0
- package/dist/src/utils/default-drives-manager.d.ts +29 -0
- package/dist/src/utils/default-drives-manager.d.ts.map +1 -0
- package/dist/src/utils/default-drives-manager.js +208 -0
- package/dist/src/utils/graphql.d.ts +34 -0
- package/dist/src/utils/graphql.d.ts.map +1 -0
- package/dist/src/utils/graphql.js +183 -0
- package/dist/src/utils/logger.d.ts +27 -0
- package/dist/src/utils/logger.d.ts.map +1 -0
- package/dist/src/utils/logger.js +105 -0
- package/dist/src/utils/migrations.d.ts +4 -0
- package/dist/src/utils/migrations.d.ts.map +1 -0
- package/dist/src/utils/migrations.js +41 -0
- package/dist/src/utils/misc.d.ts +11 -0
- package/dist/src/utils/misc.d.ts.map +1 -0
- package/dist/src/utils/misc.js +43 -0
- package/dist/src/utils/run-asap.d.ts +12 -0
- package/dist/src/utils/run-asap.d.ts.map +1 -0
- package/dist/src/utils/run-asap.js +131 -0
- package/dist/test/document-helpers/utils.d.ts +8 -0
- package/dist/test/document-helpers/utils.d.ts.map +1 -0
- package/dist/test/document-helpers/utils.js +21 -0
- package/dist/test/utils.d.ts +48 -0
- package/dist/test/utils.d.ts.map +1 -0
- package/dist/test/utils.js +132 -0
- package/dist/test/vitest-setup.d.ts +2 -0
- package/dist/test/vitest-setup.d.ts.map +1 -0
- package/dist/test/vitest-setup.js +4 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/dist/vitest.config.d.ts +3 -0
- package/dist/vitest.config.d.ts.map +1 -0
- package/dist/vitest.config.js +20 -0
- package/package.json +20 -38
- package/src/cache/index.ts +0 -2
- package/src/cache/memory.ts +0 -33
- package/src/cache/redis.ts +0 -56
- package/src/cache/types.ts +0 -9
- package/src/index.ts +0 -4
- package/src/queue/base.ts +0 -320
- package/src/queue/index.ts +0 -2
- package/src/queue/redis.ts +0 -144
- package/src/queue/types.ts +0 -79
- package/src/read-mode/errors.ts +0 -19
- package/src/read-mode/index.ts +0 -125
- package/src/read-mode/service.ts +0 -207
- package/src/read-mode/types.ts +0 -108
- package/src/server/error.ts +0 -70
- package/src/server/index.ts +0 -2444
- package/src/server/listener/index.ts +0 -2
- package/src/server/listener/manager.ts +0 -652
- package/src/server/listener/transmitter/index.ts +0 -4
- package/src/server/listener/transmitter/internal.ts +0 -143
- package/src/server/listener/transmitter/pull-responder.ts +0 -462
- package/src/server/listener/transmitter/switchboard-push.ts +0 -125
- package/src/server/listener/transmitter/types.ts +0 -27
- package/src/server/types.ts +0 -596
- package/src/server/utils.ts +0 -82
- package/src/storage/base.ts +0 -81
- package/src/storage/browser.ts +0 -238
- package/src/storage/filesystem.ts +0 -297
- package/src/storage/index.ts +0 -2
- package/src/storage/memory.ts +0 -211
- package/src/storage/prisma.ts +0 -653
- package/src/storage/sequelize.ts +0 -498
- package/src/storage/types.ts +0 -97
- package/src/utils/default-drives-manager.ts +0 -341
- package/src/utils/document-helpers.ts +0 -21
- package/src/utils/graphql.ts +0 -301
- package/src/utils/index.ts +0 -90
- package/src/utils/logger.ts +0 -38
- package/src/utils/migrations.ts +0 -58
- package/src/utils/run-asap.ts +0 -156
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { DriveNotFoundError } from "#server/error";
|
|
2
|
+
import { mergeOperations } from "#utils/misc";
|
|
3
|
+
export class MemoryStorage {
|
|
4
|
+
documents;
|
|
5
|
+
drives;
|
|
6
|
+
slugToDriveId = {};
|
|
7
|
+
constructor() {
|
|
8
|
+
this.documents = {};
|
|
9
|
+
this.drives = {};
|
|
10
|
+
}
|
|
11
|
+
checkDocumentExists(drive, id) {
|
|
12
|
+
return Promise.resolve(this.documents[drive][id] !== undefined);
|
|
13
|
+
}
|
|
14
|
+
async getDocuments(drive) {
|
|
15
|
+
return Object.keys(this.documents[drive] ?? {});
|
|
16
|
+
}
|
|
17
|
+
async getDocument(driveId, id) {
|
|
18
|
+
const drive = this.documents[driveId];
|
|
19
|
+
if (!drive) {
|
|
20
|
+
throw new DriveNotFoundError(driveId);
|
|
21
|
+
}
|
|
22
|
+
const document = drive[id];
|
|
23
|
+
if (!document) {
|
|
24
|
+
throw new Error(`Document with id ${id} not found`);
|
|
25
|
+
}
|
|
26
|
+
return document;
|
|
27
|
+
}
|
|
28
|
+
async saveDocument(drive, id, document) {
|
|
29
|
+
this.documents[drive] = this.documents[drive] ?? {};
|
|
30
|
+
this.documents[drive][id] = document;
|
|
31
|
+
}
|
|
32
|
+
async clearStorage() {
|
|
33
|
+
this.documents = {};
|
|
34
|
+
this.drives = {};
|
|
35
|
+
}
|
|
36
|
+
async createDocument(drive, id, document) {
|
|
37
|
+
this.documents[drive] = this.documents[drive] ?? {};
|
|
38
|
+
const { operations, initialState, name, revision, documentType, created, lastModified, clipboard, state, } = document;
|
|
39
|
+
this.documents[drive][id] = {
|
|
40
|
+
operations,
|
|
41
|
+
initialState,
|
|
42
|
+
name,
|
|
43
|
+
revision,
|
|
44
|
+
documentType,
|
|
45
|
+
created,
|
|
46
|
+
lastModified,
|
|
47
|
+
clipboard,
|
|
48
|
+
state,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
async addDocumentOperations(drive, id, operations, header) {
|
|
52
|
+
const document = await this.getDocument(drive, id);
|
|
53
|
+
if (!document) {
|
|
54
|
+
throw new Error(`Document with id ${id} not found`);
|
|
55
|
+
}
|
|
56
|
+
const mergedOperations = mergeOperations(document.operations, operations);
|
|
57
|
+
this.documents[drive][id] = {
|
|
58
|
+
...document,
|
|
59
|
+
...header,
|
|
60
|
+
operations: mergedOperations,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
async deleteDocument(drive, id) {
|
|
64
|
+
if (!this.documents[drive]) {
|
|
65
|
+
throw new DriveNotFoundError(drive);
|
|
66
|
+
}
|
|
67
|
+
delete this.documents[drive][id];
|
|
68
|
+
}
|
|
69
|
+
async getDrives() {
|
|
70
|
+
return Object.keys(this.drives);
|
|
71
|
+
}
|
|
72
|
+
async getDrive(id) {
|
|
73
|
+
const drive = this.drives[id];
|
|
74
|
+
if (!drive) {
|
|
75
|
+
throw new DriveNotFoundError(id);
|
|
76
|
+
}
|
|
77
|
+
return drive;
|
|
78
|
+
}
|
|
79
|
+
async getDriveBySlug(slug) {
|
|
80
|
+
const driveId = this.slugToDriveId[slug];
|
|
81
|
+
if (!driveId) {
|
|
82
|
+
throw new Error(`Drive with slug ${slug} not found`);
|
|
83
|
+
}
|
|
84
|
+
return this.getDrive(driveId);
|
|
85
|
+
}
|
|
86
|
+
async createDrive(id, drive) {
|
|
87
|
+
this.drives[id] = drive;
|
|
88
|
+
this.documents[id] = {};
|
|
89
|
+
const { slug } = drive.initialState.state.global;
|
|
90
|
+
if (slug) {
|
|
91
|
+
this.slugToDriveId[slug] = id;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
async addDriveOperations(id, operations, header) {
|
|
95
|
+
const drive = await this.getDrive(id);
|
|
96
|
+
const mergedOperations = mergeOperations(drive.operations, operations);
|
|
97
|
+
this.drives[id] = {
|
|
98
|
+
...drive,
|
|
99
|
+
...header,
|
|
100
|
+
operations: mergedOperations,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
async deleteDrive(id) {
|
|
104
|
+
delete this.documents[id];
|
|
105
|
+
delete this.drives[id];
|
|
106
|
+
}
|
|
107
|
+
async getSynchronizationUnitsRevision(units) {
|
|
108
|
+
const results = await Promise.allSettled(units.map(async (unit) => {
|
|
109
|
+
try {
|
|
110
|
+
const document = await (unit.documentId
|
|
111
|
+
? this.getDocument(unit.driveId, unit.documentId)
|
|
112
|
+
: this.getDrive(unit.driveId));
|
|
113
|
+
if (!document) {
|
|
114
|
+
return undefined;
|
|
115
|
+
}
|
|
116
|
+
const operation = document.operations[unit.scope].at(-1);
|
|
117
|
+
if (operation) {
|
|
118
|
+
return {
|
|
119
|
+
driveId: unit.driveId,
|
|
120
|
+
documentId: unit.documentId,
|
|
121
|
+
scope: unit.scope,
|
|
122
|
+
branch: unit.branch,
|
|
123
|
+
lastUpdated: operation.timestamp,
|
|
124
|
+
revision: operation.index,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
catch {
|
|
129
|
+
return undefined;
|
|
130
|
+
}
|
|
131
|
+
}));
|
|
132
|
+
return results.reduce((acc, curr) => {
|
|
133
|
+
if (curr.status === "fulfilled" && curr.value !== undefined) {
|
|
134
|
+
acc.push(curr.value);
|
|
135
|
+
}
|
|
136
|
+
return acc;
|
|
137
|
+
}, []);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { Prisma, PrismaClient } from "@prisma/client";
|
|
2
|
+
import type { BaseStateFromDocument, DocumentHeader, Operation, OperationFromDocument, PHDocument } from "document-model";
|
|
3
|
+
import { IBackOffOptions } from "exponential-backoff";
|
|
4
|
+
import { DocumentDriveAction, DocumentDriveDocument } from "../drive-document-model/gen/types.js";
|
|
5
|
+
import { SynchronizationUnitQuery } from "../server/types.js";
|
|
6
|
+
import { IDriveStorage, IStorageDelegate } from "./types.js";
|
|
7
|
+
type Transaction = Omit<PrismaClient<Prisma.PrismaClientOptions, never>, "$connect" | "$disconnect" | "$on" | "$transaction" | "$use" | "$extends"> | ExtendedPrismaClient;
|
|
8
|
+
export type PrismaStorageOptions = {
|
|
9
|
+
transactionRetryBackoff?: IBackOffOptions;
|
|
10
|
+
};
|
|
11
|
+
declare function getRetryTransactionsClient<T extends PrismaClient>(prisma: T, backOffOptions?: Partial<IBackOffOptions>): import("@prisma/client/runtime/library").DynamicClientExtensionThis<Prisma.TypeMap<import("@prisma/client/runtime/library").InternalArgs & {
|
|
12
|
+
result: {};
|
|
13
|
+
model: {};
|
|
14
|
+
query: {};
|
|
15
|
+
client: {
|
|
16
|
+
$transaction: () => (...args: Parameters<T["$transaction"]>) => Promise<unknown>;
|
|
17
|
+
};
|
|
18
|
+
}, Prisma.PrismaClientOptions>, Prisma.TypeMapCb, {
|
|
19
|
+
result: {};
|
|
20
|
+
model: {};
|
|
21
|
+
query: {};
|
|
22
|
+
client: {
|
|
23
|
+
$transaction: () => (...args: Parameters<T["$transaction"]>) => Promise<unknown>;
|
|
24
|
+
};
|
|
25
|
+
}, {}>;
|
|
26
|
+
type ExtendedPrismaClient = ReturnType<typeof getRetryTransactionsClient<PrismaClient>>;
|
|
27
|
+
export declare class PrismaStorage implements IDriveStorage {
|
|
28
|
+
private db;
|
|
29
|
+
private delegate;
|
|
30
|
+
constructor(db: PrismaClient, options?: PrismaStorageOptions);
|
|
31
|
+
setStorageDelegate(delegate: IStorageDelegate): void;
|
|
32
|
+
createDrive(id: string, drive: DocumentDriveDocument): Promise<void>;
|
|
33
|
+
addDriveOperations(id: string, operations: Operation<DocumentDriveAction>[], header: DocumentHeader): Promise<void>;
|
|
34
|
+
addDriveOperationsWithTransaction(drive: string, callback: (document: DocumentDriveDocument) => Promise<{
|
|
35
|
+
operations: Operation[];
|
|
36
|
+
header: DocumentHeader;
|
|
37
|
+
}>): Promise<never>;
|
|
38
|
+
createDocument(drive: string, id: string, document: PHDocument): Promise<void>;
|
|
39
|
+
private _addDocumentOperations;
|
|
40
|
+
addDocumentOperationsWithTransaction<TDocument extends PHDocument>(drive: string, id: string, callback: (document: TDocument) => Promise<{
|
|
41
|
+
operations: OperationFromDocument<TDocument>[];
|
|
42
|
+
header: DocumentHeader;
|
|
43
|
+
newState?: BaseStateFromDocument<TDocument> | undefined;
|
|
44
|
+
}>): Promise<never>;
|
|
45
|
+
addDocumentOperations(drive: string, id: string, operations: Operation[], header: DocumentHeader): Promise<void>;
|
|
46
|
+
getDocuments(drive: string): Promise<string[]>;
|
|
47
|
+
checkDocumentExists(driveId: string, id: string): Promise<boolean>;
|
|
48
|
+
getDocument<TDocument extends PHDocument>(driveId: string, id: string, tx?: Transaction): Promise<TDocument>;
|
|
49
|
+
deleteDocument(drive: string, id: string): Promise<void>;
|
|
50
|
+
getDrives(): Promise<string[]>;
|
|
51
|
+
getDrive(id: string): Promise<DocumentDriveDocument>;
|
|
52
|
+
getDriveBySlug(slug: string): Promise<DocumentDriveDocument>;
|
|
53
|
+
deleteDrive(id: string): Promise<void>;
|
|
54
|
+
getOperationResultingState(driveId: string, documentId: string, index: number, scope: string, branch: string): Promise<string | undefined>;
|
|
55
|
+
getDriveOperationResultingState(drive: string, index: number, scope: string, branch: string): Promise<string | undefined>;
|
|
56
|
+
getSynchronizationUnitsRevision(units: SynchronizationUnitQuery[]): Promise<{
|
|
57
|
+
driveId: string;
|
|
58
|
+
documentId: string;
|
|
59
|
+
scope: string;
|
|
60
|
+
branch: string;
|
|
61
|
+
lastUpdated: string;
|
|
62
|
+
revision: number;
|
|
63
|
+
}[]>;
|
|
64
|
+
migrateOperationSignatures(): Promise<void>;
|
|
65
|
+
}
|
|
66
|
+
export {};
|
|
67
|
+
//# sourceMappingURL=prisma.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prisma.d.ts","sourceRoot":"","sources":["../../../src/storage/prisma.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,445 @@
|
|
|
1
|
+
import { PrismaClientKnownRequestError } from "@prisma/client/runtime/library";
|
|
2
|
+
import { backOff } from "exponential-backoff";
|
|
3
|
+
import { ConflictOperationError, DriveNotFoundError } from "../server/error.js";
|
|
4
|
+
import { logger } from "../utils/logger.js";
|
|
5
|
+
function storageToOperation(op) {
|
|
6
|
+
const operation = {
|
|
7
|
+
id: op.opId || undefined,
|
|
8
|
+
skip: op.skip,
|
|
9
|
+
hash: op.hash,
|
|
10
|
+
index: op.index,
|
|
11
|
+
timestamp: new Date(op.timestamp).toISOString(),
|
|
12
|
+
input: JSON.parse(op.input),
|
|
13
|
+
type: op.type,
|
|
14
|
+
scope: op.scope,
|
|
15
|
+
resultingState: op.resultingState
|
|
16
|
+
? op.resultingState.toString()
|
|
17
|
+
: undefined,
|
|
18
|
+
attachments: op.attachments,
|
|
19
|
+
};
|
|
20
|
+
if (op.context) {
|
|
21
|
+
operation.context = op.context;
|
|
22
|
+
}
|
|
23
|
+
return operation;
|
|
24
|
+
}
|
|
25
|
+
function getRetryTransactionsClient(prisma, backOffOptions) {
|
|
26
|
+
return prisma.$extends({
|
|
27
|
+
client: {
|
|
28
|
+
$transaction: (...args) => {
|
|
29
|
+
// eslint-disable-next-line prefer-spread
|
|
30
|
+
return backOff(() => prisma.$transaction.apply(prisma, args), {
|
|
31
|
+
retry: (e) => {
|
|
32
|
+
const code = e.code;
|
|
33
|
+
// Retry the transaction only if the error was due to a write conflict or deadlock
|
|
34
|
+
// See: https://www.prisma.io/docs/reference/api-reference/error-reference#p2034
|
|
35
|
+
if (code !== "P2034") {
|
|
36
|
+
logger.error("TRANSACTION ERROR", e);
|
|
37
|
+
}
|
|
38
|
+
return code === "P2034";
|
|
39
|
+
},
|
|
40
|
+
...backOffOptions,
|
|
41
|
+
});
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
export class PrismaStorage {
|
|
47
|
+
db;
|
|
48
|
+
delegate;
|
|
49
|
+
constructor(db, options) {
|
|
50
|
+
const backOffOptions = options?.transactionRetryBackoff;
|
|
51
|
+
this.db = getRetryTransactionsClient(db, {
|
|
52
|
+
...backOffOptions,
|
|
53
|
+
jitter: backOffOptions?.jitter ?? "full",
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
setStorageDelegate(delegate) {
|
|
57
|
+
this.delegate = delegate;
|
|
58
|
+
}
|
|
59
|
+
async createDrive(id, drive) {
|
|
60
|
+
// drive for all drive documents
|
|
61
|
+
await this.createDocument("drives", id, drive);
|
|
62
|
+
await this.db.drive.upsert({
|
|
63
|
+
where: {
|
|
64
|
+
slug: drive.initialState.state.global.slug ?? id,
|
|
65
|
+
},
|
|
66
|
+
create: {
|
|
67
|
+
id: id,
|
|
68
|
+
slug: drive.initialState.state.global.slug ?? id,
|
|
69
|
+
},
|
|
70
|
+
update: {
|
|
71
|
+
id,
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
async addDriveOperations(id, operations, header) {
|
|
76
|
+
await this.addDocumentOperations("drives", id, operations, header);
|
|
77
|
+
}
|
|
78
|
+
async addDriveOperationsWithTransaction(drive, callback) {
|
|
79
|
+
return this.addDocumentOperationsWithTransaction("drives", drive, (document) => callback(document));
|
|
80
|
+
}
|
|
81
|
+
async createDocument(drive, id, document) {
|
|
82
|
+
await this.db.document.upsert({
|
|
83
|
+
where: {
|
|
84
|
+
id_driveId: {
|
|
85
|
+
id,
|
|
86
|
+
driveId: drive,
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
update: {},
|
|
90
|
+
create: {
|
|
91
|
+
name: document.name,
|
|
92
|
+
documentType: document.documentType,
|
|
93
|
+
driveId: drive,
|
|
94
|
+
initialState: JSON.stringify(document.initialState),
|
|
95
|
+
lastModified: document.lastModified,
|
|
96
|
+
revision: JSON.stringify(document.revision),
|
|
97
|
+
id,
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
async _addDocumentOperations(tx, drive, id, operations, header) {
|
|
102
|
+
try {
|
|
103
|
+
await tx.operation.createMany({
|
|
104
|
+
data: operations.map((op) => ({
|
|
105
|
+
driveId: drive,
|
|
106
|
+
documentId: id,
|
|
107
|
+
hash: op.hash,
|
|
108
|
+
index: op.index,
|
|
109
|
+
input: JSON.stringify(op.input),
|
|
110
|
+
timestamp: op.timestamp,
|
|
111
|
+
type: op.type,
|
|
112
|
+
scope: op.scope,
|
|
113
|
+
branch: "main",
|
|
114
|
+
opId: op.id,
|
|
115
|
+
skip: op.skip,
|
|
116
|
+
context: op.context,
|
|
117
|
+
resultingState: op.resultingState
|
|
118
|
+
? Buffer.from(JSON.stringify(op.resultingState))
|
|
119
|
+
: undefined,
|
|
120
|
+
})),
|
|
121
|
+
});
|
|
122
|
+
await tx.document.updateMany({
|
|
123
|
+
where: {
|
|
124
|
+
id,
|
|
125
|
+
driveId: drive,
|
|
126
|
+
},
|
|
127
|
+
data: {
|
|
128
|
+
lastModified: header.lastModified,
|
|
129
|
+
revision: JSON.stringify(header.revision),
|
|
130
|
+
},
|
|
131
|
+
});
|
|
132
|
+
await Promise.all(operations
|
|
133
|
+
.filter((o) => o.attachments?.length)
|
|
134
|
+
.map((op) => {
|
|
135
|
+
return tx.operation.update({
|
|
136
|
+
where: {
|
|
137
|
+
unique_operation: {
|
|
138
|
+
driveId: drive,
|
|
139
|
+
documentId: id,
|
|
140
|
+
index: op.index,
|
|
141
|
+
scope: op.scope,
|
|
142
|
+
branch: "main",
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
data: {
|
|
146
|
+
attachments: {
|
|
147
|
+
createMany: {
|
|
148
|
+
data: op.attachments ?? [],
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
});
|
|
153
|
+
}));
|
|
154
|
+
}
|
|
155
|
+
catch (e) {
|
|
156
|
+
// P2002: Unique constraint failed
|
|
157
|
+
// Operation with existing index
|
|
158
|
+
if (e instanceof PrismaClientKnownRequestError && e.code === "P2002") {
|
|
159
|
+
const existingOperation = await this.db.operation.findFirst({
|
|
160
|
+
where: {
|
|
161
|
+
AND: operations.map((op) => ({
|
|
162
|
+
driveId: drive,
|
|
163
|
+
documentId: id,
|
|
164
|
+
scope: op.scope,
|
|
165
|
+
branch: "main",
|
|
166
|
+
index: op.index,
|
|
167
|
+
})),
|
|
168
|
+
},
|
|
169
|
+
});
|
|
170
|
+
const conflictOp = operations.find((op) => existingOperation?.index === op.index &&
|
|
171
|
+
existingOperation.scope === op.scope);
|
|
172
|
+
if (!existingOperation || !conflictOp) {
|
|
173
|
+
console.error(e);
|
|
174
|
+
throw e;
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
throw new ConflictOperationError(storageToOperation(existingOperation), conflictOp);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
throw e;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
async addDocumentOperationsWithTransaction(drive, id, callback) {
|
|
186
|
+
let result = null;
|
|
187
|
+
await this.db.$transaction(async (tx) => {
|
|
188
|
+
const document = await this.getDocument(drive, id, tx);
|
|
189
|
+
if (!document) {
|
|
190
|
+
throw new Error(`Document with id ${id} not found`);
|
|
191
|
+
}
|
|
192
|
+
result = await callback(document);
|
|
193
|
+
const { operations, header, newState } = result;
|
|
194
|
+
return this._addDocumentOperations(tx, drive, id, operations, header);
|
|
195
|
+
}, { isolationLevel: "Serializable", maxWait: 10000, timeout: 20000 });
|
|
196
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
197
|
+
if (!result) {
|
|
198
|
+
throw new Error("No operations were provided");
|
|
199
|
+
}
|
|
200
|
+
return result;
|
|
201
|
+
}
|
|
202
|
+
async addDocumentOperations(drive, id, operations, header) {
|
|
203
|
+
return this._addDocumentOperations(this.db, drive, id, operations, header);
|
|
204
|
+
}
|
|
205
|
+
async getDocuments(drive) {
|
|
206
|
+
const docs = await this.db.document.findMany({
|
|
207
|
+
select: {
|
|
208
|
+
id: true,
|
|
209
|
+
},
|
|
210
|
+
where: {
|
|
211
|
+
AND: {
|
|
212
|
+
driveId: drive,
|
|
213
|
+
NOT: {
|
|
214
|
+
id: "drives",
|
|
215
|
+
},
|
|
216
|
+
},
|
|
217
|
+
},
|
|
218
|
+
});
|
|
219
|
+
return docs.map((doc) => doc.id);
|
|
220
|
+
}
|
|
221
|
+
async checkDocumentExists(driveId, id) {
|
|
222
|
+
const count = await this.db.document.count({
|
|
223
|
+
where: {
|
|
224
|
+
id: id,
|
|
225
|
+
driveId: driveId,
|
|
226
|
+
},
|
|
227
|
+
});
|
|
228
|
+
return count > 0;
|
|
229
|
+
}
|
|
230
|
+
async getDocument(driveId, id, tx) {
|
|
231
|
+
const prisma = tx ?? this.db;
|
|
232
|
+
const result = await prisma.document.findUnique({
|
|
233
|
+
where: {
|
|
234
|
+
id_driveId: {
|
|
235
|
+
driveId,
|
|
236
|
+
id,
|
|
237
|
+
},
|
|
238
|
+
},
|
|
239
|
+
});
|
|
240
|
+
if (result === null) {
|
|
241
|
+
throw new Error(`Document with id ${id} not found`);
|
|
242
|
+
}
|
|
243
|
+
const cachedOperations = (await this.delegate?.getCachedOperations(driveId, id)) ?? {
|
|
244
|
+
global: [],
|
|
245
|
+
local: [],
|
|
246
|
+
};
|
|
247
|
+
const scopeIndex = Object.keys(cachedOperations).reduceRight((acc, value) => {
|
|
248
|
+
const scope = value;
|
|
249
|
+
const lastIndex = cachedOperations[scope].at(-1)?.index ?? -1;
|
|
250
|
+
acc[scope] = lastIndex;
|
|
251
|
+
return acc;
|
|
252
|
+
}, { global: -1, local: -1 });
|
|
253
|
+
const conditions = Object.entries(scopeIndex).map(([scope, index]) => `("scope" = '${scope}' AND "index" > ${index})`);
|
|
254
|
+
conditions.push(`("scope" NOT IN (${Object.keys(cachedOperations)
|
|
255
|
+
.map((s) => `'${s}'`)
|
|
256
|
+
.join(", ")}))`);
|
|
257
|
+
// retrieves operations with resulting state
|
|
258
|
+
// for the last operation of each scope
|
|
259
|
+
// TODO prevent SQL injection
|
|
260
|
+
const queryOperations = await prisma.$queryRawUnsafe(`WITH ranked_operations AS (
|
|
261
|
+
SELECT
|
|
262
|
+
*,
|
|
263
|
+
ROW_NUMBER() OVER (PARTITION BY scope ORDER BY index DESC) AS rn
|
|
264
|
+
FROM "Operation"
|
|
265
|
+
)
|
|
266
|
+
SELECT
|
|
267
|
+
"id",
|
|
268
|
+
"opId",
|
|
269
|
+
"scope",
|
|
270
|
+
"branch",
|
|
271
|
+
"index",
|
|
272
|
+
"skip",
|
|
273
|
+
"hash",
|
|
274
|
+
"timestamp",
|
|
275
|
+
"input",
|
|
276
|
+
"type",
|
|
277
|
+
"context",
|
|
278
|
+
CASE
|
|
279
|
+
WHEN rn = 1 THEN "resultingState"
|
|
280
|
+
ELSE NULL
|
|
281
|
+
END AS "resultingState"
|
|
282
|
+
FROM ranked_operations
|
|
283
|
+
WHERE "driveId" = $1 AND "documentId" = $2
|
|
284
|
+
AND (${conditions.join(" OR ")})
|
|
285
|
+
ORDER BY scope, index;
|
|
286
|
+
`, driveId, id);
|
|
287
|
+
const operationIds = queryOperations.map((o) => o.id);
|
|
288
|
+
const attachments = await prisma.attachment.findMany({
|
|
289
|
+
where: {
|
|
290
|
+
operationId: {
|
|
291
|
+
in: operationIds,
|
|
292
|
+
},
|
|
293
|
+
},
|
|
294
|
+
});
|
|
295
|
+
// TODO add attachments from cached operations
|
|
296
|
+
const fileRegistry = {};
|
|
297
|
+
const operationsByScope = queryOperations.reduce((acc, operation) => {
|
|
298
|
+
const scope = operation.scope;
|
|
299
|
+
if (!acc[scope]) {
|
|
300
|
+
acc[scope] = [];
|
|
301
|
+
}
|
|
302
|
+
const result = storageToOperation(operation);
|
|
303
|
+
result.attachments = attachments.filter((a) => a.operationId === operation.id);
|
|
304
|
+
result.attachments.forEach(({ hash, ...file }) => {
|
|
305
|
+
fileRegistry[hash] = file;
|
|
306
|
+
});
|
|
307
|
+
acc[scope].push(result);
|
|
308
|
+
return acc;
|
|
309
|
+
}, cachedOperations);
|
|
310
|
+
const dbDoc = result;
|
|
311
|
+
const doc = {
|
|
312
|
+
created: dbDoc.created.toISOString(),
|
|
313
|
+
name: dbDoc.name ? dbDoc.name : "",
|
|
314
|
+
documentType: dbDoc.documentType,
|
|
315
|
+
initialState: JSON.parse(dbDoc.initialState),
|
|
316
|
+
state: undefined,
|
|
317
|
+
lastModified: new Date(dbDoc.lastModified).toISOString(),
|
|
318
|
+
operations: operationsByScope,
|
|
319
|
+
clipboard: [],
|
|
320
|
+
revision: JSON.parse(dbDoc.revision),
|
|
321
|
+
attachments: {},
|
|
322
|
+
};
|
|
323
|
+
return doc;
|
|
324
|
+
}
|
|
325
|
+
async deleteDocument(drive, id) {
|
|
326
|
+
try {
|
|
327
|
+
await this.db.document.deleteMany({
|
|
328
|
+
where: {
|
|
329
|
+
driveId: drive,
|
|
330
|
+
id: id,
|
|
331
|
+
},
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
catch (e) {
|
|
335
|
+
const prismaError = e;
|
|
336
|
+
// Ignore Error: P2025: An operation failed because it depends on one or more records that were required but not found.
|
|
337
|
+
if ((prismaError.code && prismaError.code === "P2025") ||
|
|
338
|
+
prismaError.message?.includes("An operation failed because it depends on one or more records that were required but not found.")) {
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
throw e;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
async getDrives() {
|
|
345
|
+
return this.getDocuments("drives");
|
|
346
|
+
}
|
|
347
|
+
async getDrive(id) {
|
|
348
|
+
try {
|
|
349
|
+
const doc = await this.getDocument("drives", id);
|
|
350
|
+
return doc;
|
|
351
|
+
}
|
|
352
|
+
catch (e) {
|
|
353
|
+
logger.error(e);
|
|
354
|
+
throw new DriveNotFoundError(id);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
async getDriveBySlug(slug) {
|
|
358
|
+
const driveEntity = await this.db.drive.findFirst({
|
|
359
|
+
where: {
|
|
360
|
+
slug,
|
|
361
|
+
},
|
|
362
|
+
});
|
|
363
|
+
if (!driveEntity) {
|
|
364
|
+
throw new Error(`Drive with slug ${slug} not found`);
|
|
365
|
+
}
|
|
366
|
+
return this.getDrive(driveEntity.id);
|
|
367
|
+
}
|
|
368
|
+
async deleteDrive(id) {
|
|
369
|
+
// delete drive and associated slug
|
|
370
|
+
await this.db.drive.deleteMany({
|
|
371
|
+
where: {
|
|
372
|
+
id,
|
|
373
|
+
},
|
|
374
|
+
});
|
|
375
|
+
// delete drive document and its operations
|
|
376
|
+
await this.deleteDocument("drives", id);
|
|
377
|
+
// deletes all documents of the drive
|
|
378
|
+
await this.db.document.deleteMany({
|
|
379
|
+
where: {
|
|
380
|
+
driveId: id,
|
|
381
|
+
},
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
async getOperationResultingState(driveId, documentId, index, scope, branch) {
|
|
385
|
+
const operation = await this.db.operation.findUnique({
|
|
386
|
+
where: {
|
|
387
|
+
unique_operation: {
|
|
388
|
+
driveId,
|
|
389
|
+
documentId,
|
|
390
|
+
index,
|
|
391
|
+
scope,
|
|
392
|
+
branch,
|
|
393
|
+
},
|
|
394
|
+
},
|
|
395
|
+
});
|
|
396
|
+
return operation?.resultingState?.toString();
|
|
397
|
+
}
|
|
398
|
+
getDriveOperationResultingState(drive, index, scope, branch) {
|
|
399
|
+
return this.getOperationResultingState("drives", drive, index, scope, branch);
|
|
400
|
+
}
|
|
401
|
+
async getSynchronizationUnitsRevision(units) {
|
|
402
|
+
// TODO add branch condition
|
|
403
|
+
const whereClauses = units
|
|
404
|
+
.map((_, index) => {
|
|
405
|
+
return `("driveId" = $${index * 3 + 1} AND "documentId" = $${index * 3 + 2} AND "scope" = $${index * 3 + 3})`;
|
|
406
|
+
})
|
|
407
|
+
.join(" OR ");
|
|
408
|
+
const query = `
|
|
409
|
+
SELECT "driveId", "documentId", "scope", "branch", MAX("timestamp") as "lastUpdated", MAX("index") as revision FROM "Operation"
|
|
410
|
+
WHERE ${whereClauses}
|
|
411
|
+
GROUP BY "driveId", "documentId", "scope", "branch"
|
|
412
|
+
`;
|
|
413
|
+
const params = units
|
|
414
|
+
.map((unit) => [
|
|
415
|
+
unit.documentId ? unit.driveId : "drives",
|
|
416
|
+
unit.documentId || unit.driveId,
|
|
417
|
+
unit.scope,
|
|
418
|
+
])
|
|
419
|
+
.flat();
|
|
420
|
+
const results = await this.db.$queryRawUnsafe(query, ...params);
|
|
421
|
+
return results.map((row) => ({
|
|
422
|
+
...row,
|
|
423
|
+
driveId: row.driveId === "drives" ? row.documentId : row.driveId,
|
|
424
|
+
documentId: row.driveId === "drives" ? "" : row.documentId,
|
|
425
|
+
lastUpdated: new Date(row.lastUpdated).toISOString(),
|
|
426
|
+
}));
|
|
427
|
+
}
|
|
428
|
+
// migrates all stored operations from legacy signature to signatures array
|
|
429
|
+
async migrateOperationSignatures() {
|
|
430
|
+
const count = await this.db.$executeRaw `
|
|
431
|
+
UPDATE "Operation"
|
|
432
|
+
SET context = jsonb_set(
|
|
433
|
+
context #- '{signer,signature}', -- Remove the old 'signature' field
|
|
434
|
+
'{signer,signatures}', -- Path to the new 'signatures' field
|
|
435
|
+
CASE
|
|
436
|
+
WHEN context->'signer'->>'signature' = '' THEN '[]'::jsonb
|
|
437
|
+
ELSE to_jsonb(array[context->'signer'->>'signature'])
|
|
438
|
+
END
|
|
439
|
+
)
|
|
440
|
+
WHERE context->'signer' ? 'signature' -- Check if the 'signature' key exists
|
|
441
|
+
`;
|
|
442
|
+
logger.info(`Migrated ${count} operations`);
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { DocumentDriveAction, DocumentDriveDocument } from "#drive-document-model/gen/types";
|
|
2
|
+
import { SynchronizationUnitQuery } from "#server/types";
|
|
3
|
+
import { AttachmentInput, DocumentHeader, Operation, PHDocument } from "document-model";
|
|
4
|
+
import { Options, Sequelize } from "sequelize";
|
|
5
|
+
import { IDriveStorage } from "./types.js";
|
|
6
|
+
export declare class SequelizeStorage implements IDriveStorage {
|
|
7
|
+
private db;
|
|
8
|
+
constructor(options: Options);
|
|
9
|
+
syncModels(): Promise<Sequelize>;
|
|
10
|
+
createDrive(id: string, drive: DocumentDriveDocument): Promise<void>;
|
|
11
|
+
addDriveOperations(id: string, operations: Operation<DocumentDriveAction>[], header: DocumentHeader): Promise<void>;
|
|
12
|
+
createDocument(drive: string, id: string, document: PHDocument): Promise<void>;
|
|
13
|
+
addDocumentOperations(drive: string, id: string, operations: Operation[], header: DocumentHeader): Promise<void>;
|
|
14
|
+
_addDocumentOperationAttachments(driveId: string, documentId: string, operation: Operation, attachments: AttachmentInput[]): Promise<import("sequelize").Model<any, any>[]>;
|
|
15
|
+
getDocuments(drive: string): Promise<string[]>;
|
|
16
|
+
checkDocumentExists(driveId: string, id: string): Promise<boolean>;
|
|
17
|
+
getDocument<TDocument extends PHDocument>(driveId: string, id: string): Promise<TDocument>;
|
|
18
|
+
deleteDocument(drive: string, id: string): Promise<void>;
|
|
19
|
+
getDrives(): Promise<string[]>;
|
|
20
|
+
getDrive(id: string): Promise<DocumentDriveDocument>;
|
|
21
|
+
getDriveBySlug(slug: string): Promise<DocumentDriveDocument>;
|
|
22
|
+
deleteDrive(id: string): Promise<void>;
|
|
23
|
+
getSynchronizationUnitsRevision(units: SynchronizationUnitQuery[]): Promise<{
|
|
24
|
+
driveId: string;
|
|
25
|
+
documentId: string;
|
|
26
|
+
scope: string;
|
|
27
|
+
branch: string;
|
|
28
|
+
lastUpdated: string;
|
|
29
|
+
revision: number;
|
|
30
|
+
}[]>;
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=sequelize.d.ts.map
|