document-drive 1.29.0-dev.1 → 1.29.0-dev.3
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/dist/src/cache/memory.d.ts +15 -8
- package/dist/src/cache/memory.d.ts.map +1 -1
- package/dist/src/cache/memory.js +53 -29
- package/dist/src/cache/redis.d.ts +15 -8
- package/dist/src/cache/redis.d.ts.map +1 -1
- package/dist/src/cache/redis.js +67 -27
- package/dist/src/cache/types.d.ts +10 -3
- package/dist/src/cache/types.d.ts.map +1 -1
- package/dist/src/cache/util.d.ts +3 -0
- package/dist/src/cache/util.d.ts.map +1 -0
- package/dist/src/cache/util.js +13 -0
- package/dist/src/drive-document-model/gen/schema/zod.d.ts +8 -8
- package/dist/src/drive-document-model/gen/schema/zod.d.ts.map +1 -1
- package/dist/src/server/base-server.d.ts.map +1 -1
- package/dist/src/server/base-server.js +35 -15
- package/dist/src/server/sync-manager.js +2 -2
- package/dist/src/storage/browser.d.ts +6 -1
- package/dist/src/storage/browser.d.ts.map +1 -1
- package/dist/src/storage/browser.js +56 -4
- package/dist/src/storage/filesystem.d.ts +5 -1
- package/dist/src/storage/filesystem.d.ts.map +1 -1
- package/dist/src/storage/filesystem.js +59 -6
- package/dist/src/storage/ipfs.d.ts +4 -0
- package/dist/src/storage/ipfs.d.ts.map +1 -1
- package/dist/src/storage/ipfs.js +58 -5
- package/dist/src/storage/memory.d.ts +5 -1
- package/dist/src/storage/memory.d.ts.map +1 -1
- package/dist/src/storage/memory.js +47 -15
- package/dist/src/storage/prisma/factory.d.ts +2 -2
- package/dist/src/storage/prisma/factory.d.ts.map +1 -1
- package/dist/src/storage/prisma/index.d.ts +10 -4
- package/dist/src/storage/prisma/index.d.ts.map +1 -1
- package/dist/src/storage/prisma/index.js +209 -128
- package/dist/src/storage/types.d.ts +5 -4
- package/dist/src/storage/types.d.ts.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { PrismaClientKnownRequestError } from "@prisma/client/runtime/library";
|
|
2
2
|
import { backOff } from "exponential-backoff";
|
|
3
3
|
import { ConflictOperationError } from "../../server/error.js";
|
|
4
|
-
import { logger } from "../../utils/logger.js";
|
|
4
|
+
import { childLogger, logger } from "../../utils/logger.js";
|
|
5
5
|
export * from "./factory.js";
|
|
6
6
|
function storageToOperation(op) {
|
|
7
7
|
const operation = {
|
|
@@ -45,6 +45,7 @@ function getRetryTransactionsClient(prisma, backOffOptions) {
|
|
|
45
45
|
});
|
|
46
46
|
}
|
|
47
47
|
export class PrismaStorage {
|
|
48
|
+
logger = childLogger(["PrismaStorage"]);
|
|
48
49
|
db;
|
|
49
50
|
cache;
|
|
50
51
|
constructor(db, cache, options) {
|
|
@@ -83,9 +84,211 @@ export class PrismaStorage {
|
|
|
83
84
|
id: documentId,
|
|
84
85
|
},
|
|
85
86
|
});
|
|
87
|
+
// temporary -- but we need to create drives automatically for documents
|
|
88
|
+
// of the correct type
|
|
89
|
+
if (document.documentType === "powerhouse/document-drive") {
|
|
90
|
+
const drive = document;
|
|
91
|
+
try {
|
|
92
|
+
await this.db.drive.create({
|
|
93
|
+
data: {
|
|
94
|
+
id: documentId,
|
|
95
|
+
slug: drive.initialState.state.global.slug ?? documentId,
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
catch (e) {
|
|
100
|
+
if (e instanceof PrismaClientKnownRequestError && e.code === "P2002") {
|
|
101
|
+
throw new Error(`Drive with slug ${drive.initialState.state.global.slug ?? documentId} already exists`);
|
|
102
|
+
}
|
|
103
|
+
throw e;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
async get(documentId, tx) {
|
|
108
|
+
const prisma = tx ?? this.db;
|
|
109
|
+
const query = {
|
|
110
|
+
where: {
|
|
111
|
+
id: documentId,
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
const result = await prisma.document.findUnique(query);
|
|
115
|
+
if (result === null) {
|
|
116
|
+
throw new Error(`Document with id ${documentId} not found`);
|
|
117
|
+
}
|
|
118
|
+
let cachedOperations = {
|
|
119
|
+
global: [],
|
|
120
|
+
local: [],
|
|
121
|
+
};
|
|
122
|
+
const cachedDocument = await this.cache.getDocument(documentId);
|
|
123
|
+
if (cachedDocument) {
|
|
124
|
+
cachedOperations = cachedDocument.operations;
|
|
125
|
+
}
|
|
126
|
+
const scopeIndex = Object.keys(cachedOperations).reduceRight((acc, value) => {
|
|
127
|
+
const scope = value;
|
|
128
|
+
const lastIndex = cachedOperations[scope].at(-1)?.index ?? -1;
|
|
129
|
+
acc[scope] = lastIndex;
|
|
130
|
+
return acc;
|
|
131
|
+
}, { global: -1, local: -1 });
|
|
132
|
+
const conditions = Object.entries(scopeIndex).map(([scope, index]) => `("scope" = '${scope}' AND "index" > ${index})`);
|
|
133
|
+
conditions.push(`("scope" NOT IN (${Object.keys(cachedOperations)
|
|
134
|
+
.map((s) => `'${s}'`)
|
|
135
|
+
.join(", ")}))`);
|
|
136
|
+
// retrieves operations with resulting state
|
|
137
|
+
// for the last operation of each scope
|
|
138
|
+
// TODO prevent SQL injection
|
|
139
|
+
const queryOperations = await prisma.$queryRawUnsafe(`WITH ranked_operations AS (
|
|
140
|
+
SELECT
|
|
141
|
+
*,
|
|
142
|
+
ROW_NUMBER() OVER (PARTITION BY scope ORDER BY index DESC) AS rn
|
|
143
|
+
FROM "Operation"
|
|
144
|
+
)
|
|
145
|
+
SELECT
|
|
146
|
+
"id",
|
|
147
|
+
"opId",
|
|
148
|
+
"scope",
|
|
149
|
+
"branch",
|
|
150
|
+
"index",
|
|
151
|
+
"skip",
|
|
152
|
+
"hash",
|
|
153
|
+
"timestamp",
|
|
154
|
+
"input",
|
|
155
|
+
"type",
|
|
156
|
+
"context",
|
|
157
|
+
CASE
|
|
158
|
+
WHEN rn = 1 THEN "resultingState"
|
|
159
|
+
ELSE NULL
|
|
160
|
+
END AS "resultingState"
|
|
161
|
+
FROM ranked_operations
|
|
162
|
+
WHERE "documentId" = $1
|
|
163
|
+
AND (${conditions.join(" OR ")})
|
|
164
|
+
ORDER BY scope, index;
|
|
165
|
+
`, documentId);
|
|
166
|
+
const operationIds = queryOperations.map((o) => o.id);
|
|
167
|
+
const attachments = await prisma.attachment.findMany({
|
|
168
|
+
where: {
|
|
169
|
+
operationId: {
|
|
170
|
+
in: operationIds,
|
|
171
|
+
},
|
|
172
|
+
},
|
|
173
|
+
});
|
|
174
|
+
// TODO add attachments from cached operations
|
|
175
|
+
const fileRegistry = {};
|
|
176
|
+
const operationsByScope = queryOperations.reduce((acc, operation) => {
|
|
177
|
+
const scope = operation.scope;
|
|
178
|
+
if (!acc[scope]) {
|
|
179
|
+
acc[scope] = [];
|
|
180
|
+
}
|
|
181
|
+
const result = storageToOperation(operation);
|
|
182
|
+
result.attachments = attachments.filter((a) => a.operationId === operation.id);
|
|
183
|
+
result.attachments.forEach(({ hash, ...file }) => {
|
|
184
|
+
fileRegistry[hash] = file;
|
|
185
|
+
});
|
|
186
|
+
acc[scope].push(result);
|
|
187
|
+
return acc;
|
|
188
|
+
}, cachedOperations);
|
|
189
|
+
const dbDoc = result;
|
|
190
|
+
const doc = {
|
|
191
|
+
created: dbDoc.created.toISOString(),
|
|
192
|
+
name: dbDoc.name ? dbDoc.name : "",
|
|
193
|
+
documentType: dbDoc.documentType,
|
|
194
|
+
initialState: JSON.parse(dbDoc.initialState),
|
|
195
|
+
state: undefined,
|
|
196
|
+
lastModified: new Date(dbDoc.lastModified).toISOString(),
|
|
197
|
+
operations: operationsByScope,
|
|
198
|
+
clipboard: [],
|
|
199
|
+
revision: JSON.parse(dbDoc.revision),
|
|
200
|
+
meta: dbDoc.meta ? JSON.parse(dbDoc.meta) : undefined,
|
|
201
|
+
attachments: {},
|
|
202
|
+
};
|
|
203
|
+
return doc;
|
|
86
204
|
}
|
|
87
|
-
async
|
|
88
|
-
|
|
205
|
+
async delete(documentId) {
|
|
206
|
+
try {
|
|
207
|
+
// delete out of drives
|
|
208
|
+
await this.db.drive.deleteMany({
|
|
209
|
+
where: {
|
|
210
|
+
driveDocuments: {
|
|
211
|
+
none: {
|
|
212
|
+
documentId,
|
|
213
|
+
},
|
|
214
|
+
},
|
|
215
|
+
},
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
catch (e) {
|
|
219
|
+
this.logger.error("Error deleting document from drives, could not delete DriveDocument links", e);
|
|
220
|
+
return false;
|
|
221
|
+
}
|
|
222
|
+
try {
|
|
223
|
+
// delete document
|
|
224
|
+
const result = await this.db.document.deleteMany({
|
|
225
|
+
where: {
|
|
226
|
+
id: documentId,
|
|
227
|
+
},
|
|
228
|
+
});
|
|
229
|
+
return result.count > 0;
|
|
230
|
+
}
|
|
231
|
+
catch (e) {
|
|
232
|
+
this.logger.error("Error deleting document from drives, could not delete Document", e);
|
|
233
|
+
const prismaError = e;
|
|
234
|
+
// Ignore Error: P2025: An operation failed because it depends on one or more records that were required but not found.
|
|
235
|
+
if ((prismaError.code && prismaError.code === "P2025") ||
|
|
236
|
+
prismaError.message?.includes("An operation failed because it depends on one or more records that were required but not found.")) {
|
|
237
|
+
return false;
|
|
238
|
+
}
|
|
239
|
+
throw e;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
async addChild(parentId, childId) {
|
|
243
|
+
if (parentId === childId) {
|
|
244
|
+
throw new Error("Cannot associate a document with itself");
|
|
245
|
+
}
|
|
246
|
+
// check if the child is a parent of the parent
|
|
247
|
+
const children = await this.getChildren(childId);
|
|
248
|
+
if (children.includes(parentId)) {
|
|
249
|
+
throw new Error("Cannot associate a document with its child");
|
|
250
|
+
}
|
|
251
|
+
// create the many-to-many relation
|
|
252
|
+
await this.db.document.update({
|
|
253
|
+
where: {
|
|
254
|
+
id: childId,
|
|
255
|
+
},
|
|
256
|
+
data: {
|
|
257
|
+
driveDocuments: { create: { driveId: parentId } },
|
|
258
|
+
},
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
async removeChild(parentId, childId) {
|
|
262
|
+
try {
|
|
263
|
+
await this.db.driveDocument.delete({
|
|
264
|
+
where: {
|
|
265
|
+
// use unique constraint so it either deletes or throws
|
|
266
|
+
driveId_documentId: {
|
|
267
|
+
driveId: parentId,
|
|
268
|
+
documentId: childId,
|
|
269
|
+
},
|
|
270
|
+
},
|
|
271
|
+
});
|
|
272
|
+
return true;
|
|
273
|
+
}
|
|
274
|
+
catch (e) {
|
|
275
|
+
return false;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
async getChildren(parentId) {
|
|
279
|
+
const docs = await this.db.document.findMany({
|
|
280
|
+
select: {
|
|
281
|
+
id: true,
|
|
282
|
+
},
|
|
283
|
+
where: {
|
|
284
|
+
driveDocuments: {
|
|
285
|
+
some: {
|
|
286
|
+
driveId: parentId,
|
|
287
|
+
},
|
|
288
|
+
},
|
|
289
|
+
},
|
|
290
|
+
});
|
|
291
|
+
return docs.map((doc) => doc.id);
|
|
89
292
|
}
|
|
90
293
|
////////////////////////////////
|
|
91
294
|
// IDriveStorage
|
|
@@ -266,133 +469,11 @@ export class PrismaStorage {
|
|
|
266
469
|
});
|
|
267
470
|
return count > 0;
|
|
268
471
|
}
|
|
269
|
-
async getDocument(driveId,
|
|
270
|
-
|
|
271
|
-
const query = {
|
|
272
|
-
where: {
|
|
273
|
-
id,
|
|
274
|
-
},
|
|
275
|
-
};
|
|
276
|
-
const result = await prisma.document.findUnique(query);
|
|
277
|
-
if (result === null) {
|
|
278
|
-
throw new Error(`Document with id ${id} not found`);
|
|
279
|
-
}
|
|
280
|
-
const cachedOperations = (await this.cache.getCachedOperations(driveId, id)) ?? {
|
|
281
|
-
global: [],
|
|
282
|
-
local: [],
|
|
283
|
-
};
|
|
284
|
-
const scopeIndex = Object.keys(cachedOperations).reduceRight((acc, value) => {
|
|
285
|
-
const scope = value;
|
|
286
|
-
const lastIndex = cachedOperations[scope].at(-1)?.index ?? -1;
|
|
287
|
-
acc[scope] = lastIndex;
|
|
288
|
-
return acc;
|
|
289
|
-
}, { global: -1, local: -1 });
|
|
290
|
-
const conditions = Object.entries(scopeIndex).map(([scope, index]) => `("scope" = '${scope}' AND "index" > ${index})`);
|
|
291
|
-
conditions.push(`("scope" NOT IN (${Object.keys(cachedOperations)
|
|
292
|
-
.map((s) => `'${s}'`)
|
|
293
|
-
.join(", ")}))`);
|
|
294
|
-
// retrieves operations with resulting state
|
|
295
|
-
// for the last operation of each scope
|
|
296
|
-
// TODO prevent SQL injection
|
|
297
|
-
const queryOperations = await prisma.$queryRawUnsafe(`WITH ranked_operations AS (
|
|
298
|
-
SELECT
|
|
299
|
-
*,
|
|
300
|
-
ROW_NUMBER() OVER (PARTITION BY scope ORDER BY index DESC) AS rn
|
|
301
|
-
FROM "Operation"
|
|
302
|
-
)
|
|
303
|
-
SELECT
|
|
304
|
-
"id",
|
|
305
|
-
"opId",
|
|
306
|
-
"scope",
|
|
307
|
-
"branch",
|
|
308
|
-
"index",
|
|
309
|
-
"skip",
|
|
310
|
-
"hash",
|
|
311
|
-
"timestamp",
|
|
312
|
-
"input",
|
|
313
|
-
"type",
|
|
314
|
-
"context",
|
|
315
|
-
CASE
|
|
316
|
-
WHEN rn = 1 THEN "resultingState"
|
|
317
|
-
ELSE NULL
|
|
318
|
-
END AS "resultingState"
|
|
319
|
-
FROM ranked_operations
|
|
320
|
-
WHERE "documentId" = $1
|
|
321
|
-
AND (${conditions.join(" OR ")})
|
|
322
|
-
ORDER BY scope, index;
|
|
323
|
-
`, id);
|
|
324
|
-
const operationIds = queryOperations.map((o) => o.id);
|
|
325
|
-
const attachments = await prisma.attachment.findMany({
|
|
326
|
-
where: {
|
|
327
|
-
operationId: {
|
|
328
|
-
in: operationIds,
|
|
329
|
-
},
|
|
330
|
-
},
|
|
331
|
-
});
|
|
332
|
-
// TODO add attachments from cached operations
|
|
333
|
-
const fileRegistry = {};
|
|
334
|
-
const operationsByScope = queryOperations.reduce((acc, operation) => {
|
|
335
|
-
const scope = operation.scope;
|
|
336
|
-
if (!acc[scope]) {
|
|
337
|
-
acc[scope] = [];
|
|
338
|
-
}
|
|
339
|
-
const result = storageToOperation(operation);
|
|
340
|
-
result.attachments = attachments.filter((a) => a.operationId === operation.id);
|
|
341
|
-
result.attachments.forEach(({ hash, ...file }) => {
|
|
342
|
-
fileRegistry[hash] = file;
|
|
343
|
-
});
|
|
344
|
-
acc[scope].push(result);
|
|
345
|
-
return acc;
|
|
346
|
-
}, cachedOperations);
|
|
347
|
-
const dbDoc = result;
|
|
348
|
-
const doc = {
|
|
349
|
-
created: dbDoc.created.toISOString(),
|
|
350
|
-
name: dbDoc.name ? dbDoc.name : "",
|
|
351
|
-
documentType: dbDoc.documentType,
|
|
352
|
-
initialState: JSON.parse(dbDoc.initialState),
|
|
353
|
-
state: undefined,
|
|
354
|
-
lastModified: new Date(dbDoc.lastModified).toISOString(),
|
|
355
|
-
operations: operationsByScope,
|
|
356
|
-
clipboard: [],
|
|
357
|
-
revision: JSON.parse(dbDoc.revision),
|
|
358
|
-
meta: dbDoc.meta ? JSON.parse(dbDoc.meta) : undefined,
|
|
359
|
-
attachments: {},
|
|
360
|
-
};
|
|
361
|
-
return doc;
|
|
472
|
+
async getDocument(driveId, documentId, tx) {
|
|
473
|
+
return this.get(documentId, tx);
|
|
362
474
|
}
|
|
363
475
|
async deleteDocument(drive, id) {
|
|
364
|
-
|
|
365
|
-
// delete out of drives
|
|
366
|
-
await this.db.drive.deleteMany({
|
|
367
|
-
where: {
|
|
368
|
-
driveDocuments: {
|
|
369
|
-
none: {
|
|
370
|
-
documentId: id,
|
|
371
|
-
},
|
|
372
|
-
},
|
|
373
|
-
},
|
|
374
|
-
});
|
|
375
|
-
// delete document
|
|
376
|
-
await this.db.document.deleteMany({
|
|
377
|
-
where: {
|
|
378
|
-
driveDocuments: {
|
|
379
|
-
some: {
|
|
380
|
-
driveId: drive,
|
|
381
|
-
},
|
|
382
|
-
},
|
|
383
|
-
id,
|
|
384
|
-
},
|
|
385
|
-
});
|
|
386
|
-
}
|
|
387
|
-
catch (e) {
|
|
388
|
-
const prismaError = e;
|
|
389
|
-
// Ignore Error: P2025: An operation failed because it depends on one or more records that were required but not found.
|
|
390
|
-
if ((prismaError.code && prismaError.code === "P2025") ||
|
|
391
|
-
prismaError.message?.includes("An operation failed because it depends on one or more records that were required but not found.")) {
|
|
392
|
-
return;
|
|
393
|
-
}
|
|
394
|
-
throw e;
|
|
395
|
-
}
|
|
476
|
+
await this.delete(id);
|
|
396
477
|
}
|
|
397
478
|
async getDrives() {
|
|
398
479
|
const drives = await this.db.drive.findMany({
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { type DocumentDriveDocument } from "#drive-document-model/gen/types";
|
|
2
2
|
import { type SynchronizationUnitQuery } from "#server/types";
|
|
3
|
-
import type { DocumentHeader, Operation, OperationFromDocument,
|
|
4
|
-
export interface IOperationsCache {
|
|
5
|
-
getCachedOperations<TDocument extends PHDocument = PHDocument>(drive: string, id: string): Promise<OperationsFromDocument<TDocument> | undefined>;
|
|
6
|
-
}
|
|
3
|
+
import type { DocumentHeader, Operation, OperationFromDocument, PHDocument } from "document-model";
|
|
7
4
|
export interface IDocumentStorage {
|
|
8
5
|
exists(documentId: string): Promise<boolean>;
|
|
9
6
|
create(documentId: string, document: PHDocument): Promise<void>;
|
|
10
7
|
get<TDocument extends PHDocument>(documentId: string): Promise<TDocument>;
|
|
8
|
+
delete(documentId: string): Promise<boolean>;
|
|
9
|
+
addChild(parentId: string, childId: string): Promise<void>;
|
|
10
|
+
removeChild(parentId: string, childId: string): Promise<boolean>;
|
|
11
|
+
getChildren(parentId: string): Promise<string[]>;
|
|
11
12
|
}
|
|
12
13
|
export interface IStorage {
|
|
13
14
|
checkDocumentExists(drive: string, id: string): Promise<boolean>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/storage/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,qBAAqB,EAAE,MAAM,iCAAiC,CAAC;AAC7E,OAAO,EAAE,KAAK,wBAAwB,EAAE,MAAM,eAAe,CAAC;AAC9D,OAAO,KAAK,EACV,cAAc,EACd,SAAS,EACT,qBAAqB,EACrB,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/storage/types.ts"],"names":[],"mappings":"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"}
|