document-drive 1.29.0-dev.1 → 1.29.0-dev.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/dist/src/cache/memory.d.ts +15 -8
  2. package/dist/src/cache/memory.d.ts.map +1 -1
  3. package/dist/src/cache/memory.js +53 -29
  4. package/dist/src/cache/redis.d.ts +15 -8
  5. package/dist/src/cache/redis.d.ts.map +1 -1
  6. package/dist/src/cache/redis.js +67 -27
  7. package/dist/src/cache/types.d.ts +10 -3
  8. package/dist/src/cache/types.d.ts.map +1 -1
  9. package/dist/src/cache/util.d.ts +3 -0
  10. package/dist/src/cache/util.d.ts.map +1 -0
  11. package/dist/src/cache/util.js +13 -0
  12. package/dist/src/drive-document-model/gen/schema/zod.d.ts +8 -8
  13. package/dist/src/drive-document-model/gen/schema/zod.d.ts.map +1 -1
  14. package/dist/src/server/base-server.d.ts.map +1 -1
  15. package/dist/src/server/base-server.js +13 -15
  16. package/dist/src/server/sync-manager.js +2 -2
  17. package/dist/src/storage/browser.d.ts +6 -1
  18. package/dist/src/storage/browser.d.ts.map +1 -1
  19. package/dist/src/storage/browser.js +56 -4
  20. package/dist/src/storage/filesystem.d.ts +5 -1
  21. package/dist/src/storage/filesystem.d.ts.map +1 -1
  22. package/dist/src/storage/filesystem.js +59 -6
  23. package/dist/src/storage/ipfs.d.ts +4 -0
  24. package/dist/src/storage/ipfs.d.ts.map +1 -1
  25. package/dist/src/storage/ipfs.js +58 -5
  26. package/dist/src/storage/memory.d.ts +5 -1
  27. package/dist/src/storage/memory.d.ts.map +1 -1
  28. package/dist/src/storage/memory.js +47 -15
  29. package/dist/src/storage/prisma/factory.d.ts +2 -2
  30. package/dist/src/storage/prisma/factory.d.ts.map +1 -1
  31. package/dist/src/storage/prisma/index.d.ts +10 -4
  32. package/dist/src/storage/prisma/index.d.ts.map +1 -1
  33. package/dist/src/storage/prisma/index.js +209 -128
  34. package/dist/src/storage/types.d.ts +5 -4
  35. package/dist/src/storage/types.d.ts.map +1 -1
  36. package/dist/tsconfig.tsbuildinfo +1 -1
  37. package/package.json +3 -3
@@ -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 get(documentId) {
88
- throw new Error("Not implemented");
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, id, tx) {
270
- const prisma = tx ?? this.db;
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
- try {
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, OperationsFromDocument, PHDocument } from "document-model";
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":"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"}
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,UAAU,EACX,MAAM,gBAAgB,CAAC;AAExB,MAAM,WAAW,gBAAgB;IAC/B,MAAM,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC7C,MAAM,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAChE,GAAG,CAAC,SAAS,SAAS,UAAU,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC;IAC1E,MAAM,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAE7C,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3D,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACjE,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;CAElD;AAED,MAAM,WAAW,QAAQ;IACvB,mBAAmB,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACjE,YAAY,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IACnD,WAAW,CAAC,SAAS,SAAS,UAAU,EACtC,KAAK,EAAE,MAAM,EACb,EAAE,EAAE,MAAM,GACT,OAAO,CAAC,SAAS,CAAC,CAAC;IACtB,cAAc,CACZ,KAAK,EAAE,MAAM,EACb,EAAE,EAAE,MAAM,EACV,QAAQ,EAAE,UAAU,GACnB,OAAO,CAAC,IAAI,CAAC,CAAC;IACjB,qBAAqB,CAAC,SAAS,SAAS,UAAU,EAChD,KAAK,EAAE,MAAM,EACb,EAAE,EAAE,MAAM,EACV,UAAU,EAAE,qBAAqB,CAAC,SAAS,CAAC,EAAE,EAC9C,MAAM,EAAE,cAAc,GACrB,OAAO,CAAC,IAAI,CAAC,CAAC;IACjB,oCAAoC,CAAC,CAAC,SAAS,SAAS,UAAU,EAChE,KAAK,EAAE,MAAM,EACb,EAAE,EAAE,MAAM,EACV,QAAQ,EAAE,CAAC,QAAQ,EAAE,SAAS,KAAK,OAAO,CAAC;QACzC,UAAU,EAAE,qBAAqB,CAAC,SAAS,CAAC,EAAE,CAAC;QAC/C,MAAM,EAAE,cAAc,CAAC;KACxB,CAAC,GACD,OAAO,CAAC,IAAI,CAAC,CAAC;IACjB,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACzD,0BAA0B,CAAC,CACzB,KAAK,EAAE,MAAM,EACb,EAAE,EAAE,MAAM,EACV,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IAC/B,+BAA+B,CAAC,KAAK,EAAE,wBAAwB,EAAE,GAAG,OAAO,CACzE;QACE,UAAU,EAAE,MAAM,CAAC;QACnB,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,MAAM,CAAC;QACf,WAAW,EAAE,MAAM,CAAC;QACpB,QAAQ,EAAE,MAAM,CAAC;KAClB,EAAE,CACJ,CAAC;CACH;AACD,MAAM,WAAW,aAAc,SAAQ,QAAQ;IAC7C,SAAS,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAC/B,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,qBAAqB,CAAC,CAAC;IACrD,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,qBAAqB,CAAC,CAAC;IAC7D,WAAW,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACrE,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACvC,YAAY,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/B,kBAAkB,CAChB,EAAE,EAAE,MAAM,EACV,UAAU,EAAE,SAAS,EAAE,EACvB,MAAM,EAAE,cAAc,GACrB,OAAO,CAAC,IAAI,CAAC,CAAC;IACjB,iCAAiC,CAAC,CAChC,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,CAAC,QAAQ,EAAE,qBAAqB,KAAK,OAAO,CAAC;QACrD,UAAU,EAAE,SAAS,EAAE,CAAC;QACxB,MAAM,EAAE,cAAc,CAAC;KACxB,CAAC,GACD,OAAO,CAAC,IAAI,CAAC,CAAC;IACjB,+BAA+B,CAAC,CAC9B,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;CAChC"}