document-drive 1.0.0-alpha.2 → 1.0.0-alpha.21
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 +5 -5
- package/src/server/index.ts +256 -90
- package/src/server/listener/manager.ts +11 -29
- package/src/server/listener/transmitter/index.ts +1 -0
- package/src/server/listener/transmitter/internal.ts +90 -0
- package/src/server/listener/transmitter/pull-responder.ts +32 -0
- package/src/server/types.ts +43 -2
- package/src/server/utils.ts +34 -0
- package/src/storage/browser.ts +4 -0
- package/src/storage/filesystem.ts +38 -0
- package/src/storage/memory.ts +5 -0
- package/src/storage/prisma.ts +33 -74
- package/src/storage/types.ts +1 -0
- package/src/utils/index.ts +5 -0
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.21",
|
|
4
4
|
"license": "AGPL-3.0-only",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"module": "./src/index.ts",
|
|
@@ -30,8 +30,8 @@
|
|
|
30
30
|
},
|
|
31
31
|
"peerDependencies": {
|
|
32
32
|
"@prisma/client": "5.8.1",
|
|
33
|
-
"document-model": "^1.0.
|
|
34
|
-
"document-model-libs": "^1.1.
|
|
33
|
+
"document-model": "^1.0.34",
|
|
34
|
+
"document-model-libs": "^1.1.51",
|
|
35
35
|
"localforage": "^1.10.0",
|
|
36
36
|
"sequelize": "^6.35.2",
|
|
37
37
|
"sqlite3": "^5.1.7"
|
|
@@ -54,8 +54,8 @@
|
|
|
54
54
|
"@typescript-eslint/eslint-plugin": "^6.18.1",
|
|
55
55
|
"@typescript-eslint/parser": "^6.18.1",
|
|
56
56
|
"@vitest/coverage-v8": "^0.34.6",
|
|
57
|
-
"document-model": "^1.0.
|
|
58
|
-
"document-model-libs": "^1.1
|
|
57
|
+
"document-model": "^1.0.34",
|
|
58
|
+
"document-model-libs": "^1.17.1",
|
|
59
59
|
"eslint": "^8.56.0",
|
|
60
60
|
"eslint-config-prettier": "^9.1.0",
|
|
61
61
|
"fake-indexeddb": "^5.0.1",
|
package/src/server/index.ts
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
|
+
actions,
|
|
3
|
+
AddListenerInput,
|
|
2
4
|
DocumentDriveAction,
|
|
3
5
|
DocumentDriveDocument,
|
|
4
6
|
DocumentDriveState,
|
|
5
7
|
FileNode,
|
|
6
8
|
isFileNode,
|
|
9
|
+
ListenerFilter,
|
|
10
|
+
RemoveListenerInput,
|
|
7
11
|
Trigger,
|
|
8
12
|
utils
|
|
9
13
|
} from 'document-model-libs/document-drive';
|
|
@@ -19,18 +23,26 @@ import {
|
|
|
19
23
|
import { createNanoEvents, Unsubscribe } from 'nanoevents';
|
|
20
24
|
import { MemoryStorage } from '../storage/memory';
|
|
21
25
|
import type { DocumentStorage, IDriveStorage } from '../storage/types';
|
|
22
|
-
import {
|
|
26
|
+
import {
|
|
27
|
+
generateUUID,
|
|
28
|
+
isBefore,
|
|
29
|
+
isDocumentDrive,
|
|
30
|
+
isNoopUpdate
|
|
31
|
+
} from '../utils';
|
|
23
32
|
import { requestPublicDrive } from '../utils/graphql';
|
|
24
33
|
import { OperationError } from './error';
|
|
25
34
|
import { ListenerManager } from './listener/manager';
|
|
26
35
|
import {
|
|
27
36
|
CancelPullLoop,
|
|
37
|
+
InternalTransmitter,
|
|
38
|
+
IReceiver,
|
|
28
39
|
ITransmitter,
|
|
29
40
|
PullResponderTransmitter
|
|
30
41
|
} from './listener/transmitter';
|
|
31
42
|
import {
|
|
32
43
|
BaseDocumentDriveServer,
|
|
33
44
|
DriveEvents,
|
|
45
|
+
GetDocumentOptions,
|
|
34
46
|
IOperationResult,
|
|
35
47
|
ListenerState,
|
|
36
48
|
RemoteDriveOptions,
|
|
@@ -42,6 +54,7 @@ import {
|
|
|
42
54
|
type SignalResult,
|
|
43
55
|
type SynchronizationUnit
|
|
44
56
|
} from './types';
|
|
57
|
+
import { filterOperationsByRevision } from './utils';
|
|
45
58
|
|
|
46
59
|
export * from './listener';
|
|
47
60
|
export type * from './types';
|
|
@@ -71,11 +84,15 @@ export class DocumentDriveServer extends BaseDocumentDriveServer {
|
|
|
71
84
|
|
|
72
85
|
private updateSyncStatus(
|
|
73
86
|
driveId: string,
|
|
74
|
-
status: SyncStatus,
|
|
87
|
+
status: SyncStatus | null,
|
|
75
88
|
error?: Error
|
|
76
89
|
) {
|
|
77
|
-
|
|
78
|
-
|
|
90
|
+
if (status === null) {
|
|
91
|
+
this.syncStatus.delete(driveId);
|
|
92
|
+
} else if (this.getSyncStatus(driveId) !== status) {
|
|
93
|
+
this.syncStatus.set(driveId, status);
|
|
94
|
+
this.emit('syncStatus', driveId, status, error);
|
|
95
|
+
}
|
|
79
96
|
}
|
|
80
97
|
|
|
81
98
|
private async saveStrand(strand: StrandUpdate) {
|
|
@@ -178,17 +195,40 @@ export class DocumentDriveServer extends BaseDocumentDriveServer {
|
|
|
178
195
|
private async stopSyncRemoteDrive(driveId: string) {
|
|
179
196
|
const triggers = this.triggerMap.get(driveId);
|
|
180
197
|
triggers?.forEach(cancel => cancel());
|
|
198
|
+
this.updateSyncStatus(driveId, null);
|
|
181
199
|
return this.triggerMap.delete(driveId);
|
|
182
200
|
}
|
|
183
201
|
|
|
184
202
|
async initialize() {
|
|
185
203
|
await this.listenerStateManager.init();
|
|
186
204
|
const drives = await this.getDrives();
|
|
187
|
-
for (const
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
205
|
+
for (const drive of drives) {
|
|
206
|
+
await this._initializeDrive(drive);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
private async _initializeDrive(driveId: string) {
|
|
211
|
+
const drive = await this.getDrive(driveId);
|
|
212
|
+
|
|
213
|
+
if (this.shouldSyncRemoteDrive(drive)) {
|
|
214
|
+
await this.startSyncRemoteDrive(driveId);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
for (const listener of drive.state.local.listeners) {
|
|
218
|
+
await this.listenerStateManager.addListener({
|
|
219
|
+
driveId,
|
|
220
|
+
block: listener.block,
|
|
221
|
+
filter: {
|
|
222
|
+
branch: listener.filter.branch ?? [],
|
|
223
|
+
documentId: listener.filter.documentId ?? [],
|
|
224
|
+
documentType: listener.filter.documentType ?? [],
|
|
225
|
+
scope: listener.filter.scope ?? []
|
|
226
|
+
},
|
|
227
|
+
listenerId: listener.listenerId,
|
|
228
|
+
system: listener.system,
|
|
229
|
+
callInfo: listener.callInfo ?? undefined,
|
|
230
|
+
label: listener.label ?? ''
|
|
231
|
+
});
|
|
192
232
|
}
|
|
193
233
|
}
|
|
194
234
|
|
|
@@ -203,13 +243,38 @@ export class DocumentDriveServer extends BaseDocumentDriveServer {
|
|
|
203
243
|
const nodes = drive.state.global.nodes.filter(
|
|
204
244
|
node =>
|
|
205
245
|
isFileNode(node) &&
|
|
206
|
-
(!documentId?.length ||
|
|
207
|
-
|
|
246
|
+
(!documentId?.length ||
|
|
247
|
+
documentId.includes(node.id) ||
|
|
248
|
+
documentId.includes('*'))
|
|
249
|
+
) as Pick<
|
|
250
|
+
FileNode,
|
|
251
|
+
'id' | 'documentType' | 'scopes' | 'synchronizationUnits'
|
|
252
|
+
>[];
|
|
208
253
|
|
|
209
254
|
if (documentId && !nodes.length) {
|
|
210
255
|
throw new Error('File node not found');
|
|
211
256
|
}
|
|
212
257
|
|
|
258
|
+
// checks if document drive synchronization unit should be added
|
|
259
|
+
if (
|
|
260
|
+
!documentId ||
|
|
261
|
+
documentId.includes('*') ||
|
|
262
|
+
documentId.includes('')
|
|
263
|
+
) {
|
|
264
|
+
nodes.unshift({
|
|
265
|
+
id: '',
|
|
266
|
+
documentType: 'powerhouse/document-drive',
|
|
267
|
+
scopes: ['global'],
|
|
268
|
+
synchronizationUnits: [
|
|
269
|
+
{
|
|
270
|
+
syncId: '0',
|
|
271
|
+
scope: 'global',
|
|
272
|
+
branch: 'main'
|
|
273
|
+
}
|
|
274
|
+
]
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
|
|
213
278
|
const synchronizationUnits: SynchronizationUnit[] = [];
|
|
214
279
|
|
|
215
280
|
for (const node of nodes) {
|
|
@@ -225,7 +290,9 @@ export class DocumentDriveServer extends BaseDocumentDriveServer {
|
|
|
225
290
|
continue;
|
|
226
291
|
}
|
|
227
292
|
|
|
228
|
-
const document = await
|
|
293
|
+
const document = await (node.id
|
|
294
|
+
? this.getDocument(driveId, node.id)
|
|
295
|
+
: this.getDrive(driveId));
|
|
229
296
|
|
|
230
297
|
for (const { syncId, scope, branch } of nodeUnits) {
|
|
231
298
|
const operations =
|
|
@@ -305,10 +372,10 @@ export class DocumentDriveServer extends BaseDocumentDriveServer {
|
|
|
305
372
|
const filteredOperations = operations.filter(
|
|
306
373
|
operation =>
|
|
307
374
|
Object.keys(filter).length === 0 ||
|
|
308
|
-
(filter.since
|
|
309
|
-
filter.since
|
|
310
|
-
|
|
311
|
-
|
|
375
|
+
((filter.since === undefined ||
|
|
376
|
+
isBefore(filter.since, operation.timestamp)) &&
|
|
377
|
+
(filter.fromRevision === undefined ||
|
|
378
|
+
operation.index > filter.fromRevision))
|
|
312
379
|
);
|
|
313
380
|
|
|
314
381
|
return filteredOperations.map(operation => ({
|
|
@@ -349,30 +416,7 @@ export class DocumentDriveServer extends BaseDocumentDriveServer {
|
|
|
349
416
|
});
|
|
350
417
|
|
|
351
418
|
await this.storage.createDrive(id, document);
|
|
352
|
-
|
|
353
|
-
// add listeners to state manager
|
|
354
|
-
for (const listener of drive.local.listeners) {
|
|
355
|
-
await this.listenerStateManager.addListener({
|
|
356
|
-
block: listener.block,
|
|
357
|
-
driveId: id,
|
|
358
|
-
filter: {
|
|
359
|
-
branch: listener.filter.branch ?? [],
|
|
360
|
-
documentId: listener.filter.documentId ?? [],
|
|
361
|
-
documentType: listener.filter.documentType ?? [],
|
|
362
|
-
scope: listener.filter.scope ?? []
|
|
363
|
-
},
|
|
364
|
-
listenerId: listener.listenerId,
|
|
365
|
-
system: listener.system,
|
|
366
|
-
callInfo: listener.callInfo ?? undefined,
|
|
367
|
-
label: listener.label ?? ''
|
|
368
|
-
});
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
// if it is a remote drive that should be available offline, starts
|
|
372
|
-
// the sync process to pull changes from remote every 30 seconds
|
|
373
|
-
if (this.shouldSyncRemoteDrive(document)) {
|
|
374
|
-
await this.startSyncRemoteDrive(id);
|
|
375
|
-
}
|
|
419
|
+
await this._initializeDrive(id);
|
|
376
420
|
}
|
|
377
421
|
|
|
378
422
|
async addRemoteDrive(url: string, options: RemoteDriveOptions) {
|
|
@@ -385,26 +429,12 @@ export class DocumentDriveServer extends BaseDocumentDriveServer {
|
|
|
385
429
|
listeners,
|
|
386
430
|
triggers
|
|
387
431
|
} = options;
|
|
388
|
-
const listenerId = await PullResponderTransmitter.registerPullResponder(
|
|
389
|
-
id,
|
|
390
|
-
url,
|
|
391
|
-
pullFilter ?? {
|
|
392
|
-
documentId: ['*'],
|
|
393
|
-
documentType: ['*'],
|
|
394
|
-
branch: ['*'],
|
|
395
|
-
scope: ['*']
|
|
396
|
-
}
|
|
397
|
-
);
|
|
398
432
|
|
|
399
|
-
const pullTrigger
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
listenerId,
|
|
405
|
-
interval: pullInterval?.toString() ?? ''
|
|
406
|
-
}
|
|
407
|
-
};
|
|
433
|
+
const pullTrigger =
|
|
434
|
+
await PullResponderTransmitter.createPullResponderTrigger(id, url, {
|
|
435
|
+
pullFilter,
|
|
436
|
+
pullInterval
|
|
437
|
+
});
|
|
408
438
|
|
|
409
439
|
return await this.addDrive({
|
|
410
440
|
global: {
|
|
@@ -431,12 +461,15 @@ export class DocumentDriveServer extends BaseDocumentDriveServer {
|
|
|
431
461
|
return this.storage.getDrives();
|
|
432
462
|
}
|
|
433
463
|
|
|
434
|
-
async getDrive(drive: string) {
|
|
464
|
+
async getDrive(drive: string, options?: GetDocumentOptions) {
|
|
435
465
|
const driveStorage = await this.storage.getDrive(drive);
|
|
436
466
|
const documentModel = this._getDocumentModel(driveStorage.documentType);
|
|
437
467
|
const document = baseUtils.replayDocument(
|
|
438
468
|
driveStorage.initialState,
|
|
439
|
-
|
|
469
|
+
filterOperationsByRevision(
|
|
470
|
+
driveStorage.operations,
|
|
471
|
+
options?.revisions
|
|
472
|
+
),
|
|
440
473
|
documentModel.reducer,
|
|
441
474
|
undefined,
|
|
442
475
|
driveStorage
|
|
@@ -450,7 +483,7 @@ export class DocumentDriveServer extends BaseDocumentDriveServer {
|
|
|
450
483
|
}
|
|
451
484
|
}
|
|
452
485
|
|
|
453
|
-
async getDocument(drive: string, id: string) {
|
|
486
|
+
async getDocument(drive: string, id: string, options?: GetDocumentOptions) {
|
|
454
487
|
const { initialState, operations, ...header } =
|
|
455
488
|
await this.storage.getDocument(drive, id);
|
|
456
489
|
|
|
@@ -458,7 +491,7 @@ export class DocumentDriveServer extends BaseDocumentDriveServer {
|
|
|
458
491
|
|
|
459
492
|
return baseUtils.replayDocument(
|
|
460
493
|
initialState,
|
|
461
|
-
operations,
|
|
494
|
+
filterOperationsByRevision(operations, options?.revisions),
|
|
462
495
|
documentModel.reducer,
|
|
463
496
|
undefined,
|
|
464
497
|
header
|
|
@@ -859,6 +892,14 @@ export class DocumentDriveServer extends BaseDocumentDriveServer {
|
|
|
859
892
|
return this.addDriveOperations(drive, [operation]);
|
|
860
893
|
}
|
|
861
894
|
|
|
895
|
+
async clearStorage() {
|
|
896
|
+
for (const drive of await this.getDrives()) {
|
|
897
|
+
await this.deleteDrive(drive);
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
await this.storage.clearStorage?.();
|
|
901
|
+
}
|
|
902
|
+
|
|
862
903
|
async addDriveOperations(
|
|
863
904
|
drive: string,
|
|
864
905
|
operations: Operation<DocumentDriveAction | BaseAction>[]
|
|
@@ -897,33 +938,15 @@ export class DocumentDriveServer extends BaseDocumentDriveServer {
|
|
|
897
938
|
}
|
|
898
939
|
|
|
899
940
|
for (const operation of operationsApplied) {
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
documentId: listener.filter.documentId ?? [],
|
|
910
|
-
documentType: listener.filter.documentType ?? [],
|
|
911
|
-
scope: listener.filter.scope ?? []
|
|
912
|
-
},
|
|
913
|
-
callInfo: {
|
|
914
|
-
data: listener.callInfo?.data ?? '',
|
|
915
|
-
name: listener.callInfo?.name ?? 'PullResponder',
|
|
916
|
-
transmitterType:
|
|
917
|
-
listener.callInfo?.transmitterType ??
|
|
918
|
-
'PullResponder'
|
|
919
|
-
}
|
|
920
|
-
});
|
|
921
|
-
} else if (operation.type === 'REMOVE_LISTENER') {
|
|
922
|
-
const { listenerId } = operation.input;
|
|
923
|
-
await this.listenerStateManager.removeListener(
|
|
924
|
-
drive,
|
|
925
|
-
listenerId
|
|
926
|
-
);
|
|
941
|
+
switch (operation.type) {
|
|
942
|
+
case 'ADD_LISTENER': {
|
|
943
|
+
await this.addListener(drive, operation);
|
|
944
|
+
break;
|
|
945
|
+
}
|
|
946
|
+
case 'REMOVE_LISTENER': {
|
|
947
|
+
await this.removeListener(drive, operation);
|
|
948
|
+
break;
|
|
949
|
+
}
|
|
927
950
|
}
|
|
928
951
|
}
|
|
929
952
|
|
|
@@ -995,6 +1018,149 @@ export class DocumentDriveServer extends BaseDocumentDriveServer {
|
|
|
995
1018
|
}
|
|
996
1019
|
}
|
|
997
1020
|
|
|
1021
|
+
private _buildOperation<T extends Action>(
|
|
1022
|
+
documentStorage: DocumentStorage,
|
|
1023
|
+
action: T | BaseAction
|
|
1024
|
+
): Operation<T | BaseAction> {
|
|
1025
|
+
const [operation] = this._buildOperations(documentStorage, [action]);
|
|
1026
|
+
if (!operation) {
|
|
1027
|
+
throw new Error('Error creating operation');
|
|
1028
|
+
}
|
|
1029
|
+
return operation;
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
private _buildOperations<T extends Action>(
|
|
1033
|
+
documentStorage: DocumentStorage,
|
|
1034
|
+
actions: (T | BaseAction)[]
|
|
1035
|
+
): Operation<T | BaseAction>[] {
|
|
1036
|
+
const operations: Operation<T | BaseAction>[] = [];
|
|
1037
|
+
const { reducer } = this._getDocumentModel(
|
|
1038
|
+
documentStorage.documentType
|
|
1039
|
+
);
|
|
1040
|
+
let document = baseUtils.replayDocument(
|
|
1041
|
+
documentStorage.initialState,
|
|
1042
|
+
documentStorage.operations,
|
|
1043
|
+
reducer,
|
|
1044
|
+
undefined,
|
|
1045
|
+
documentStorage
|
|
1046
|
+
);
|
|
1047
|
+
for (const action of actions) {
|
|
1048
|
+
document = reducer(document, action);
|
|
1049
|
+
const operation = document.operations[action.scope].slice().pop();
|
|
1050
|
+
if (!operation) {
|
|
1051
|
+
throw new Error('Error creating operations');
|
|
1052
|
+
}
|
|
1053
|
+
operations.push(operation);
|
|
1054
|
+
}
|
|
1055
|
+
return operations;
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
async addAction(
|
|
1059
|
+
drive: string,
|
|
1060
|
+
id: string,
|
|
1061
|
+
action: Action
|
|
1062
|
+
): Promise<IOperationResult> {
|
|
1063
|
+
const documentStorage = await this.storage.getDocument(drive, id);
|
|
1064
|
+
const operation = this._buildOperation(documentStorage, action);
|
|
1065
|
+
return this.addOperation(drive, id, operation);
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
async addActions(
|
|
1069
|
+
drive: string,
|
|
1070
|
+
id: string,
|
|
1071
|
+
actions: Action[]
|
|
1072
|
+
): Promise<IOperationResult> {
|
|
1073
|
+
const documentStorage = await this.storage.getDocument(drive, id);
|
|
1074
|
+
const operations = this._buildOperations(documentStorage, actions);
|
|
1075
|
+
return this.addOperations(drive, id, operations);
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
async addDriveAction(
|
|
1079
|
+
drive: string,
|
|
1080
|
+
action: DocumentDriveAction | BaseAction
|
|
1081
|
+
): Promise<IOperationResult<DocumentDriveDocument>> {
|
|
1082
|
+
const documentStorage = await this.storage.getDrive(drive);
|
|
1083
|
+
const operation = this._buildOperation(documentStorage, action);
|
|
1084
|
+
return this.addDriveOperation(drive, operation);
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
async addDriveActions(
|
|
1088
|
+
drive: string,
|
|
1089
|
+
actions: (DocumentDriveAction | BaseAction)[]
|
|
1090
|
+
): Promise<IOperationResult<DocumentDriveDocument>> {
|
|
1091
|
+
const documentStorage = await this.storage.getDrive(drive);
|
|
1092
|
+
const operations = this._buildOperations(documentStorage, actions);
|
|
1093
|
+
return this.addDriveOperations(drive, operations);
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
async addInternalListener(
|
|
1097
|
+
driveId: string,
|
|
1098
|
+
receiver: IReceiver,
|
|
1099
|
+
options: {
|
|
1100
|
+
listenerId: string;
|
|
1101
|
+
label: string;
|
|
1102
|
+
block: boolean;
|
|
1103
|
+
filter: ListenerFilter;
|
|
1104
|
+
}
|
|
1105
|
+
) {
|
|
1106
|
+
const listener: AddListenerInput['listener'] = {
|
|
1107
|
+
callInfo: {
|
|
1108
|
+
data: '',
|
|
1109
|
+
name: 'Interal',
|
|
1110
|
+
transmitterType: 'Internal'
|
|
1111
|
+
},
|
|
1112
|
+
system: true,
|
|
1113
|
+
...options
|
|
1114
|
+
};
|
|
1115
|
+
await this.addDriveAction(driveId, actions.addListener({ listener }));
|
|
1116
|
+
const transmitter = await this.getTransmitter(
|
|
1117
|
+
driveId,
|
|
1118
|
+
options.listenerId
|
|
1119
|
+
);
|
|
1120
|
+
if (!transmitter) {
|
|
1121
|
+
throw new Error('Internal listener not found');
|
|
1122
|
+
}
|
|
1123
|
+
if (!(transmitter instanceof InternalTransmitter)) {
|
|
1124
|
+
throw new Error('Listener is not an internal transmitter');
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
transmitter.setReceiver(receiver);
|
|
1128
|
+
return transmitter;
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
private async addListener(
|
|
1132
|
+
driveId: string,
|
|
1133
|
+
operation: Operation<Action<'ADD_LISTENER', AddListenerInput>>
|
|
1134
|
+
) {
|
|
1135
|
+
const { listener } = operation.input;
|
|
1136
|
+
await this.listenerStateManager.addListener({
|
|
1137
|
+
...listener,
|
|
1138
|
+
driveId,
|
|
1139
|
+
label: listener.label ?? '',
|
|
1140
|
+
system: listener.system ?? false,
|
|
1141
|
+
filter: {
|
|
1142
|
+
branch: listener.filter.branch ?? [],
|
|
1143
|
+
documentId: listener.filter.documentId ?? [],
|
|
1144
|
+
documentType: listener.filter.documentType ?? [],
|
|
1145
|
+
scope: listener.filter.scope ?? []
|
|
1146
|
+
},
|
|
1147
|
+
callInfo: {
|
|
1148
|
+
data: listener.callInfo?.data ?? '',
|
|
1149
|
+
name: listener.callInfo?.name ?? 'PullResponder',
|
|
1150
|
+
transmitterType:
|
|
1151
|
+
listener.callInfo?.transmitterType ?? 'PullResponder'
|
|
1152
|
+
}
|
|
1153
|
+
});
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
private async removeListener(
|
|
1157
|
+
driveId: string,
|
|
1158
|
+
operation: Operation<Action<'REMOVE_LISTENER', RemoveListenerInput>>
|
|
1159
|
+
) {
|
|
1160
|
+
const { listenerId } = operation.input;
|
|
1161
|
+
await this.listenerStateManager.removeListener(driveId, listenerId);
|
|
1162
|
+
}
|
|
1163
|
+
|
|
998
1164
|
getTransmitter(
|
|
999
1165
|
driveId: string,
|
|
1000
1166
|
listenerId: string
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
SynchronizationUnit
|
|
16
16
|
} from '../types';
|
|
17
17
|
import { PullResponderTransmitter } from './transmitter';
|
|
18
|
+
import { InternalTransmitter } from './transmitter/internal';
|
|
18
19
|
import { SwitchboardPushTransmitter } from './transmitter/switchboard-push';
|
|
19
20
|
import { ITransmitter } from './transmitter/types';
|
|
20
21
|
|
|
@@ -42,41 +43,17 @@ export class ListenerManager extends BaseListenerManager {
|
|
|
42
43
|
}
|
|
43
44
|
|
|
44
45
|
const driveMap = this.listenerState.get(drive)!;
|
|
45
|
-
|
|
46
|
-
const driveDocument = await this.drive.getDrive(drive);
|
|
47
|
-
|
|
48
|
-
const lastDriveOperation = driveDocument.operations.global
|
|
49
|
-
.slice()
|
|
50
|
-
.pop();
|
|
51
|
-
|
|
52
46
|
driveMap.set(listener.listenerId, {
|
|
53
47
|
block: listener.block,
|
|
54
48
|
driveId: listener.driveId,
|
|
55
49
|
pendingTimeout: '0',
|
|
56
50
|
listener,
|
|
57
51
|
listenerStatus: 'CREATED',
|
|
58
|
-
syncUnits:
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
documentType: driveDocument.documentType,
|
|
64
|
-
scope: 'global',
|
|
65
|
-
branch: 'main',
|
|
66
|
-
lastUpdated:
|
|
67
|
-
lastDriveOperation?.timestamp ??
|
|
68
|
-
driveDocument.lastModified,
|
|
69
|
-
revision: lastDriveOperation?.index ?? 0,
|
|
70
|
-
listenerRev: -1,
|
|
71
|
-
syncRev: lastDriveOperation?.index ?? 0
|
|
72
|
-
}
|
|
73
|
-
].concat(
|
|
74
|
-
filteredSyncUnits.map(e => ({
|
|
75
|
-
...e,
|
|
76
|
-
listenerRev: -1,
|
|
77
|
-
syncRev: e.revision
|
|
78
|
-
}))
|
|
79
|
-
)
|
|
52
|
+
syncUnits: filteredSyncUnits.map(e => ({
|
|
53
|
+
...e,
|
|
54
|
+
listenerRev: -1,
|
|
55
|
+
syncRev: e.revision
|
|
56
|
+
}))
|
|
80
57
|
});
|
|
81
58
|
|
|
82
59
|
let transmitter: ITransmitter | undefined;
|
|
@@ -96,6 +73,11 @@ export class ListenerManager extends BaseListenerManager {
|
|
|
96
73
|
this.drive,
|
|
97
74
|
this
|
|
98
75
|
);
|
|
76
|
+
break;
|
|
77
|
+
}
|
|
78
|
+
case 'Internal': {
|
|
79
|
+
transmitter = new InternalTransmitter(listener, this.drive);
|
|
80
|
+
break;
|
|
99
81
|
}
|
|
100
82
|
}
|
|
101
83
|
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { Document, OperationScope } from 'document-model/document';
|
|
2
|
+
import {
|
|
3
|
+
BaseDocumentDriveServer,
|
|
4
|
+
Listener,
|
|
5
|
+
ListenerRevision,
|
|
6
|
+
OperationUpdate,
|
|
7
|
+
StrandUpdate
|
|
8
|
+
} from '../../types';
|
|
9
|
+
import { buildRevisionsFilter } from '../../utils';
|
|
10
|
+
import { ITransmitter } from './types';
|
|
11
|
+
|
|
12
|
+
export interface IReceiver {
|
|
13
|
+
transmit: (strands: InternalTransmitterUpdate[]) => Promise<void>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export type InternalTransmitterUpdate<
|
|
17
|
+
T extends Document = Document,
|
|
18
|
+
S extends OperationScope = OperationScope
|
|
19
|
+
> = {
|
|
20
|
+
driveId: string;
|
|
21
|
+
documentId: string;
|
|
22
|
+
scope: S;
|
|
23
|
+
branch: string;
|
|
24
|
+
operations: OperationUpdate[];
|
|
25
|
+
state: T['state'][S];
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export class InternalTransmitter implements ITransmitter {
|
|
29
|
+
private drive: BaseDocumentDriveServer;
|
|
30
|
+
private listener: Listener;
|
|
31
|
+
private receiver: IReceiver | undefined;
|
|
32
|
+
|
|
33
|
+
constructor(listener: Listener, drive: BaseDocumentDriveServer) {
|
|
34
|
+
this.listener = listener;
|
|
35
|
+
this.drive = drive;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async transmit(strands: StrandUpdate[]): Promise<ListenerRevision[]> {
|
|
39
|
+
if (!this.receiver) {
|
|
40
|
+
return [];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const retrievedDocuments = new Map<string, Document>();
|
|
44
|
+
const updates: InternalTransmitterUpdate[] = [];
|
|
45
|
+
for (const strand of strands) {
|
|
46
|
+
let document = retrievedDocuments.get(
|
|
47
|
+
`${strand.driveId}:${strand.documentId}`
|
|
48
|
+
);
|
|
49
|
+
if (!document) {
|
|
50
|
+
const revisions = buildRevisionsFilter(
|
|
51
|
+
strands,
|
|
52
|
+
strand.driveId,
|
|
53
|
+
strand.documentId
|
|
54
|
+
);
|
|
55
|
+
document = await (strand.documentId
|
|
56
|
+
? this.drive.getDocument(
|
|
57
|
+
strand.driveId,
|
|
58
|
+
strand.documentId,
|
|
59
|
+
{ revisions }
|
|
60
|
+
)
|
|
61
|
+
: this.drive.getDrive(strand.driveId, { revisions }));
|
|
62
|
+
retrievedDocuments.set(
|
|
63
|
+
`${strand.driveId}:${strand.documentId}`,
|
|
64
|
+
document
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
updates.push({ ...strand, state: document.state[strand.scope] });
|
|
68
|
+
}
|
|
69
|
+
try {
|
|
70
|
+
await this.receiver.transmit(updates);
|
|
71
|
+
return strands.map(({ operations, ...s }) => ({
|
|
72
|
+
...s,
|
|
73
|
+
status: 'SUCCESS',
|
|
74
|
+
revision: operations[operations.length - 1]?.index ?? -1
|
|
75
|
+
}));
|
|
76
|
+
} catch (error) {
|
|
77
|
+
console.error(error);
|
|
78
|
+
// TODO check which strand caused an error
|
|
79
|
+
return strands.map(({ operations, ...s }) => ({
|
|
80
|
+
...s,
|
|
81
|
+
status: 'ERROR',
|
|
82
|
+
revision: (operations[0]?.index ?? 0) - 1
|
|
83
|
+
}));
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
setReceiver(receiver: IReceiver) {
|
|
88
|
+
this.receiver = receiver;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ListenerFilter, Trigger, z } from 'document-model-libs/document-drive';
|
|
2
2
|
import { Operation, OperationScope } from 'document-model/document';
|
|
3
3
|
import { PULL_DRIVE_INTERVAL } from '../..';
|
|
4
|
+
import { generateUUID } from '../../../utils';
|
|
4
5
|
import { gql, requestGraphql } from '../../../utils/graphql';
|
|
5
6
|
import { OperationError } from '../../error';
|
|
6
7
|
import {
|
|
@@ -10,6 +11,7 @@ import {
|
|
|
10
11
|
ListenerRevision,
|
|
11
12
|
ListenerRevisionWithError,
|
|
12
13
|
OperationUpdate,
|
|
14
|
+
RemoteDriveOptions,
|
|
13
15
|
StrandUpdate
|
|
14
16
|
} from '../../types';
|
|
15
17
|
import { ListenerManager } from '../manager';
|
|
@@ -208,6 +210,7 @@ export class PullResponderTransmitter implements IPullResponderTransmitter {
|
|
|
208
210
|
|
|
209
211
|
// if there are no new strands then do nothing
|
|
210
212
|
if (!strands.length) {
|
|
213
|
+
onRevisions?.([]);
|
|
211
214
|
return;
|
|
212
215
|
}
|
|
213
216
|
|
|
@@ -329,6 +332,35 @@ export class PullResponderTransmitter implements IPullResponderTransmitter {
|
|
|
329
332
|
};
|
|
330
333
|
}
|
|
331
334
|
|
|
335
|
+
static async createPullResponderTrigger(
|
|
336
|
+
driveId: string,
|
|
337
|
+
url: string,
|
|
338
|
+
options: Pick<RemoteDriveOptions, 'pullInterval' | 'pullFilter'>
|
|
339
|
+
): Promise<PullResponderTrigger> {
|
|
340
|
+
const { pullFilter, pullInterval } = options;
|
|
341
|
+
const listenerId = await PullResponderTransmitter.registerPullResponder(
|
|
342
|
+
driveId,
|
|
343
|
+
url,
|
|
344
|
+
pullFilter ?? {
|
|
345
|
+
documentId: ['*'],
|
|
346
|
+
documentType: ['*'],
|
|
347
|
+
branch: ['*'],
|
|
348
|
+
scope: ['*']
|
|
349
|
+
}
|
|
350
|
+
);
|
|
351
|
+
|
|
352
|
+
const pullTrigger: PullResponderTrigger = {
|
|
353
|
+
id: generateUUID(),
|
|
354
|
+
type: 'PullResponder',
|
|
355
|
+
data: {
|
|
356
|
+
url,
|
|
357
|
+
listenerId,
|
|
358
|
+
interval: pullInterval?.toString() ?? ''
|
|
359
|
+
}
|
|
360
|
+
};
|
|
361
|
+
return pullTrigger;
|
|
362
|
+
}
|
|
363
|
+
|
|
332
364
|
static isPullResponderTrigger(
|
|
333
365
|
trigger: Trigger
|
|
334
366
|
): trigger is PullResponderTrigger {
|
package/src/server/types.ts
CHANGED
|
@@ -7,6 +7,7 @@ import type {
|
|
|
7
7
|
ListenerFilter
|
|
8
8
|
} from 'document-model-libs/document-drive';
|
|
9
9
|
import type {
|
|
10
|
+
Action,
|
|
10
11
|
BaseAction,
|
|
11
12
|
CreateChildDocumentInput,
|
|
12
13
|
Document,
|
|
@@ -127,6 +128,17 @@ export interface DriveEvents {
|
|
|
127
128
|
strandUpdate: (update: StrandUpdate) => void;
|
|
128
129
|
}
|
|
129
130
|
|
|
131
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
132
|
+
export type PartialRecord<K extends keyof any, T> = {
|
|
133
|
+
[P in K]?: T;
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
export type RevisionsFilter = PartialRecord<OperationScope, number>;
|
|
137
|
+
|
|
138
|
+
export type GetDocumentOptions = {
|
|
139
|
+
revisions?: RevisionsFilter;
|
|
140
|
+
};
|
|
141
|
+
|
|
130
142
|
export abstract class BaseDocumentDriveServer {
|
|
131
143
|
/** Public methods **/
|
|
132
144
|
abstract getDrives(): Promise<string[]>;
|
|
@@ -136,10 +148,17 @@ export abstract class BaseDocumentDriveServer {
|
|
|
136
148
|
options: RemoteDriveOptions
|
|
137
149
|
): Promise<void>;
|
|
138
150
|
abstract deleteDrive(id: string): Promise<void>;
|
|
139
|
-
abstract getDrive(
|
|
151
|
+
abstract getDrive(
|
|
152
|
+
id: string,
|
|
153
|
+
options?: GetDocumentOptions
|
|
154
|
+
): Promise<DocumentDriveDocument>;
|
|
140
155
|
|
|
141
156
|
abstract getDocuments(drive: string): Promise<string[]>;
|
|
142
|
-
abstract getDocument(
|
|
157
|
+
abstract getDocument(
|
|
158
|
+
drive: string,
|
|
159
|
+
id: string,
|
|
160
|
+
options?: GetDocumentOptions
|
|
161
|
+
): Promise<Document>;
|
|
143
162
|
|
|
144
163
|
abstract addOperation(
|
|
145
164
|
drive: string,
|
|
@@ -161,6 +180,26 @@ export abstract class BaseDocumentDriveServer {
|
|
|
161
180
|
operations: Operation<DocumentDriveAction | BaseAction>[]
|
|
162
181
|
): Promise<IOperationResult<DocumentDriveDocument>>;
|
|
163
182
|
|
|
183
|
+
abstract addAction(
|
|
184
|
+
drive: string,
|
|
185
|
+
id: string,
|
|
186
|
+
action: Action
|
|
187
|
+
): Promise<IOperationResult>;
|
|
188
|
+
abstract addActions(
|
|
189
|
+
drive: string,
|
|
190
|
+
id: string,
|
|
191
|
+
actions: Action[]
|
|
192
|
+
): Promise<IOperationResult>;
|
|
193
|
+
|
|
194
|
+
abstract addDriveAction(
|
|
195
|
+
drive: string,
|
|
196
|
+
action: DocumentDriveAction | BaseAction
|
|
197
|
+
): Promise<IOperationResult<DocumentDriveDocument>>;
|
|
198
|
+
abstract addDriveActions(
|
|
199
|
+
drive: string,
|
|
200
|
+
actions: (DocumentDriveAction | BaseAction)[]
|
|
201
|
+
): Promise<IOperationResult<DocumentDriveDocument>>;
|
|
202
|
+
|
|
164
203
|
abstract getSyncStatus(drive: string): SyncStatus;
|
|
165
204
|
|
|
166
205
|
/** Synchronization methods */
|
|
@@ -208,6 +247,8 @@ export abstract class BaseDocumentDriveServer {
|
|
|
208
247
|
driveId: string,
|
|
209
248
|
listenerId: string
|
|
210
249
|
): Promise<ITransmitter | undefined>;
|
|
250
|
+
|
|
251
|
+
abstract clearStorage(): Promise<void>;
|
|
211
252
|
}
|
|
212
253
|
|
|
213
254
|
export abstract class BaseListenerManager {
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { Document, OperationScope } from 'document-model/document';
|
|
2
|
+
import { RevisionsFilter, StrandUpdate } from './types';
|
|
3
|
+
|
|
4
|
+
export function buildRevisionsFilter(
|
|
5
|
+
strands: StrandUpdate[],
|
|
6
|
+
driveId: string,
|
|
7
|
+
documentId: string
|
|
8
|
+
): RevisionsFilter {
|
|
9
|
+
return strands.reduce<RevisionsFilter>((acc, s) => {
|
|
10
|
+
if (!(s.driveId === driveId && s.documentId === documentId)) {
|
|
11
|
+
return acc;
|
|
12
|
+
}
|
|
13
|
+
acc[s.scope] = s.operations[s.operations.length - 1]?.index ?? -1;
|
|
14
|
+
return acc;
|
|
15
|
+
}, {});
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function filterOperationsByRevision(
|
|
19
|
+
operations: Document['operations'],
|
|
20
|
+
revisions?: RevisionsFilter
|
|
21
|
+
): Document['operations'] {
|
|
22
|
+
if (!revisions) {
|
|
23
|
+
return operations;
|
|
24
|
+
}
|
|
25
|
+
return (Object.keys(operations) as OperationScope[]).reduce<
|
|
26
|
+
Document['operations']
|
|
27
|
+
>((acc, scope) => {
|
|
28
|
+
const revision = revisions[scope];
|
|
29
|
+
if (revision !== undefined) {
|
|
30
|
+
acc[scope] = operations[scope].filter(op => op.index <= revision);
|
|
31
|
+
}
|
|
32
|
+
return acc;
|
|
33
|
+
}, operations);
|
|
34
|
+
}
|
package/src/storage/browser.ts
CHANGED
|
@@ -53,6 +53,10 @@ export class BrowserStorage implements IDriveStorage {
|
|
|
53
53
|
await (await this.db).removeItem(this.buildKey(drive, id));
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
+
async clearStorage(): Promise<void> {
|
|
57
|
+
return (await this.db).clear();
|
|
58
|
+
}
|
|
59
|
+
|
|
56
60
|
async addDocumentOperations(
|
|
57
61
|
drive: string,
|
|
58
62
|
id: string,
|
|
@@ -96,6 +96,44 @@ export class FilesystemStorage implements IDriveStorage {
|
|
|
96
96
|
});
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
+
async clearStorage() {
|
|
100
|
+
const drivesPath = path.join(
|
|
101
|
+
this.basePath,
|
|
102
|
+
FilesystemStorage.DRIVES_DIR
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
// delete content of drives directory
|
|
106
|
+
const drives = (
|
|
107
|
+
await fs.readdir(drivesPath, {
|
|
108
|
+
withFileTypes: true,
|
|
109
|
+
recursive: true
|
|
110
|
+
})
|
|
111
|
+
).filter(dirent => !!dirent.name);
|
|
112
|
+
|
|
113
|
+
await Promise.all(
|
|
114
|
+
drives.map(async dirent => {
|
|
115
|
+
await fs.rm(path.join(drivesPath, dirent.name), {
|
|
116
|
+
recursive: true
|
|
117
|
+
});
|
|
118
|
+
})
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
// delete files in basePath
|
|
122
|
+
const files = (
|
|
123
|
+
await fs.readdir(this.basePath, { withFileTypes: true })
|
|
124
|
+
).filter(
|
|
125
|
+
file => file.name !== FilesystemStorage.DRIVES_DIR && !!file.name
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
await Promise.all(
|
|
129
|
+
files.map(async dirent => {
|
|
130
|
+
await fs.rm(path.join(this.basePath, dirent.name), {
|
|
131
|
+
recursive: true
|
|
132
|
+
});
|
|
133
|
+
})
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
|
|
99
137
|
async deleteDocument(drive: string, id: string) {
|
|
100
138
|
return fs.rm(this._buildDocumentPath(drive, id));
|
|
101
139
|
}
|
package/src/storage/memory.ts
CHANGED
|
@@ -39,6 +39,11 @@ export class MemoryStorage implements IDriveStorage {
|
|
|
39
39
|
this.documents[drive]![id] = document;
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
+
async clearStorage(): Promise<void> {
|
|
43
|
+
this.documents = {};
|
|
44
|
+
this.drives = {};
|
|
45
|
+
}
|
|
46
|
+
|
|
42
47
|
async createDocument(drive: string, id: string, document: DocumentStorage) {
|
|
43
48
|
this.documents[drive] = this.documents[drive] ?? {};
|
|
44
49
|
const {
|
package/src/storage/prisma.ts
CHANGED
|
@@ -58,53 +58,33 @@ export class PrismaStorage implements IDriveStorage {
|
|
|
58
58
|
drive: string,
|
|
59
59
|
id: string,
|
|
60
60
|
operations: Operation[],
|
|
61
|
-
header: DocumentHeader
|
|
61
|
+
header: DocumentHeader,
|
|
62
|
+
updatedOperations: Operation[] = []
|
|
62
63
|
): Promise<void> {
|
|
63
64
|
const document = await this.getDocument(drive, id);
|
|
64
65
|
if (!document) {
|
|
65
66
|
throw new Error(`Document with id ${id} not found`);
|
|
66
67
|
}
|
|
67
68
|
|
|
69
|
+
const mergedOperations = [...operations, ...updatedOperations].sort(
|
|
70
|
+
(a, b) => a.index - b.index
|
|
71
|
+
);
|
|
72
|
+
|
|
68
73
|
try {
|
|
69
|
-
await
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
documentId: id,
|
|
84
|
-
hash: op.hash,
|
|
85
|
-
index: op.index,
|
|
86
|
-
input: op.input as Prisma.InputJsonObject,
|
|
87
|
-
timestamp: op.timestamp,
|
|
88
|
-
type: op.type,
|
|
89
|
-
scope: op.scope,
|
|
90
|
-
branch: 'main',
|
|
91
|
-
skip: op.skip
|
|
92
|
-
},
|
|
93
|
-
update: {
|
|
94
|
-
driveId: drive,
|
|
95
|
-
documentId: id,
|
|
96
|
-
hash: op.hash,
|
|
97
|
-
index: op.index,
|
|
98
|
-
input: op.input as Prisma.InputJsonObject,
|
|
99
|
-
timestamp: op.timestamp,
|
|
100
|
-
type: op.type,
|
|
101
|
-
scope: op.scope,
|
|
102
|
-
branch: 'main',
|
|
103
|
-
skip: op.skip
|
|
104
|
-
}
|
|
105
|
-
});
|
|
106
|
-
})
|
|
107
|
-
);
|
|
74
|
+
await this.db.operation.createMany({
|
|
75
|
+
data: mergedOperations.map(op => ({
|
|
76
|
+
driveId: drive,
|
|
77
|
+
documentId: id,
|
|
78
|
+
hash: op.hash,
|
|
79
|
+
index: op.index,
|
|
80
|
+
input: op.input as Prisma.InputJsonObject,
|
|
81
|
+
timestamp: op.timestamp,
|
|
82
|
+
type: op.type,
|
|
83
|
+
scope: op.scope,
|
|
84
|
+
branch: 'main',
|
|
85
|
+
skip: op.skip
|
|
86
|
+
}))
|
|
87
|
+
});
|
|
108
88
|
|
|
109
89
|
await this.db.document.updateMany({
|
|
110
90
|
where: {
|
|
@@ -213,48 +193,21 @@ export class PrismaStorage implements IDriveStorage {
|
|
|
213
193
|
}
|
|
214
194
|
|
|
215
195
|
async deleteDocument(drive: string, id: string) {
|
|
216
|
-
await this.db.attachment.deleteMany({
|
|
217
|
-
where: {
|
|
218
|
-
driveId: drive,
|
|
219
|
-
documentId: id
|
|
220
|
-
}
|
|
221
|
-
});
|
|
222
|
-
|
|
223
|
-
await this.db.operation.deleteMany({
|
|
224
|
-
where: {
|
|
225
|
-
driveId: drive,
|
|
226
|
-
documentId: id
|
|
227
|
-
}
|
|
228
|
-
});
|
|
229
|
-
|
|
230
196
|
await this.db.document.delete({
|
|
231
197
|
where: {
|
|
232
198
|
id_driveId: {
|
|
233
199
|
driveId: drive,
|
|
234
200
|
id: id
|
|
235
201
|
}
|
|
202
|
+
},
|
|
203
|
+
include: {
|
|
204
|
+
operations: {
|
|
205
|
+
include: {
|
|
206
|
+
attachments: true
|
|
207
|
+
}
|
|
208
|
+
}
|
|
236
209
|
}
|
|
237
210
|
});
|
|
238
|
-
|
|
239
|
-
if (drive === 'drives') {
|
|
240
|
-
await this.db.attachment.deleteMany({
|
|
241
|
-
where: {
|
|
242
|
-
driveId: id
|
|
243
|
-
}
|
|
244
|
-
});
|
|
245
|
-
|
|
246
|
-
await this.db.operation.deleteMany({
|
|
247
|
-
where: {
|
|
248
|
-
driveId: id
|
|
249
|
-
}
|
|
250
|
-
});
|
|
251
|
-
|
|
252
|
-
await this.db.document.deleteMany({
|
|
253
|
-
where: {
|
|
254
|
-
driveId: id
|
|
255
|
-
}
|
|
256
|
-
});
|
|
257
|
-
}
|
|
258
211
|
}
|
|
259
212
|
|
|
260
213
|
async getDrives() {
|
|
@@ -271,6 +224,12 @@ export class PrismaStorage implements IDriveStorage {
|
|
|
271
224
|
}
|
|
272
225
|
|
|
273
226
|
async deleteDrive(id: string) {
|
|
227
|
+
const docs = await this.getDocuments(id);
|
|
228
|
+
await Promise.all(
|
|
229
|
+
docs.map(async doc => {
|
|
230
|
+
return this.deleteDocument(id, doc);
|
|
231
|
+
})
|
|
232
|
+
);
|
|
274
233
|
await this.deleteDocument('drives', id);
|
|
275
234
|
}
|
|
276
235
|
}
|
package/src/storage/types.ts
CHANGED
|
@@ -38,6 +38,7 @@ export interface IDriveStorage extends IStorage {
|
|
|
38
38
|
getDrive(id: string): Promise<DocumentDriveStorage>;
|
|
39
39
|
createDrive(id: string, drive: DocumentDriveStorage): Promise<void>;
|
|
40
40
|
deleteDrive(id: string): Promise<void>;
|
|
41
|
+
clearStorage?(): Promise<void>;
|
|
41
42
|
addDriveOperations(
|
|
42
43
|
id: string,
|
|
43
44
|
operations: Operation<DocumentDriveAction | BaseAction>[],
|
package/src/utils/index.ts
CHANGED