document-drive 0.0.28 → 0.0.29
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 +1 -1
- package/src/server/index.ts +117 -45
- package/src/server/listener/transmitter/pull-responder.ts +22 -5
- package/src/storage/browser.ts +9 -3
- package/src/storage/filesystem.ts +9 -3
- package/src/storage/memory.ts +9 -3
- package/src/storage/prisma.ts +15 -7
- package/src/storage/types.ts +2 -1
- package/src/utils/index.ts +37 -0
package/package.json
CHANGED
package/src/server/index.ts
CHANGED
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
} from 'document-model/document';
|
|
19
19
|
import { MemoryStorage } from '../storage/memory';
|
|
20
20
|
import type { DocumentStorage, IDriveStorage } from '../storage/types';
|
|
21
|
-
import { generateUUID, isDocumentDrive } from '../utils';
|
|
21
|
+
import { generateUUID, isDocumentDrive, isNoopUpdate } from '../utils';
|
|
22
22
|
import { requestPublicDrive } from '../utils/graphql';
|
|
23
23
|
import { OperationError } from './error';
|
|
24
24
|
import { ListenerManager } from './listener/manager';
|
|
@@ -127,7 +127,7 @@ export class DocumentDriveServer extends BaseDocumentDriveServer {
|
|
|
127
127
|
acknowledgeSuccess => {}
|
|
128
128
|
);
|
|
129
129
|
driveTriggers.set(trigger.id, intervalId);
|
|
130
|
-
this.triggerMap.set(
|
|
130
|
+
this.triggerMap.set(driveId, driveTriggers);
|
|
131
131
|
}
|
|
132
132
|
}
|
|
133
133
|
}
|
|
@@ -465,18 +465,27 @@ export class DocumentDriveServer extends BaseDocumentDriveServer {
|
|
|
465
465
|
operations: Operation<A | BaseAction>[]
|
|
466
466
|
) {
|
|
467
467
|
const operationsApplied: Operation<A | BaseAction>[] = [];
|
|
468
|
+
const operationsUpdated: Operation<A | BaseAction>[] = [];
|
|
468
469
|
let document: T | undefined;
|
|
469
470
|
const signals: SignalResult[] = [];
|
|
470
471
|
|
|
471
472
|
// eslint-disable-next-line prefer-const
|
|
472
|
-
let [operationsToApply, error] =
|
|
473
|
-
operations,
|
|
474
|
-
|
|
475
|
-
|
|
473
|
+
let [operationsToApply, error, updatedOperations] =
|
|
474
|
+
this._validateOperations(operations, documentStorage);
|
|
475
|
+
|
|
476
|
+
const unregisteredOps = [
|
|
477
|
+
...operationsToApply.map(operation => ({ operation, type: 'new' })),
|
|
478
|
+
...updatedOperations.map(operation => ({
|
|
479
|
+
operation,
|
|
480
|
+
type: 'update'
|
|
481
|
+
}))
|
|
482
|
+
].sort((a, b) => a.operation.index - b.operation.index);
|
|
476
483
|
|
|
477
484
|
// retrieves the document's document model and
|
|
478
485
|
// applies the operations using its reducer
|
|
479
|
-
for (const
|
|
486
|
+
for (const unregisteredOp of unregisteredOps) {
|
|
487
|
+
const { operation, type } = unregisteredOp;
|
|
488
|
+
|
|
480
489
|
try {
|
|
481
490
|
const {
|
|
482
491
|
document: newDocument,
|
|
@@ -488,7 +497,13 @@ export class DocumentDriveServer extends BaseDocumentDriveServer {
|
|
|
488
497
|
operation
|
|
489
498
|
);
|
|
490
499
|
document = newDocument;
|
|
491
|
-
|
|
500
|
+
|
|
501
|
+
if (type === 'new') {
|
|
502
|
+
operationsApplied.push(appliedOperation);
|
|
503
|
+
} else {
|
|
504
|
+
operationsUpdated.push(appliedOperation);
|
|
505
|
+
}
|
|
506
|
+
|
|
492
507
|
signals.push(...signals);
|
|
493
508
|
} catch (e) {
|
|
494
509
|
if (!error) {
|
|
@@ -505,7 +520,18 @@ export class DocumentDriveServer extends BaseDocumentDriveServer {
|
|
|
505
520
|
break;
|
|
506
521
|
}
|
|
507
522
|
}
|
|
508
|
-
|
|
523
|
+
|
|
524
|
+
if (!document) {
|
|
525
|
+
document = this._buildDocument(documentStorage);
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
return {
|
|
529
|
+
document,
|
|
530
|
+
operationsApplied,
|
|
531
|
+
signals,
|
|
532
|
+
error,
|
|
533
|
+
operationsUpdated
|
|
534
|
+
} as const;
|
|
509
535
|
}
|
|
510
536
|
|
|
511
537
|
private _validateOperations<T extends Document, A extends Action>(
|
|
@@ -513,6 +539,7 @@ export class DocumentDriveServer extends BaseDocumentDriveServer {
|
|
|
513
539
|
documentStorage: DocumentStorage<T>
|
|
514
540
|
) {
|
|
515
541
|
const operationsToApply: Operation<A | BaseAction>[] = [];
|
|
542
|
+
const updatedOperations: Operation<A | BaseAction>[] = [];
|
|
516
543
|
let error: OperationError | undefined;
|
|
517
544
|
|
|
518
545
|
// sort operations so from smaller index to biggest
|
|
@@ -525,7 +552,17 @@ export class DocumentDriveServer extends BaseDocumentDriveServer {
|
|
|
525
552
|
.slice(0, i);
|
|
526
553
|
const scopeOperations = documentStorage.operations[op.scope];
|
|
527
554
|
|
|
528
|
-
|
|
555
|
+
// get latest operation
|
|
556
|
+
const ops = [...scopeOperations, ...pastOperations];
|
|
557
|
+
const latestOperation = ops.slice().pop();
|
|
558
|
+
|
|
559
|
+
const noopUpdate = isNoopUpdate(op, latestOperation);
|
|
560
|
+
|
|
561
|
+
let nextIndex = scopeOperations.length + pastOperations.length;
|
|
562
|
+
if (noopUpdate) {
|
|
563
|
+
nextIndex = nextIndex - 1;
|
|
564
|
+
}
|
|
565
|
+
|
|
529
566
|
if (op.index > nextIndex) {
|
|
530
567
|
error = new OperationError(
|
|
531
568
|
'MISSING',
|
|
@@ -534,39 +571,54 @@ export class DocumentDriveServer extends BaseDocumentDriveServer {
|
|
|
534
571
|
);
|
|
535
572
|
continue;
|
|
536
573
|
} else if (op.index < nextIndex) {
|
|
537
|
-
const existingOperation =
|
|
538
|
-
|
|
574
|
+
const existingOperation = scopeOperations.find(
|
|
575
|
+
existingOperation => existingOperation.index === op.index
|
|
576
|
+
);
|
|
539
577
|
if (existingOperation && existingOperation.hash !== op.hash) {
|
|
540
578
|
error = new OperationError(
|
|
541
579
|
'CONFLICT',
|
|
542
580
|
op,
|
|
543
|
-
`Conflicting operation on index ${op.index}
|
|
581
|
+
`Conflicting operation on index ${op.index}`,
|
|
582
|
+
{ existingOperation, newOperation: op }
|
|
544
583
|
);
|
|
545
584
|
continue;
|
|
546
585
|
}
|
|
547
586
|
} else {
|
|
548
|
-
|
|
587
|
+
if (noopUpdate) {
|
|
588
|
+
updatedOperations.push(op);
|
|
589
|
+
} else {
|
|
590
|
+
operationsToApply.push(op);
|
|
591
|
+
}
|
|
549
592
|
}
|
|
550
593
|
}
|
|
551
594
|
|
|
552
|
-
return [operationsToApply, error] as const;
|
|
595
|
+
return [operationsToApply, error, updatedOperations] as const;
|
|
553
596
|
}
|
|
554
597
|
|
|
555
|
-
private
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
operation: Operation<A | BaseAction>
|
|
559
|
-
) {
|
|
598
|
+
private _buildDocument<T extends Document>(
|
|
599
|
+
documentStorage: DocumentStorage<T>
|
|
600
|
+
): T {
|
|
560
601
|
const documentModel = this._getDocumentModel(
|
|
561
602
|
documentStorage.documentType
|
|
562
603
|
);
|
|
563
|
-
|
|
604
|
+
return baseUtils.replayDocument(
|
|
564
605
|
documentStorage.initialState,
|
|
565
606
|
documentStorage.operations,
|
|
566
607
|
documentModel.reducer,
|
|
567
608
|
undefined,
|
|
568
609
|
documentStorage
|
|
569
610
|
) as T;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
private async _performOperation<T extends Document, A extends Action>(
|
|
614
|
+
drive: string,
|
|
615
|
+
documentStorage: DocumentStorage<T>,
|
|
616
|
+
operation: Operation<A | BaseAction>
|
|
617
|
+
) {
|
|
618
|
+
const documentModel = this._getDocumentModel(
|
|
619
|
+
documentStorage.documentType
|
|
620
|
+
);
|
|
621
|
+
const document = this._buildDocument(documentStorage);
|
|
570
622
|
|
|
571
623
|
const signalResults: SignalResult[] = [];
|
|
572
624
|
let newDocument = document;
|
|
@@ -634,6 +686,7 @@ export class DocumentDriveServer extends BaseDocumentDriveServer {
|
|
|
634
686
|
|
|
635
687
|
let document: Document | undefined;
|
|
636
688
|
const operationsApplied: Operation[] = [];
|
|
689
|
+
const updatedOperations: Operation[] = [];
|
|
637
690
|
const signals: SignalResult[] = [];
|
|
638
691
|
let error: Error | undefined;
|
|
639
692
|
|
|
@@ -647,7 +700,10 @@ export class DocumentDriveServer extends BaseDocumentDriveServer {
|
|
|
647
700
|
);
|
|
648
701
|
|
|
649
702
|
document = result.document;
|
|
703
|
+
|
|
650
704
|
operationsApplied.push(...result.operationsApplied);
|
|
705
|
+
updatedOperations.push(...result.operationsUpdated);
|
|
706
|
+
|
|
651
707
|
signals.push(...result.signals);
|
|
652
708
|
error = result.error;
|
|
653
709
|
|
|
@@ -656,15 +712,21 @@ export class DocumentDriveServer extends BaseDocumentDriveServer {
|
|
|
656
712
|
}
|
|
657
713
|
|
|
658
714
|
// saves the applied operations to storage
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
715
|
+
if (operationsApplied.length > 0 || updatedOperations.length > 0) {
|
|
716
|
+
await this.storage.addDocumentOperations(
|
|
717
|
+
drive,
|
|
718
|
+
id,
|
|
719
|
+
operationsApplied,
|
|
720
|
+
document,
|
|
721
|
+
updatedOperations
|
|
722
|
+
);
|
|
723
|
+
}
|
|
665
724
|
|
|
666
725
|
// gets all the different scopes and branches combinations from the operations
|
|
667
|
-
const { scopes, branches } =
|
|
726
|
+
const { scopes, branches } = [
|
|
727
|
+
...operationsApplied,
|
|
728
|
+
...updatedOperations
|
|
729
|
+
].reduce(
|
|
668
730
|
(acc, operation) => {
|
|
669
731
|
if (!acc.scopes.includes(operation.scope)) {
|
|
670
732
|
acc.scopes.push(operation.scope);
|
|
@@ -682,12 +744,16 @@ export class DocumentDriveServer extends BaseDocumentDriveServer {
|
|
|
682
744
|
);
|
|
683
745
|
// update listener cache
|
|
684
746
|
for (const syncUnit of syncUnits) {
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
747
|
+
this.listenerStateManager
|
|
748
|
+
.updateSynchronizationRevision(
|
|
749
|
+
drive,
|
|
750
|
+
syncUnit.syncId,
|
|
751
|
+
syncUnit.revision,
|
|
752
|
+
syncUnit.lastUpdated
|
|
753
|
+
)
|
|
754
|
+
.catch(error => {
|
|
755
|
+
console.error('Error updating sync revision', error);
|
|
756
|
+
});
|
|
691
757
|
}
|
|
692
758
|
|
|
693
759
|
// after applying all the valid operations,throws
|
|
@@ -759,11 +825,13 @@ export class DocumentDriveServer extends BaseDocumentDriveServer {
|
|
|
759
825
|
}
|
|
760
826
|
|
|
761
827
|
// saves the applied operations to storage
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
828
|
+
if (operationsApplied.length > 0) {
|
|
829
|
+
await this.storage.addDriveOperations(
|
|
830
|
+
drive,
|
|
831
|
+
operationsApplied,
|
|
832
|
+
document
|
|
833
|
+
);
|
|
834
|
+
}
|
|
767
835
|
|
|
768
836
|
for (const operation of operationsApplied) {
|
|
769
837
|
if (operation.type === 'ADD_LISTENER') {
|
|
@@ -802,12 +870,16 @@ export class DocumentDriveServer extends BaseDocumentDriveServer {
|
|
|
802
870
|
.slice()
|
|
803
871
|
.pop();
|
|
804
872
|
if (lastOperation) {
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
873
|
+
this.listenerStateManager
|
|
874
|
+
.updateSynchronizationRevision(
|
|
875
|
+
drive,
|
|
876
|
+
'0',
|
|
877
|
+
lastOperation.index,
|
|
878
|
+
lastOperation.timestamp
|
|
879
|
+
)
|
|
880
|
+
.catch(error => {
|
|
881
|
+
console.error('Error updating sync revision', error);
|
|
882
|
+
});
|
|
811
883
|
}
|
|
812
884
|
|
|
813
885
|
if (this.shouldSyncRemoteDrive(document)) {
|
|
@@ -76,6 +76,11 @@ export class PullResponderTransmitter implements ITransmitter {
|
|
|
76
76
|
fromRevision: entry.listenerRev
|
|
77
77
|
}
|
|
78
78
|
);
|
|
79
|
+
|
|
80
|
+
if (!operations.length) {
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
|
|
79
84
|
strands.push({
|
|
80
85
|
driveId,
|
|
81
86
|
documentId,
|
|
@@ -94,12 +99,18 @@ export class PullResponderTransmitter implements ITransmitter {
|
|
|
94
99
|
revisions: ListenerRevision[]
|
|
95
100
|
): Promise<boolean> {
|
|
96
101
|
const listener = this.manager.getListener(driveId, listenerId);
|
|
102
|
+
|
|
97
103
|
let success = true;
|
|
98
104
|
for (const revision of revisions) {
|
|
99
|
-
const
|
|
100
|
-
s =>
|
|
101
|
-
|
|
102
|
-
|
|
105
|
+
const syncUnit = listener.syncUnits.find(
|
|
106
|
+
s =>
|
|
107
|
+
s.scope === revision.scope &&
|
|
108
|
+
s.branch === revision.branch &&
|
|
109
|
+
s.driveId === revision.driveId &&
|
|
110
|
+
s.documentId == revision.documentId
|
|
111
|
+
);
|
|
112
|
+
if (!syncUnit) {
|
|
113
|
+
console.log('Sync unit not found', revision);
|
|
103
114
|
success = false;
|
|
104
115
|
continue;
|
|
105
116
|
}
|
|
@@ -107,7 +118,7 @@ export class PullResponderTransmitter implements ITransmitter {
|
|
|
107
118
|
await this.manager.updateListenerRevision(
|
|
108
119
|
listenerId,
|
|
109
120
|
driveId,
|
|
110
|
-
syncId,
|
|
121
|
+
syncUnit.syncId,
|
|
111
122
|
revision.revision
|
|
112
123
|
);
|
|
113
124
|
}
|
|
@@ -235,6 +246,12 @@ export class PullResponderTransmitter implements ITransmitter {
|
|
|
235
246
|
listenerId
|
|
236
247
|
// since ?
|
|
237
248
|
);
|
|
249
|
+
|
|
250
|
+
// if there are no new strands then do nothing
|
|
251
|
+
if (!strands.length) {
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
|
|
238
255
|
const listenerRevisions: ListenerRevision[] = [];
|
|
239
256
|
|
|
240
257
|
for (const strand of strands) {
|
package/src/storage/browser.ts
CHANGED
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
DocumentHeader,
|
|
6
6
|
Operation
|
|
7
7
|
} from 'document-model/document';
|
|
8
|
-
import { mergeOperations } from '..';
|
|
8
|
+
import { applyUpdatedOperations, mergeOperations } from '..';
|
|
9
9
|
import { DocumentDriveStorage, DocumentStorage, IDriveStorage } from './types';
|
|
10
10
|
|
|
11
11
|
export class BrowserStorage implements IDriveStorage {
|
|
@@ -57,7 +57,8 @@ export class BrowserStorage implements IDriveStorage {
|
|
|
57
57
|
drive: string,
|
|
58
58
|
id: string,
|
|
59
59
|
operations: Operation[],
|
|
60
|
-
header: DocumentHeader
|
|
60
|
+
header: DocumentHeader,
|
|
61
|
+
updatedOperations: Operation[] = []
|
|
61
62
|
): Promise<void> {
|
|
62
63
|
const document = await this.getDocument(drive, id);
|
|
63
64
|
if (!document) {
|
|
@@ -69,12 +70,17 @@ export class BrowserStorage implements IDriveStorage {
|
|
|
69
70
|
operations
|
|
70
71
|
);
|
|
71
72
|
|
|
73
|
+
const mergedUpdatedOperations = applyUpdatedOperations(
|
|
74
|
+
mergedOperations,
|
|
75
|
+
updatedOperations
|
|
76
|
+
);
|
|
77
|
+
|
|
72
78
|
await (
|
|
73
79
|
await this.db
|
|
74
80
|
).setItem(this.buildKey(drive, id), {
|
|
75
81
|
...document,
|
|
76
82
|
...header,
|
|
77
|
-
operations:
|
|
83
|
+
operations: mergedUpdatedOperations
|
|
78
84
|
});
|
|
79
85
|
}
|
|
80
86
|
|
|
@@ -12,7 +12,7 @@ import fs from 'fs/promises';
|
|
|
12
12
|
import stringify from 'json-stringify-deterministic';
|
|
13
13
|
import path from 'path';
|
|
14
14
|
import sanitize from 'sanitize-filename';
|
|
15
|
-
import { mergeOperations } from '..';
|
|
15
|
+
import { applyUpdatedOperations, mergeOperations } from '..';
|
|
16
16
|
import { DocumentDriveStorage, DocumentStorage, IDriveStorage } from './types';
|
|
17
17
|
|
|
18
18
|
type FSError = {
|
|
@@ -104,7 +104,8 @@ export class FilesystemStorage implements IDriveStorage {
|
|
|
104
104
|
drive: string,
|
|
105
105
|
id: string,
|
|
106
106
|
operations: Operation[],
|
|
107
|
-
header: DocumentHeader
|
|
107
|
+
header: DocumentHeader,
|
|
108
|
+
updatedOperations: Operation[] = []
|
|
108
109
|
) {
|
|
109
110
|
const document = await this.getDocument(drive, id);
|
|
110
111
|
if (!document) {
|
|
@@ -116,10 +117,15 @@ export class FilesystemStorage implements IDriveStorage {
|
|
|
116
117
|
operations
|
|
117
118
|
);
|
|
118
119
|
|
|
120
|
+
const mergedUpdatedOperations = applyUpdatedOperations(
|
|
121
|
+
mergedOperations,
|
|
122
|
+
updatedOperations
|
|
123
|
+
);
|
|
124
|
+
|
|
119
125
|
this.createDocument(drive, id, {
|
|
120
126
|
...document,
|
|
121
127
|
...header,
|
|
122
|
-
operations:
|
|
128
|
+
operations: mergedUpdatedOperations
|
|
123
129
|
});
|
|
124
130
|
}
|
|
125
131
|
|
package/src/storage/memory.ts
CHANGED
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
DocumentHeader,
|
|
6
6
|
Operation
|
|
7
7
|
} from 'document-model/document';
|
|
8
|
-
import { mergeOperations } from '..';
|
|
8
|
+
import { applyUpdatedOperations, mergeOperations } from '..';
|
|
9
9
|
import { DocumentDriveStorage, DocumentStorage, IDriveStorage } from './types';
|
|
10
10
|
|
|
11
11
|
export class MemoryStorage implements IDriveStorage {
|
|
@@ -67,7 +67,8 @@ export class MemoryStorage implements IDriveStorage {
|
|
|
67
67
|
drive: string,
|
|
68
68
|
id: string,
|
|
69
69
|
operations: Operation[],
|
|
70
|
-
header: DocumentHeader
|
|
70
|
+
header: DocumentHeader,
|
|
71
|
+
updatedOperations: Operation[] = []
|
|
71
72
|
): Promise<void> {
|
|
72
73
|
const document = await this.getDocument(drive, id);
|
|
73
74
|
if (!document) {
|
|
@@ -79,10 +80,15 @@ export class MemoryStorage implements IDriveStorage {
|
|
|
79
80
|
operations
|
|
80
81
|
);
|
|
81
82
|
|
|
83
|
+
const mergedUpdatedOperations = applyUpdatedOperations(
|
|
84
|
+
mergedOperations,
|
|
85
|
+
updatedOperations
|
|
86
|
+
);
|
|
87
|
+
|
|
82
88
|
this.documents[drive]![id] = {
|
|
83
89
|
...document,
|
|
84
90
|
...header,
|
|
85
|
-
operations:
|
|
91
|
+
operations: mergedUpdatedOperations
|
|
86
92
|
};
|
|
87
93
|
}
|
|
88
94
|
|
package/src/storage/prisma.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { type Prisma } from '@prisma/client';
|
|
1
|
+
import { PrismaClient, type Prisma } from '@prisma/client';
|
|
2
2
|
import {
|
|
3
3
|
DocumentDriveLocalState,
|
|
4
4
|
DocumentDriveState
|
|
5
5
|
} from 'document-model-libs/document-drive';
|
|
6
|
-
import {
|
|
6
|
+
import type {
|
|
7
7
|
DocumentHeader,
|
|
8
8
|
ExtendedState,
|
|
9
9
|
Operation,
|
|
@@ -12,16 +12,16 @@ import {
|
|
|
12
12
|
import { DocumentDriveStorage, DocumentStorage, IDriveStorage } from './types';
|
|
13
13
|
|
|
14
14
|
export class PrismaStorage implements IDriveStorage {
|
|
15
|
-
private db:
|
|
15
|
+
private db: PrismaClient;
|
|
16
16
|
|
|
17
|
-
constructor(db:
|
|
17
|
+
constructor(db: PrismaClient) {
|
|
18
18
|
this.db = db;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
async createDrive(id: string, drive: DocumentDriveStorage): Promise<void> {
|
|
22
|
+
// drive for all drive documents
|
|
22
23
|
await this.createDocument('drives', id, drive as DocumentStorage);
|
|
23
24
|
}
|
|
24
|
-
|
|
25
25
|
async addDriveOperations(
|
|
26
26
|
id: string,
|
|
27
27
|
operations: Operation[],
|
|
@@ -166,6 +166,9 @@ export class PrismaStorage implements IDriveStorage {
|
|
|
166
166
|
},
|
|
167
167
|
include: {
|
|
168
168
|
operations: {
|
|
169
|
+
orderBy: {
|
|
170
|
+
index: 'asc'
|
|
171
|
+
},
|
|
169
172
|
include: {
|
|
170
173
|
attachments: true
|
|
171
174
|
}
|
|
@@ -225,7 +228,7 @@ export class PrismaStorage implements IDriveStorage {
|
|
|
225
228
|
scope: op.scope as OperationScope
|
|
226
229
|
// attachments: fileRegistry
|
|
227
230
|
})),
|
|
228
|
-
revision: dbDoc.revision as
|
|
231
|
+
revision: dbDoc.revision as Record<OperationScope, number>
|
|
229
232
|
};
|
|
230
233
|
|
|
231
234
|
return doc;
|
|
@@ -281,7 +284,12 @@ export class PrismaStorage implements IDriveStorage {
|
|
|
281
284
|
}
|
|
282
285
|
|
|
283
286
|
async getDrive(id: string) {
|
|
284
|
-
|
|
287
|
+
try {
|
|
288
|
+
const doc = await this.getDocument('drives', id);
|
|
289
|
+
return doc as DocumentDriveStorage;
|
|
290
|
+
} catch (e) {
|
|
291
|
+
throw new Error(`Drive with id ${id} not found`);
|
|
292
|
+
}
|
|
285
293
|
}
|
|
286
294
|
|
|
287
295
|
async deleteDrive(id: string) {
|
package/src/storage/types.ts
CHANGED
|
@@ -27,7 +27,8 @@ export interface IStorage {
|
|
|
27
27
|
drive: string,
|
|
28
28
|
id: string,
|
|
29
29
|
operations: Operation[],
|
|
30
|
-
header: DocumentHeader
|
|
30
|
+
header: DocumentHeader,
|
|
31
|
+
updatedOperations?: Operation[]
|
|
31
32
|
): Promise<void>;
|
|
32
33
|
deleteDocument(drive: string, id: string): Promise<void>;
|
|
33
34
|
}
|
package/src/utils/index.ts
CHANGED
|
@@ -38,3 +38,40 @@ export function generateUUID(): string {
|
|
|
38
38
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
|
|
39
39
|
return crypto.randomUUID() as string;
|
|
40
40
|
}
|
|
41
|
+
|
|
42
|
+
export function applyUpdatedOperations<A extends Action = Action>(
|
|
43
|
+
currentOperations: DocumentOperations<A>,
|
|
44
|
+
updatedOperations: Operation<A | BaseAction>[]
|
|
45
|
+
): DocumentOperations<A> {
|
|
46
|
+
return updatedOperations.reduce(
|
|
47
|
+
(acc, curr) => {
|
|
48
|
+
const operations = acc[curr.scope] ?? [];
|
|
49
|
+
acc[curr.scope] = operations.map(op => {
|
|
50
|
+
return op.index === curr.index ? curr : op;
|
|
51
|
+
});
|
|
52
|
+
return acc;
|
|
53
|
+
},
|
|
54
|
+
{ ...currentOperations }
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function isNoopUpdate(
|
|
59
|
+
operation: Operation,
|
|
60
|
+
latestOperation?: Operation
|
|
61
|
+
) {
|
|
62
|
+
if (!latestOperation) {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const isNoopOp = operation.type === 'NOOP';
|
|
67
|
+
const isNoopLatestOp = latestOperation.type === 'NOOP';
|
|
68
|
+
const isSameIndexOp = operation.index === latestOperation.index;
|
|
69
|
+
const isSkipOpGreaterThanLatestOp = operation.skip > latestOperation.skip;
|
|
70
|
+
|
|
71
|
+
return (
|
|
72
|
+
isNoopOp &&
|
|
73
|
+
isNoopLatestOp &&
|
|
74
|
+
isSameIndexOp &&
|
|
75
|
+
isSkipOpGreaterThanLatestOp
|
|
76
|
+
);
|
|
77
|
+
}
|