document-drive 1.0.0-alpha.65 → 1.0.0-alpha.67
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/package.json +8 -7
- package/src/server/index.ts +12 -0
- package/src/storage/prisma.ts +32 -17
- package/src/storage/types.ts +7 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "document-drive",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.67",
|
|
4
4
|
"license": "AGPL-3.0-only",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"module": "./src/index.ts",
|
|
@@ -34,8 +34,8 @@
|
|
|
34
34
|
"test:watch": "vitest watch"
|
|
35
35
|
},
|
|
36
36
|
"peerDependencies": {
|
|
37
|
-
"document-model": "1.
|
|
38
|
-
"document-model-libs": "^1.
|
|
37
|
+
"document-model": "^1.4.0",
|
|
38
|
+
"document-model-libs": "^1.57.0"
|
|
39
39
|
},
|
|
40
40
|
"optionalDependencies": {
|
|
41
41
|
"@prisma/client": "5.14.0",
|
|
@@ -65,8 +65,8 @@
|
|
|
65
65
|
"@typescript-eslint/eslint-plugin": "^6.21.0",
|
|
66
66
|
"@typescript-eslint/parser": "^6.21.0",
|
|
67
67
|
"@vitest/coverage-v8": "^1.4.0",
|
|
68
|
-
"document-model": "^1.
|
|
69
|
-
"document-model-libs": "^1.
|
|
68
|
+
"document-model": "^1.4.0",
|
|
69
|
+
"document-model-libs": "^1.57.0",
|
|
70
70
|
"eslint": "^8.57.0",
|
|
71
71
|
"eslint-config-prettier": "^9.1.0",
|
|
72
72
|
"fake-indexeddb": "^5.0.2",
|
|
@@ -79,6 +79,7 @@
|
|
|
79
79
|
"sequelize": "^6.37.2",
|
|
80
80
|
"sqlite3": "^5.1.7",
|
|
81
81
|
"typescript": "^5.4.4",
|
|
82
|
-
"vitest": "^1.
|
|
83
|
-
}
|
|
82
|
+
"vitest": "^1.6.0"
|
|
83
|
+
},
|
|
84
|
+
"packageManager": "pnpm@9.1.4+sha256.30a1801ac4e723779efed13a21f4c39f9eb6c9fbb4ced101bce06b422593d7c9"
|
|
84
85
|
}
|
package/src/server/index.ts
CHANGED
|
@@ -103,6 +103,18 @@ export class DocumentDriveServer extends BaseDocumentDriveServer {
|
|
|
103
103
|
this.storage = storage;
|
|
104
104
|
this.cache = cache;
|
|
105
105
|
this.queueManager = queueManager;
|
|
106
|
+
|
|
107
|
+
this.storage.setStorageDelegate?.({
|
|
108
|
+
getCachedOperations: async (drive, id) => {
|
|
109
|
+
try {
|
|
110
|
+
const document = await this.cache.getDocument(drive, id);
|
|
111
|
+
return document?.operations;
|
|
112
|
+
} catch (error) {
|
|
113
|
+
logger.error(error);
|
|
114
|
+
return undefined;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
})
|
|
106
118
|
}
|
|
107
119
|
|
|
108
120
|
private updateSyncStatus(
|
package/src/storage/prisma.ts
CHANGED
|
@@ -21,7 +21,7 @@ import type {
|
|
|
21
21
|
import { IBackOffOptions, backOff } from 'exponential-backoff';
|
|
22
22
|
import { ConflictOperationError } from '../server/error';
|
|
23
23
|
import { logger } from '../utils/logger';
|
|
24
|
-
import { DocumentDriveStorage, DocumentStorage, IDriveStorage } from './types';
|
|
24
|
+
import { DocumentDriveStorage, DocumentStorage, IDriveStorage, IStorageDelegate } from './types';
|
|
25
25
|
|
|
26
26
|
type Transaction =
|
|
27
27
|
| Omit<
|
|
@@ -90,6 +90,7 @@ type ExtendedPrismaClient = ReturnType<
|
|
|
90
90
|
|
|
91
91
|
export class PrismaStorage implements IDriveStorage {
|
|
92
92
|
private db: ExtendedPrismaClient;
|
|
93
|
+
private delegate: IStorageDelegate | undefined;
|
|
93
94
|
|
|
94
95
|
constructor(db: PrismaClient, options?: PrismaStorageOptions) {
|
|
95
96
|
const backOffOptions = options?.transactionRetryBackoff;
|
|
@@ -99,6 +100,10 @@ export class PrismaStorage implements IDriveStorage {
|
|
|
99
100
|
});
|
|
100
101
|
}
|
|
101
102
|
|
|
103
|
+
setStorageDelegate(delegate: IStorageDelegate): void {
|
|
104
|
+
this.delegate = delegate;
|
|
105
|
+
}
|
|
106
|
+
|
|
102
107
|
async createDrive(id: string, drive: DocumentDriveStorage): Promise<void> {
|
|
103
108
|
// drive for all drive documents
|
|
104
109
|
await this.createDocument('drives', id, drive as DocumentStorage);
|
|
@@ -362,12 +367,26 @@ export class PrismaStorage implements IDriveStorage {
|
|
|
362
367
|
throw new Error(`Document with id ${id} not found`);
|
|
363
368
|
}
|
|
364
369
|
|
|
370
|
+
const cachedOperations = await this.delegate?.getCachedOperations(driveId, id) ?? {
|
|
371
|
+
global: [],
|
|
372
|
+
local: []
|
|
373
|
+
};
|
|
374
|
+
const scopeIndex = Object.keys(cachedOperations).reduceRight<Record<OperationScope, number>>((acc, value) => {
|
|
375
|
+
const scope = value as OperationScope;
|
|
376
|
+
const lastIndex = cachedOperations[scope]?.at(-1)?.index ?? -1;
|
|
377
|
+
acc[scope] = lastIndex;
|
|
378
|
+
return acc;
|
|
379
|
+
}, { global: -1, local: -1 });
|
|
380
|
+
|
|
381
|
+
const conditions = Object.entries(scopeIndex).map(([scope, index]) => `("scope" = '${scope}' AND "index" > ${index})`);
|
|
382
|
+
conditions.push(`("scope" NOT IN (${Object.keys(cachedOperations).map(s => `'${s}'`).join(", ")}))`);
|
|
383
|
+
|
|
365
384
|
// retrieves operations with resulting state
|
|
366
385
|
// for the last operation of each scope
|
|
367
|
-
|
|
386
|
+
// TODO prevent SQL injection
|
|
387
|
+
const queryOperations = await prisma.$queryRawUnsafe<
|
|
368
388
|
Prisma.$OperationPayload['scalars'][]
|
|
369
|
-
|
|
370
|
-
WITH ranked_operations AS (
|
|
389
|
+
>(`WITH ranked_operations AS (
|
|
371
390
|
SELECT
|
|
372
391
|
*,
|
|
373
392
|
ROW_NUMBER() OVER (PARTITION BY scope ORDER BY index DESC) AS rn
|
|
@@ -390,21 +409,23 @@ export class PrismaStorage implements IDriveStorage {
|
|
|
390
409
|
ELSE NULL
|
|
391
410
|
END AS "resultingState"
|
|
392
411
|
FROM ranked_operations
|
|
393
|
-
WHERE "driveId" = $
|
|
412
|
+
WHERE "driveId" = $1 AND "documentId" = $2
|
|
413
|
+
AND (${conditions.join(' OR ')})
|
|
394
414
|
ORDER BY scope, index;
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
const operationIds = operations.map(o => o.id);
|
|
415
|
+
`, driveId, id);
|
|
416
|
+
const operationIds = queryOperations.map(o => o.id)
|
|
398
417
|
const attachments = await prisma.attachment.findMany({
|
|
399
418
|
where: {
|
|
400
419
|
operationId: {
|
|
401
420
|
in: operationIds
|
|
402
421
|
}
|
|
403
|
-
}
|
|
422
|
+
},
|
|
404
423
|
});
|
|
405
424
|
|
|
425
|
+
// TODO add attachments from cached operations
|
|
406
426
|
const fileRegistry: FileRegistry = {};
|
|
407
|
-
|
|
427
|
+
|
|
428
|
+
const operationsByScope = queryOperations.reduce<DocumentOperations<Action>>(
|
|
408
429
|
(acc, operation) => {
|
|
409
430
|
const scope = operation.scope as OperationScope;
|
|
410
431
|
if (!acc[scope]) {
|
|
@@ -420,10 +441,7 @@ export class PrismaStorage implements IDriveStorage {
|
|
|
420
441
|
acc[scope].push(result);
|
|
421
442
|
return acc;
|
|
422
443
|
},
|
|
423
|
-
|
|
424
|
-
global: [],
|
|
425
|
-
local: []
|
|
426
|
-
}
|
|
444
|
+
cachedOperations
|
|
427
445
|
);
|
|
428
446
|
|
|
429
447
|
const dbDoc = result;
|
|
@@ -445,9 +463,6 @@ export class PrismaStorage implements IDriveStorage {
|
|
|
445
463
|
>,
|
|
446
464
|
attachments: {}
|
|
447
465
|
};
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
466
|
return doc;
|
|
452
467
|
}
|
|
453
468
|
|
package/src/storage/types.ts
CHANGED
|
@@ -3,12 +3,12 @@ import type {
|
|
|
3
3
|
DocumentDriveDocument,
|
|
4
4
|
} from 'document-model-libs/document-drive';
|
|
5
5
|
import type {
|
|
6
|
+
Action,
|
|
6
7
|
BaseAction,
|
|
7
8
|
Document,
|
|
8
9
|
DocumentHeader,
|
|
9
|
-
|
|
10
|
+
DocumentOperations,
|
|
10
11
|
Operation,
|
|
11
|
-
State
|
|
12
12
|
} from 'document-model/document';
|
|
13
13
|
|
|
14
14
|
export type DocumentStorage<D extends Document = Document> = Omit<
|
|
@@ -18,6 +18,10 @@ export type DocumentStorage<D extends Document = Document> = Omit<
|
|
|
18
18
|
|
|
19
19
|
export type DocumentDriveStorage = DocumentStorage<DocumentDriveDocument>;
|
|
20
20
|
|
|
21
|
+
export interface IStorageDelegate {
|
|
22
|
+
getCachedOperations(drive: string, id: string): Promise<DocumentOperations<Action> | undefined>;
|
|
23
|
+
}
|
|
24
|
+
|
|
21
25
|
export interface IStorage {
|
|
22
26
|
checkDocumentExists(drive: string, id: string): Promise<boolean>;
|
|
23
27
|
getDocuments: (drive: string) => Promise<string[]>;
|
|
@@ -43,8 +47,8 @@ export interface IStorage {
|
|
|
43
47
|
): Promise<void>;
|
|
44
48
|
deleteDocument(drive: string, id: string): Promise<void>;
|
|
45
49
|
getOperationResultingState?(drive: string, id: string, index: number, scope: string, branch: string): Promise<unknown>;
|
|
50
|
+
setStorageDelegate?(delegate: IStorageDelegate): void;
|
|
46
51
|
}
|
|
47
|
-
|
|
48
52
|
export interface IDriveStorage extends IStorage {
|
|
49
53
|
getDrives(): Promise<string[]>;
|
|
50
54
|
getDrive(id: string): Promise<DocumentDriveStorage>;
|