document-drive 1.0.0-experimental.21 → 1.0.0-experimental.23
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/CHANGELOG.md +70 -0
- package/package.json +5 -5
- package/src/queue/base.ts +0 -16
- package/src/queue/manager.ts +22 -0
- package/src/queue/redis.ts +0 -11
- package/src/queue/types.ts +0 -3
- package/src/server/index.ts +49 -17
- package/src/server/listener/transmitter/pull-responder.ts +7 -8
- package/src/server/types.ts +3 -1
- package/src/storage/prisma.ts +191 -76
- package/src/storage/sequelize.ts +11 -7
- package/src/storage/types.ts +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,73 @@
|
|
|
1
|
+
# [1.0.0-alpha.65](https://github.com/powerhouse-inc/document-drive/compare/v1.0.0-alpha.64...v1.0.0-alpha.65) (2024-05-30)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Features
|
|
5
|
+
|
|
6
|
+
* fetch resulting state for last unskipped operation ([8297353](https://github.com/powerhouse-inc/document-drive/commit/8297353dc8eaca107d8134580dee67c3265b05b5))
|
|
7
|
+
|
|
8
|
+
# [1.0.0-alpha.64](https://github.com/powerhouse-inc/document-drive/compare/v1.0.0-alpha.63...v1.0.0-alpha.64) (2024-05-30)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
* update document-model lib version ([#180](https://github.com/powerhouse-inc/document-drive/issues/180)) ([83cec58](https://github.com/powerhouse-inc/document-drive/commit/83cec58cb02388a3b2a643dd5af6c31cd850242d))
|
|
14
|
+
|
|
15
|
+
# [1.0.0-alpha.63](https://github.com/powerhouse-inc/document-drive/compare/v1.0.0-alpha.62...v1.0.0-alpha.63) (2024-05-30)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
### Bug Fixes
|
|
19
|
+
|
|
20
|
+
* operations query ([8dd11c7](https://github.com/powerhouse-inc/document-drive/commit/8dd11c72058452f3e8febac472d01ad56d959e17))
|
|
21
|
+
|
|
22
|
+
# [1.0.0-alpha.62](https://github.com/powerhouse-inc/document-drive/compare/v1.0.0-alpha.61...v1.0.0-alpha.62) (2024-05-30)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
### Bug Fixes
|
|
26
|
+
|
|
27
|
+
* added drive and documentId filter ([718405f](https://github.com/powerhouse-inc/document-drive/commit/718405febbe8e53a3fd392cdd4acf2b041347eaf))
|
|
28
|
+
|
|
29
|
+
# [1.0.0-alpha.61](https://github.com/powerhouse-inc/document-drive/compare/v1.0.0-alpha.60...v1.0.0-alpha.61) (2024-05-30)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
### Features
|
|
33
|
+
|
|
34
|
+
* store resulting state as bytes and only retrieve state for last op of each scope ([c6a5004](https://github.com/powerhouse-inc/document-drive/commit/c6a5004a3dd07ee1ce2f5521bb7a6aa5e970465e))
|
|
35
|
+
|
|
36
|
+
# [1.0.0-alpha.60](https://github.com/powerhouse-inc/document-drive/compare/v1.0.0-alpha.59...v1.0.0-alpha.60) (2024-05-29)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
### Features
|
|
40
|
+
|
|
41
|
+
* avoid duplicated getDocument call ([c0684bc](https://github.com/powerhouse-inc/document-drive/commit/c0684bc0746d8bafb9393cdaeebfb60b38a8a32f))
|
|
42
|
+
|
|
43
|
+
# [1.0.0-alpha.59](https://github.com/powerhouse-inc/document-drive/compare/v1.0.0-alpha.58...v1.0.0-alpha.59) (2024-05-28)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
### Features
|
|
47
|
+
|
|
48
|
+
* enable operation id ([#174](https://github.com/powerhouse-inc/document-drive/issues/174)) ([1d77fd2](https://github.com/powerhouse-inc/document-drive/commit/1d77fd2f6a4618371c6fc4c072d4eab7d27a662a))
|
|
49
|
+
|
|
50
|
+
# [1.0.0-alpha.58](https://github.com/powerhouse-inc/document-drive/compare/v1.0.0-alpha.57...v1.0.0-alpha.58) (2024-05-28)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
### Features
|
|
54
|
+
|
|
55
|
+
* don't save queue job result ([efbf239](https://github.com/powerhouse-inc/document-drive/commit/efbf239e5f77592267446d5c11d670a82f1f6e58))
|
|
56
|
+
|
|
57
|
+
# [1.0.0-alpha.57](https://github.com/powerhouse-inc/document-drive/compare/v1.0.0-alpha.56...v1.0.0-alpha.57) (2024-05-22)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
### Features
|
|
61
|
+
|
|
62
|
+
* new release ([17796e8](https://github.com/powerhouse-inc/document-drive/commit/17796e8577d16d3095a8adf3c40e7bd7146b6142))
|
|
63
|
+
|
|
64
|
+
# [1.0.0-alpha.56](https://github.com/powerhouse-inc/document-drive/compare/v1.0.0-alpha.55...v1.0.0-alpha.56) (2024-05-22)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
### Features
|
|
68
|
+
|
|
69
|
+
* add queues and append only conflict resolution ([d17abd6](https://github.com/powerhouse-inc/document-drive/commit/d17abd664a7381f80faa6530f83ca9e224282ba1)), closes [#153](https://github.com/powerhouse-inc/document-drive/issues/153)
|
|
70
|
+
|
|
1
71
|
# 1.0.0-experimental.1 (2024-05-15)
|
|
2
72
|
|
|
3
73
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "document-drive",
|
|
3
|
-
"version": "1.0.0-experimental.
|
|
3
|
+
"version": "1.0.0-experimental.23",
|
|
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.1.0-experimental.
|
|
38
|
-
"document-model-libs": "
|
|
37
|
+
"document-model": "1.1.0-experimental.5",
|
|
38
|
+
"document-model-libs": "1.55.1-experimental"
|
|
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.1.0-experimental.
|
|
69
|
-
"document-model-libs": "1.
|
|
68
|
+
"document-model": "1.1.0-experimental.5",
|
|
69
|
+
"document-model-libs": "1.55.1-experimental",
|
|
70
70
|
"eslint": "^8.57.0",
|
|
71
71
|
"eslint-config-prettier": "^9.1.0",
|
|
72
72
|
"fake-indexeddb": "^5.0.2",
|
package/src/queue/base.ts
CHANGED
|
@@ -10,7 +10,6 @@ export class MemoryQueue<T, R> implements IQueue<T, R> {
|
|
|
10
10
|
private blocked = false;
|
|
11
11
|
private deleted = false;
|
|
12
12
|
private items: IJob<T>[] = [];
|
|
13
|
-
private results = new Map<JobId, R>();
|
|
14
13
|
private dependencies = new Array<IJob<OperationJob>>();
|
|
15
14
|
|
|
16
15
|
constructor(id: string) {
|
|
@@ -25,15 +24,6 @@ export class MemoryQueue<T, R> implements IQueue<T, R> {
|
|
|
25
24
|
return this.deleted;
|
|
26
25
|
}
|
|
27
26
|
|
|
28
|
-
async setResult(jobId: string, result: R): Promise<void> {
|
|
29
|
-
this.results.set(jobId, result);
|
|
30
|
-
return Promise.resolve();
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
async getResult(jobId: string): Promise<R | undefined> {
|
|
34
|
-
return Promise.resolve(this.results.get(jobId));
|
|
35
|
-
}
|
|
36
|
-
|
|
37
27
|
async addJob(data: IJob<T>) {
|
|
38
28
|
this.items.push(data);
|
|
39
29
|
return Promise.resolve();
|
|
@@ -157,11 +147,6 @@ export class BaseQueueManager implements IQueueManager {
|
|
|
157
147
|
return jobId;
|
|
158
148
|
}
|
|
159
149
|
|
|
160
|
-
async getResult(driveId: string, documentId: string, jobId: JobId): Promise<IOperationResult | undefined> {
|
|
161
|
-
const queue = this.getQueue(driveId, documentId);
|
|
162
|
-
return queue.getResult(jobId);
|
|
163
|
-
}
|
|
164
|
-
|
|
165
150
|
getQueue(driveId: string, documentId?: string) {
|
|
166
151
|
const queueId = this.getQueueId(driveId, documentId);
|
|
167
152
|
let queue = this.queues.find((q) => q.getId() === queueId);
|
|
@@ -237,7 +222,6 @@ export class BaseQueueManager implements IQueueManager {
|
|
|
237
222
|
|
|
238
223
|
try {
|
|
239
224
|
const result = await this.delegate.processOperationJob(nextJob);
|
|
240
|
-
await queue.setResult(nextJob.jobId, result);
|
|
241
225
|
|
|
242
226
|
// unblock the document queues of each add_file operation
|
|
243
227
|
const addFileOperations = nextJob.operations.filter((op) => op.type === "ADD_FILE");
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Unsubscribe } from "nanoevents";
|
|
2
|
+
import { IOperationResult } from "../server";
|
|
3
|
+
import { IQueueManager, QueueEvents } from "./types";
|
|
4
|
+
|
|
5
|
+
export class BaseQueueManager implements IQueueManager {
|
|
6
|
+
|
|
7
|
+
constructor() {
|
|
8
|
+
}
|
|
9
|
+
addJob(job: OperationJob): Promise<string> {
|
|
10
|
+
throw new Error("Method not implemented.");
|
|
11
|
+
}
|
|
12
|
+
getResult(driveId: string, documentId: string, jobId: JobId): Promise<IOperationResult | undefined> {
|
|
13
|
+
throw new Error("Method not implemented.");
|
|
14
|
+
}
|
|
15
|
+
init(processor: OperationJobProcessor, onError: (error: Error) => void): Promise<void> {
|
|
16
|
+
throw new Error("Method not implemented.");
|
|
17
|
+
}
|
|
18
|
+
on<K extends keyof QueueEvents>(this: this, event: K, cb: QueueEvents[K]): Unsubscribe {
|
|
19
|
+
throw new Error("Method not implemented.");
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
}
|
package/src/queue/redis.ts
CHANGED
|
@@ -13,17 +13,6 @@ export class RedisQueue<T, R> implements IQueue<T, R> {
|
|
|
13
13
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
async setResult(jobId: string, result: any): Promise<void> {
|
|
17
|
-
await this.client.hSet(this.id + "-results", jobId, JSON.stringify(result));
|
|
18
|
-
}
|
|
19
|
-
async getResult(jobId: string): Promise<any> {
|
|
20
|
-
const results = await this.client.hGet(this.id + "-results", jobId);
|
|
21
|
-
if (!results) {
|
|
22
|
-
return null;
|
|
23
|
-
}
|
|
24
|
-
return JSON.parse(results);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
16
|
async addJob(data: any) {
|
|
28
17
|
await this.client.lPush(this.id + "-jobs", JSON.stringify(data));
|
|
29
18
|
}
|
package/src/queue/types.ts
CHANGED
|
@@ -23,7 +23,6 @@ export interface IServerDelegate {
|
|
|
23
23
|
|
|
24
24
|
export interface IQueueManager {
|
|
25
25
|
addJob(job: OperationJob): Promise<JobId>;
|
|
26
|
-
getResult(driveId: string, documentId: string, jobId: JobId): Promise<IOperationResult | undefined>;
|
|
27
26
|
getQueue(driveId: string, document?: string): IQueue<OperationJob, IOperationResult>;
|
|
28
27
|
removeQueue(driveId: string, documentId?: string): void;
|
|
29
28
|
getQueueByIndex(index: number): IQueue<OperationJob, IOperationResult> | null;
|
|
@@ -47,8 +46,6 @@ export interface IQueue<T, R> {
|
|
|
47
46
|
isBlocked(): Promise<boolean>;
|
|
48
47
|
isDeleted(): Promise<boolean>;
|
|
49
48
|
setDeleted(deleted: boolean): Promise<void>;
|
|
50
|
-
setResult(jobId: JobId, result: R): Promise<void>;
|
|
51
|
-
getResult(jobId: JobId): Promise<R | undefined>;
|
|
52
49
|
getJobs(): Promise<IJob<T>[]>;
|
|
53
50
|
addDependencies(job: IJob<OperationJob>): Promise<void>;
|
|
54
51
|
removeDependencies(job: IJob<OperationJob>): Promise<void>;
|
package/src/server/index.ts
CHANGED
|
@@ -20,7 +20,8 @@ import {
|
|
|
20
20
|
DocumentModel,
|
|
21
21
|
Operation,
|
|
22
22
|
OperationScope,
|
|
23
|
-
|
|
23
|
+
utils as DocumentUtils,
|
|
24
|
+
Reducer
|
|
24
25
|
} from 'document-model/document';
|
|
25
26
|
import { createNanoEvents, Unsubscribe } from 'nanoevents';
|
|
26
27
|
import { ICache } from '../cache';
|
|
@@ -425,7 +426,8 @@ export class DocumentDriveServer extends BaseDocumentDriveServer {
|
|
|
425
426
|
type: operation.type,
|
|
426
427
|
input: operation.input as object,
|
|
427
428
|
skip: operation.skip,
|
|
428
|
-
context: operation.context
|
|
429
|
+
context: operation.context,
|
|
430
|
+
id: operation.id
|
|
429
431
|
}));
|
|
430
432
|
}
|
|
431
433
|
|
|
@@ -630,6 +632,7 @@ export class DocumentDriveServer extends BaseDocumentDriveServer {
|
|
|
630
632
|
|
|
631
633
|
async _processOperations<T extends Document, A extends Action>(
|
|
632
634
|
drive: string,
|
|
635
|
+
documentId: string | undefined,
|
|
633
636
|
storageDocument: DocumentStorage<T>,
|
|
634
637
|
operations: Operation<A | BaseAction>[]
|
|
635
638
|
) {
|
|
@@ -667,7 +670,7 @@ export class DocumentDriveServer extends BaseDocumentDriveServer {
|
|
|
667
670
|
: merge(trunk, invertedTrunk, reshuffleByTimestamp);
|
|
668
671
|
|
|
669
672
|
const newOperations = newHistory.filter(
|
|
670
|
-
(op
|
|
673
|
+
(op) => trunk.length < 1 || precedes(trunk[trunk.length - 1]!, op)
|
|
671
674
|
);
|
|
672
675
|
|
|
673
676
|
for (const nextOperation of newOperations) {
|
|
@@ -676,15 +679,20 @@ export class DocumentDriveServer extends BaseDocumentDriveServer {
|
|
|
676
679
|
// when dealing with a merge (tail.length > 0) we have to skip hash validation
|
|
677
680
|
// for the operations that were re-indexed (previous hash becomes invalid due the new position in the history)
|
|
678
681
|
if (tail.length > 0) {
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
invertedTrunkOp.hash === nextOperation.hash
|
|
682
|
+
const sourceOperation = operations.find(
|
|
683
|
+
op => op.hash === nextOperation.hash
|
|
682
684
|
);
|
|
685
|
+
|
|
686
|
+
skipHashValidation =
|
|
687
|
+
!sourceOperation ||
|
|
688
|
+
sourceOperation.index !== nextOperation.index ||
|
|
689
|
+
sourceOperation.skip !== nextOperation.skip;
|
|
683
690
|
}
|
|
684
691
|
|
|
685
692
|
try {
|
|
686
693
|
const appliedResult = await this._performOperation(
|
|
687
694
|
drive,
|
|
695
|
+
documentId,
|
|
688
696
|
document,
|
|
689
697
|
nextOperation,
|
|
690
698
|
skipHashValidation
|
|
@@ -720,6 +728,10 @@ export class DocumentDriveServer extends BaseDocumentDriveServer {
|
|
|
720
728
|
private _buildDocument<T extends Document>(
|
|
721
729
|
documentStorage: DocumentStorage<T>, options?: GetDocumentOptions
|
|
722
730
|
): T {
|
|
731
|
+
if (documentStorage.state && (!options || options.checkHashes === false)) {
|
|
732
|
+
return documentStorage as T;
|
|
733
|
+
}
|
|
734
|
+
|
|
723
735
|
const documentModel = this._getDocumentModel(
|
|
724
736
|
documentStorage.documentType
|
|
725
737
|
);
|
|
@@ -730,10 +742,6 @@ export class DocumentDriveServer extends BaseDocumentDriveServer {
|
|
|
730
742
|
) : documentStorage.operations;
|
|
731
743
|
const operations = baseUtils.documentHelpers.garbageCollectDocumentOperations(revisionOperations);
|
|
732
744
|
|
|
733
|
-
if (documentStorage.state && (!options || options.checkHashes === false)) {
|
|
734
|
-
return documentStorage as T;
|
|
735
|
-
}
|
|
736
|
-
|
|
737
745
|
return baseUtils.replayDocument(
|
|
738
746
|
documentStorage.initialState,
|
|
739
747
|
operations,
|
|
@@ -741,12 +749,17 @@ export class DocumentDriveServer extends BaseDocumentDriveServer {
|
|
|
741
749
|
undefined,
|
|
742
750
|
documentStorage,
|
|
743
751
|
undefined,
|
|
744
|
-
{
|
|
752
|
+
{
|
|
753
|
+
...options,
|
|
754
|
+
checkHashes: options?.checkHashes ?? true,
|
|
755
|
+
reuseOperationResultingState: options?.checkHashes ?? true
|
|
756
|
+
}
|
|
745
757
|
) as T;
|
|
746
758
|
}
|
|
747
759
|
|
|
748
760
|
private async _performOperation<T extends Document>(
|
|
749
761
|
drive: string,
|
|
762
|
+
id: string | undefined,
|
|
750
763
|
document: T,
|
|
751
764
|
operation: Operation,
|
|
752
765
|
skipHashValidation = false
|
|
@@ -756,8 +769,28 @@ export class DocumentDriveServer extends BaseDocumentDriveServer {
|
|
|
756
769
|
const signalResults: SignalResult[] = [];
|
|
757
770
|
let newDocument = document;
|
|
758
771
|
|
|
772
|
+
const scope = operation.scope;
|
|
773
|
+
const documentOperations = DocumentUtils.documentHelpers.garbageCollectDocumentOperations(
|
|
774
|
+
{
|
|
775
|
+
...document.operations,
|
|
776
|
+
[scope]: DocumentUtils.documentHelpers.skipHeaderOperations(
|
|
777
|
+
document.operations[scope],
|
|
778
|
+
operation,
|
|
779
|
+
),
|
|
780
|
+
},
|
|
781
|
+
);
|
|
782
|
+
|
|
783
|
+
const lastRemainingOperation = documentOperations[scope].at(-1);
|
|
784
|
+
// if the latest operation doesn't have a resulting state then tries
|
|
785
|
+
// to retrieve it from the db to avoid rerunning all the operations
|
|
786
|
+
if (lastRemainingOperation && !lastRemainingOperation.resultingState) {
|
|
787
|
+
lastRemainingOperation.resultingState = await (id ? this.storage.getOperationResultingState?.(drive, id, lastRemainingOperation.index, lastRemainingOperation.scope, "main") :
|
|
788
|
+
this.storage.getDriveOperationResultingState?.(drive, lastRemainingOperation.index, lastRemainingOperation.scope, "main"))
|
|
789
|
+
}
|
|
790
|
+
|
|
759
791
|
const operationSignals: (() => Promise<SignalResult>)[] = [];
|
|
760
|
-
|
|
792
|
+
const reducer = (document.documentType === "powerhouse/document-drive") ? utils.unsafeReducer as Reducer<unknown, Action, unknown> : documentModel.reducer;
|
|
793
|
+
newDocument = reducer(
|
|
761
794
|
newDocument,
|
|
762
795
|
operation,
|
|
763
796
|
signal => {
|
|
@@ -792,7 +825,7 @@ export class DocumentDriveServer extends BaseDocumentDriveServer {
|
|
|
792
825
|
);
|
|
793
826
|
}
|
|
794
827
|
},
|
|
795
|
-
{ skip: operation.skip, reuseOperationResultingState: true
|
|
828
|
+
{ skip: operation.skip, reuseOperationResultingState: true }
|
|
796
829
|
) as T;
|
|
797
830
|
|
|
798
831
|
const appliedOperation = newDocument.operations[operation.scope].filter(
|
|
@@ -837,7 +870,6 @@ export class DocumentDriveServer extends BaseDocumentDriveServer {
|
|
|
837
870
|
callback: (document: DocumentStorage) => Promise<{
|
|
838
871
|
operations: Operation[];
|
|
839
872
|
header: DocumentHeader;
|
|
840
|
-
newState: State<any, any> | undefined;
|
|
841
873
|
}>
|
|
842
874
|
) {
|
|
843
875
|
if (!this.storage.addDocumentOperationsWithTransaction) {
|
|
@@ -884,7 +916,6 @@ export class DocumentDriveServer extends BaseDocumentDriveServer {
|
|
|
884
916
|
}
|
|
885
917
|
});
|
|
886
918
|
const unsubscribeError = this.queueManager.on('jobFailed', (job, error) => {
|
|
887
|
-
console.log("test")
|
|
888
919
|
if (job.jobId === jobId) {
|
|
889
920
|
unsubscribe();
|
|
890
921
|
unsubscribeError();
|
|
@@ -913,6 +944,7 @@ export class DocumentDriveServer extends BaseDocumentDriveServer {
|
|
|
913
944
|
await this._addOperations(drive, id, async documentStorage => {
|
|
914
945
|
const result = await this._processOperations(
|
|
915
946
|
drive,
|
|
947
|
+
id,
|
|
916
948
|
documentStorage,
|
|
917
949
|
operations
|
|
918
950
|
);
|
|
@@ -1098,7 +1130,7 @@ export class DocumentDriveServer extends BaseDocumentDriveServer {
|
|
|
1098
1130
|
const result = await this._processOperations<
|
|
1099
1131
|
DocumentDriveDocument,
|
|
1100
1132
|
DocumentDriveAction
|
|
1101
|
-
>(drive, documentStorage, operations.slice());
|
|
1133
|
+
>(drive, undefined, documentStorage, operations.slice());
|
|
1102
1134
|
|
|
1103
1135
|
document = result.document;
|
|
1104
1136
|
operationsApplied.push(...result.operationsApplied);
|
|
@@ -1259,7 +1291,7 @@ export class DocumentDriveServer extends BaseDocumentDriveServer {
|
|
|
1259
1291
|
const document = await this.getDrive(drive);
|
|
1260
1292
|
const operations = this._buildOperations(document, actions);
|
|
1261
1293
|
const result = await this.queueDriveOperations(drive, operations);
|
|
1262
|
-
return result
|
|
1294
|
+
return result;
|
|
1263
1295
|
}
|
|
1264
1296
|
|
|
1265
1297
|
async addInternalListener(
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ListenerFilter, Trigger
|
|
1
|
+
import { ListenerFilter, Trigger } from 'document-model-libs/document-drive';
|
|
2
2
|
import { Operation, OperationScope } from 'document-model/document';
|
|
3
3
|
import { PULL_DRIVE_INTERVAL } from '../..';
|
|
4
4
|
import { generateUUID } from '../../../utils';
|
|
@@ -153,6 +153,7 @@ export class PullResponderTransmitter implements IPullResponderTransmitter {
|
|
|
153
153
|
scope
|
|
154
154
|
branch
|
|
155
155
|
operations {
|
|
156
|
+
id
|
|
156
157
|
timestamp
|
|
157
158
|
skip
|
|
158
159
|
type
|
|
@@ -237,13 +238,11 @@ export class PullResponderTransmitter implements IPullResponderTransmitter {
|
|
|
237
238
|
const listenerRevisions: ListenerRevisionWithError[] = [];
|
|
238
239
|
|
|
239
240
|
for (const strand of strands) {
|
|
240
|
-
const operations: Operation[] = strand.operations.map(
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
})
|
|
246
|
-
);
|
|
241
|
+
const operations: Operation[] = strand.operations.map(op => ({
|
|
242
|
+
...op,
|
|
243
|
+
scope: strand.scope,
|
|
244
|
+
branch: strand.branch
|
|
245
|
+
}));
|
|
247
246
|
|
|
248
247
|
let error: Error | undefined = undefined;
|
|
249
248
|
try {
|
package/src/server/types.ts
CHANGED
|
@@ -14,6 +14,7 @@ import type {
|
|
|
14
14
|
Document,
|
|
15
15
|
Operation,
|
|
16
16
|
OperationScope,
|
|
17
|
+
ReducerOptions,
|
|
17
18
|
Signal,
|
|
18
19
|
State
|
|
19
20
|
} from 'document-model/document';
|
|
@@ -116,6 +117,7 @@ export type OperationUpdate = {
|
|
|
116
117
|
input: object;
|
|
117
118
|
hash: string;
|
|
118
119
|
context?: ActionContext;
|
|
120
|
+
id?: string;
|
|
119
121
|
};
|
|
120
122
|
|
|
121
123
|
export type StrandUpdate = {
|
|
@@ -140,7 +142,7 @@ export type PartialRecord<K extends keyof any, T> = {
|
|
|
140
142
|
|
|
141
143
|
export type RevisionsFilter = PartialRecord<OperationScope, number>;
|
|
142
144
|
|
|
143
|
-
export type GetDocumentOptions = {
|
|
145
|
+
export type GetDocumentOptions = ReducerOptions & {
|
|
144
146
|
revisions?: RevisionsFilter;
|
|
145
147
|
checkHashes?: boolean;
|
|
146
148
|
};
|
package/src/storage/prisma.ts
CHANGED
|
@@ -1,30 +1,44 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Prisma, PrismaClient } from '@prisma/client';
|
|
2
2
|
import { PrismaClientKnownRequestError } from '@prisma/client/runtime/library';
|
|
3
|
-
import { backOff, IBackOffOptions } from "exponential-backoff";
|
|
4
3
|
import {
|
|
5
4
|
DocumentDriveAction,
|
|
6
5
|
DocumentDriveLocalState,
|
|
7
6
|
DocumentDriveState
|
|
8
7
|
} from 'document-model-libs/document-drive';
|
|
9
8
|
import type {
|
|
9
|
+
Action,
|
|
10
|
+
AttachmentInput,
|
|
11
|
+
DocumentOperations,
|
|
12
|
+
FileRegistry,
|
|
10
13
|
BaseAction,
|
|
14
|
+
Document,
|
|
11
15
|
DocumentHeader,
|
|
12
16
|
ExtendedState,
|
|
13
17
|
Operation,
|
|
14
18
|
OperationScope,
|
|
15
19
|
State
|
|
16
20
|
} from 'document-model/document';
|
|
21
|
+
import { IBackOffOptions, backOff } from 'exponential-backoff';
|
|
17
22
|
import { ConflictOperationError } from '../server/error';
|
|
18
23
|
import { logger } from '../utils/logger';
|
|
19
24
|
import { DocumentDriveStorage, DocumentStorage, IDriveStorage } from './types';
|
|
20
25
|
|
|
21
|
-
type Transaction =
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
26
|
+
type Transaction =
|
|
27
|
+
| Omit<
|
|
28
|
+
PrismaClient<Prisma.PrismaClientOptions, never>,
|
|
29
|
+
| '$connect'
|
|
30
|
+
| '$disconnect'
|
|
31
|
+
| '$on'
|
|
32
|
+
| '$transaction'
|
|
33
|
+
| '$use'
|
|
34
|
+
| '$extends'
|
|
35
|
+
>
|
|
36
|
+
| ExtendedPrismaClient;
|
|
25
37
|
|
|
26
38
|
function storageToOperation(
|
|
27
|
-
op: Prisma.$OperationPayload['scalars']
|
|
39
|
+
op: Prisma.$OperationPayload['scalars'] & {
|
|
40
|
+
attachments?: AttachmentInput[];
|
|
41
|
+
}
|
|
28
42
|
): Operation {
|
|
29
43
|
const operation: Operation = {
|
|
30
44
|
skip: op.skip,
|
|
@@ -34,8 +48,10 @@ function storageToOperation(
|
|
|
34
48
|
input: JSON.parse(op.input),
|
|
35
49
|
type: op.type,
|
|
36
50
|
scope: op.scope as OperationScope,
|
|
37
|
-
resultingState: op.resultingState
|
|
38
|
-
|
|
51
|
+
resultingState: op.resultingState
|
|
52
|
+
? op.resultingState.toString()
|
|
53
|
+
: undefined,
|
|
54
|
+
attachments: op.attachments
|
|
39
55
|
};
|
|
40
56
|
if (op.context) {
|
|
41
57
|
operation.context = op.context as Prisma.JsonObject;
|
|
@@ -45,27 +61,32 @@ function storageToOperation(
|
|
|
45
61
|
|
|
46
62
|
export type PrismaStorageOptions = {
|
|
47
63
|
transactionRetryBackoff?: IBackOffOptions;
|
|
48
|
-
}
|
|
64
|
+
};
|
|
49
65
|
|
|
50
|
-
function getRetryTransactionsClient<T extends PrismaClient>(
|
|
66
|
+
function getRetryTransactionsClient<T extends PrismaClient>(
|
|
67
|
+
prisma: T,
|
|
68
|
+
backOffOptions?: Partial<IBackOffOptions>
|
|
69
|
+
) {
|
|
51
70
|
return prisma.$extends({
|
|
52
71
|
client: {
|
|
53
|
-
$transaction: (...args: Parameters<T[
|
|
72
|
+
$transaction: (...args: Parameters<T['$transaction']>) => {
|
|
54
73
|
// eslint-disable-next-line prefer-spread
|
|
55
74
|
return backOff(() => prisma.$transaction.apply(prisma, args), {
|
|
56
|
-
retry:
|
|
75
|
+
retry: e => {
|
|
57
76
|
// Retry the transaction only if the error was due to a write conflict or deadlock
|
|
58
77
|
// See: https://www.prisma.io/docs/reference/api-reference/error-reference#p2034
|
|
59
|
-
return (e as { code: string }).code ===
|
|
78
|
+
return (e as { code: string }).code === 'P2034';
|
|
60
79
|
},
|
|
61
|
-
...backOffOptions
|
|
80
|
+
...backOffOptions
|
|
62
81
|
});
|
|
63
82
|
}
|
|
64
83
|
}
|
|
65
84
|
});
|
|
66
85
|
}
|
|
67
86
|
|
|
68
|
-
type ExtendedPrismaClient = ReturnType<
|
|
87
|
+
type ExtendedPrismaClient = ReturnType<
|
|
88
|
+
typeof getRetryTransactionsClient<PrismaClient>
|
|
89
|
+
>;
|
|
69
90
|
|
|
70
91
|
export class PrismaStorage implements IDriveStorage {
|
|
71
92
|
private db: ExtendedPrismaClient;
|
|
@@ -74,9 +95,8 @@ export class PrismaStorage implements IDriveStorage {
|
|
|
74
95
|
const backOffOptions = options?.transactionRetryBackoff;
|
|
75
96
|
this.db = getRetryTransactionsClient(db, {
|
|
76
97
|
...backOffOptions,
|
|
77
|
-
jitter: backOffOptions?.jitter ??
|
|
98
|
+
jitter: backOffOptions?.jitter ?? 'full'
|
|
78
99
|
});
|
|
79
|
-
|
|
80
100
|
}
|
|
81
101
|
|
|
82
102
|
async createDrive(id: string, drive: DocumentDriveStorage): Promise<void> {
|
|
@@ -137,7 +157,7 @@ export class PrismaStorage implements IDriveStorage {
|
|
|
137
157
|
initialState: JSON.stringify(document.initialState),
|
|
138
158
|
lastModified: document.lastModified,
|
|
139
159
|
revision: JSON.stringify(document.revision),
|
|
140
|
-
id
|
|
160
|
+
id
|
|
141
161
|
}
|
|
142
162
|
});
|
|
143
163
|
}
|
|
@@ -147,14 +167,8 @@ export class PrismaStorage implements IDriveStorage {
|
|
|
147
167
|
drive: string,
|
|
148
168
|
id: string,
|
|
149
169
|
operations: Operation[],
|
|
150
|
-
header: DocumentHeader
|
|
151
|
-
newState: State<any, any> | undefined = undefined
|
|
170
|
+
header: DocumentHeader
|
|
152
171
|
): Promise<void> {
|
|
153
|
-
const document = await this.getDocument(drive, id, tx);
|
|
154
|
-
if (!document) {
|
|
155
|
-
throw new Error(`Document with id ${id} not found`);
|
|
156
|
-
}
|
|
157
|
-
|
|
158
172
|
try {
|
|
159
173
|
await tx.operation.createMany({
|
|
160
174
|
data: operations.map(op => ({
|
|
@@ -169,7 +183,9 @@ export class PrismaStorage implements IDriveStorage {
|
|
|
169
183
|
branch: 'main',
|
|
170
184
|
skip: op.skip,
|
|
171
185
|
context: op.context,
|
|
172
|
-
resultingState:
|
|
186
|
+
resultingState: op.resultingState
|
|
187
|
+
? Buffer.from(JSON.stringify(op.resultingState))
|
|
188
|
+
: undefined
|
|
173
189
|
}))
|
|
174
190
|
});
|
|
175
191
|
|
|
@@ -180,10 +196,34 @@ export class PrismaStorage implements IDriveStorage {
|
|
|
180
196
|
},
|
|
181
197
|
data: {
|
|
182
198
|
lastModified: header.lastModified,
|
|
183
|
-
revision: JSON.stringify(header.revision)
|
|
184
|
-
state: JSON.stringify(newState)
|
|
199
|
+
revision: JSON.stringify(header.revision)
|
|
185
200
|
}
|
|
186
201
|
});
|
|
202
|
+
|
|
203
|
+
await Promise.all(
|
|
204
|
+
operations
|
|
205
|
+
.filter(o => o.attachments?.length)
|
|
206
|
+
.map(op => {
|
|
207
|
+
return tx.operation.update({
|
|
208
|
+
where: {
|
|
209
|
+
unique_operation: {
|
|
210
|
+
driveId: drive,
|
|
211
|
+
documentId: id,
|
|
212
|
+
index: op.index,
|
|
213
|
+
scope: op.scope,
|
|
214
|
+
branch: 'main'
|
|
215
|
+
}
|
|
216
|
+
},
|
|
217
|
+
data: {
|
|
218
|
+
attachments: {
|
|
219
|
+
createMany: {
|
|
220
|
+
data: op.attachments ?? []
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
})
|
|
226
|
+
);
|
|
187
227
|
} catch (e) {
|
|
188
228
|
// P2002: Unique constraint failed
|
|
189
229
|
// Operation with existing index
|
|
@@ -210,6 +250,7 @@ export class PrismaStorage implements IDriveStorage {
|
|
|
210
250
|
);
|
|
211
251
|
|
|
212
252
|
if (!existingOperation || !conflictOp) {
|
|
253
|
+
console.error(e);
|
|
213
254
|
throw e;
|
|
214
255
|
} else {
|
|
215
256
|
throw new ConflictOperationError(
|
|
@@ -229,7 +270,7 @@ export class PrismaStorage implements IDriveStorage {
|
|
|
229
270
|
callback: (document: DocumentStorage) => Promise<{
|
|
230
271
|
operations: Operation[];
|
|
231
272
|
header: DocumentHeader;
|
|
232
|
-
newState?: State<any, any> | undefined
|
|
273
|
+
newState?: State<any, any> | undefined;
|
|
233
274
|
}>
|
|
234
275
|
) {
|
|
235
276
|
let result: {
|
|
@@ -238,24 +279,25 @@ export class PrismaStorage implements IDriveStorage {
|
|
|
238
279
|
newState?: State<any, any> | undefined;
|
|
239
280
|
} | null = null;
|
|
240
281
|
|
|
241
|
-
await this.db.$transaction(
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
const { operations, header, newState } = result;
|
|
249
|
-
return this._addDocumentOperations(
|
|
250
|
-
tx,
|
|
251
|
-
drive,
|
|
252
|
-
id,
|
|
253
|
-
operations,
|
|
254
|
-
header,
|
|
255
|
-
newState
|
|
256
|
-
);
|
|
257
|
-
}, { isolationLevel: "Serializable" });
|
|
282
|
+
await this.db.$transaction(
|
|
283
|
+
async tx => {
|
|
284
|
+
const document = await this.getDocument(drive, id, tx);
|
|
285
|
+
if (!document) {
|
|
286
|
+
throw new Error(`Document with id ${id} not found`);
|
|
287
|
+
}
|
|
288
|
+
result = await callback(document);
|
|
258
289
|
|
|
290
|
+
const { operations, header, newState } = result;
|
|
291
|
+
return this._addDocumentOperations(
|
|
292
|
+
tx,
|
|
293
|
+
drive,
|
|
294
|
+
id,
|
|
295
|
+
operations,
|
|
296
|
+
header
|
|
297
|
+
);
|
|
298
|
+
},
|
|
299
|
+
{ isolationLevel: 'Serializable' }
|
|
300
|
+
);
|
|
259
301
|
|
|
260
302
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
261
303
|
if (!result) {
|
|
@@ -269,14 +311,14 @@ export class PrismaStorage implements IDriveStorage {
|
|
|
269
311
|
drive: string,
|
|
270
312
|
id: string,
|
|
271
313
|
operations: Operation[],
|
|
272
|
-
header: DocumentHeader
|
|
314
|
+
header: DocumentHeader
|
|
273
315
|
): Promise<void> {
|
|
274
316
|
return this._addDocumentOperations(
|
|
275
317
|
this.db,
|
|
276
318
|
drive,
|
|
277
319
|
id,
|
|
278
320
|
operations,
|
|
279
|
-
header
|
|
321
|
+
header
|
|
280
322
|
);
|
|
281
323
|
}
|
|
282
324
|
|
|
@@ -300,25 +342,18 @@ export class PrismaStorage implements IDriveStorage {
|
|
|
300
342
|
where: {
|
|
301
343
|
id: id,
|
|
302
344
|
driveId: driveId
|
|
303
|
-
}
|
|
345
|
+
}
|
|
304
346
|
});
|
|
305
347
|
return count > 0;
|
|
306
348
|
}
|
|
307
349
|
|
|
308
350
|
async getDocument(driveId: string, id: string, tx?: Transaction) {
|
|
309
|
-
const
|
|
351
|
+
const prisma = tx ?? this.db;
|
|
352
|
+
const result = await prisma.document.findUnique({
|
|
310
353
|
where: {
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
include: {
|
|
315
|
-
operations: {
|
|
316
|
-
orderBy: {
|
|
317
|
-
index: 'asc'
|
|
318
|
-
},
|
|
319
|
-
include: {
|
|
320
|
-
attachments: true
|
|
321
|
-
}
|
|
354
|
+
id_driveId: {
|
|
355
|
+
driveId,
|
|
356
|
+
id
|
|
322
357
|
}
|
|
323
358
|
}
|
|
324
359
|
});
|
|
@@ -327,8 +362,72 @@ export class PrismaStorage implements IDriveStorage {
|
|
|
327
362
|
throw new Error(`Document with id ${id} not found`);
|
|
328
363
|
}
|
|
329
364
|
|
|
365
|
+
// retrieves operations with resulting state
|
|
366
|
+
// for the last operation of each scope
|
|
367
|
+
const operations = await prisma.$queryRaw<
|
|
368
|
+
Prisma.$OperationPayload['scalars'][]
|
|
369
|
+
>`
|
|
370
|
+
WITH ranked_operations AS (
|
|
371
|
+
SELECT
|
|
372
|
+
*,
|
|
373
|
+
ROW_NUMBER() OVER (PARTITION BY scope ORDER BY index DESC) AS rn
|
|
374
|
+
FROM "Operation"
|
|
375
|
+
)
|
|
376
|
+
SELECT
|
|
377
|
+
"id",
|
|
378
|
+
"opId",
|
|
379
|
+
"scope",
|
|
380
|
+
"branch",
|
|
381
|
+
"index",
|
|
382
|
+
"skip",
|
|
383
|
+
"hash",
|
|
384
|
+
"timestamp",
|
|
385
|
+
"input",
|
|
386
|
+
"type",
|
|
387
|
+
"context",
|
|
388
|
+
CASE
|
|
389
|
+
WHEN rn = 1 THEN "resultingState"
|
|
390
|
+
ELSE NULL
|
|
391
|
+
END AS "resultingState"
|
|
392
|
+
FROM ranked_operations
|
|
393
|
+
WHERE "driveId" = ${driveId} AND "documentId" = ${id}
|
|
394
|
+
ORDER BY scope, index;
|
|
395
|
+
`;
|
|
396
|
+
|
|
397
|
+
const operationIds = operations.map(o => o.id);
|
|
398
|
+
const attachments = await prisma.attachment.findMany({
|
|
399
|
+
where: {
|
|
400
|
+
operationId: {
|
|
401
|
+
in: operationIds
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
const fileRegistry: FileRegistry = {};
|
|
407
|
+
const operationsByScope = operations.reduce<DocumentOperations<Action>>(
|
|
408
|
+
(acc, operation) => {
|
|
409
|
+
const scope = operation.scope as OperationScope;
|
|
410
|
+
if (!acc[scope]) {
|
|
411
|
+
acc[scope] = [];
|
|
412
|
+
}
|
|
413
|
+
const result = storageToOperation(operation);
|
|
414
|
+
result.attachments = attachments.filter(
|
|
415
|
+
a => a.operationId === operation.id
|
|
416
|
+
);
|
|
417
|
+
result.attachments.forEach(({ hash, ...file }) => {
|
|
418
|
+
fileRegistry[hash] = file;
|
|
419
|
+
});
|
|
420
|
+
acc[scope].push(result);
|
|
421
|
+
return acc;
|
|
422
|
+
},
|
|
423
|
+
{
|
|
424
|
+
global: [],
|
|
425
|
+
local: []
|
|
426
|
+
}
|
|
427
|
+
);
|
|
428
|
+
|
|
330
429
|
const dbDoc = result;
|
|
331
|
-
const doc = {
|
|
430
|
+
const doc: Document = {
|
|
332
431
|
created: dbDoc.created.toISOString(),
|
|
333
432
|
name: dbDoc.name ? dbDoc.name : '',
|
|
334
433
|
documentType: dbDoc.documentType,
|
|
@@ -336,22 +435,19 @@ export class PrismaStorage implements IDriveStorage {
|
|
|
336
435
|
DocumentDriveState,
|
|
337
436
|
DocumentDriveLocalState
|
|
338
437
|
>,
|
|
339
|
-
state:
|
|
438
|
+
state: undefined,
|
|
340
439
|
lastModified: new Date(dbDoc.lastModified).toISOString(),
|
|
341
|
-
operations:
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
},
|
|
349
|
-
clipboard: dbDoc.operations
|
|
350
|
-
.filter(op => op.clipboard)
|
|
351
|
-
.map(storageToOperation),
|
|
352
|
-
revision: JSON.parse(dbDoc.revision) as Record<OperationScope, number>
|
|
440
|
+
operations: operationsByScope,
|
|
441
|
+
clipboard: [],
|
|
442
|
+
revision: JSON.parse(dbDoc.revision) as Record<
|
|
443
|
+
OperationScope,
|
|
444
|
+
number
|
|
445
|
+
>,
|
|
446
|
+
attachments: {}
|
|
353
447
|
};
|
|
354
448
|
|
|
449
|
+
|
|
450
|
+
|
|
355
451
|
return doc;
|
|
356
452
|
}
|
|
357
453
|
|
|
@@ -409,4 +505,23 @@ export class PrismaStorage implements IDriveStorage {
|
|
|
409
505
|
});
|
|
410
506
|
await this.deleteDocument('drives', id);
|
|
411
507
|
}
|
|
508
|
+
|
|
509
|
+
async getOperationResultingState(driveId: string, documentId: string, index: number, scope: string, branch: string): Promise<unknown> {
|
|
510
|
+
const operation = await this.db.operation.findUnique({
|
|
511
|
+
where: {
|
|
512
|
+
unique_operation: {
|
|
513
|
+
driveId,
|
|
514
|
+
documentId,
|
|
515
|
+
index,
|
|
516
|
+
scope,
|
|
517
|
+
branch
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
});
|
|
521
|
+
return operation?.resultingState?.toString();
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
getDriveOperationResultingState(drive: string, index: number, scope: string, branch: string): Promise<unknown> {
|
|
525
|
+
return this.getOperationResultingState("drives", drive, index, scope, branch);
|
|
526
|
+
}
|
|
412
527
|
}
|
package/src/storage/sequelize.ts
CHANGED
|
@@ -25,8 +25,8 @@ export class SequelizeStorage implements IDriveStorage {
|
|
|
25
25
|
type: DataTypes.STRING,
|
|
26
26
|
primaryKey: true
|
|
27
27
|
},
|
|
28
|
-
id: DataTypes.STRING
|
|
29
|
-
})
|
|
28
|
+
id: DataTypes.STRING
|
|
29
|
+
});
|
|
30
30
|
const Document = this.db.define('document', {
|
|
31
31
|
id: {
|
|
32
32
|
type: DataTypes.STRING,
|
|
@@ -155,7 +155,7 @@ export class SequelizeStorage implements IDriveStorage {
|
|
|
155
155
|
drive: string,
|
|
156
156
|
id: string,
|
|
157
157
|
operations: Operation[],
|
|
158
|
-
header: DocumentHeader
|
|
158
|
+
header: DocumentHeader
|
|
159
159
|
): Promise<void> {
|
|
160
160
|
const document = await this.getDocument(drive, id);
|
|
161
161
|
if (!document) {
|
|
@@ -177,7 +177,8 @@ export class SequelizeStorage implements IDriveStorage {
|
|
|
177
177
|
timestamp: op.timestamp,
|
|
178
178
|
type: op.type,
|
|
179
179
|
scope: op.scope,
|
|
180
|
-
branch: 'main'
|
|
180
|
+
branch: 'main',
|
|
181
|
+
opId: op.id
|
|
181
182
|
}))
|
|
182
183
|
);
|
|
183
184
|
|
|
@@ -284,8 +285,8 @@ export class SequelizeStorage implements IDriveStorage {
|
|
|
284
285
|
where: {
|
|
285
286
|
id: id,
|
|
286
287
|
driveId: driveId
|
|
287
|
-
}
|
|
288
|
-
})
|
|
288
|
+
}
|
|
289
|
+
});
|
|
289
290
|
|
|
290
291
|
return count > 0;
|
|
291
292
|
}
|
|
@@ -323,6 +324,7 @@ export class SequelizeStorage implements IDriveStorage {
|
|
|
323
324
|
input: JSON;
|
|
324
325
|
type: string;
|
|
325
326
|
scope: string;
|
|
327
|
+
opId?: string;
|
|
326
328
|
}
|
|
327
329
|
];
|
|
328
330
|
revision: Required<Record<OperationScope, number>>;
|
|
@@ -348,13 +350,15 @@ export class SequelizeStorage implements IDriveStorage {
|
|
|
348
350
|
input: JSON;
|
|
349
351
|
type: string;
|
|
350
352
|
scope: string;
|
|
353
|
+
opId?: string;
|
|
351
354
|
}) => ({
|
|
352
355
|
hash: op.hash,
|
|
353
356
|
index: op.index,
|
|
354
357
|
timestamp: new Date(op.timestamp).toISOString(),
|
|
355
358
|
input: op.input,
|
|
356
359
|
type: op.type,
|
|
357
|
-
scope: op.scope as OperationScope
|
|
360
|
+
scope: op.scope as OperationScope,
|
|
361
|
+
id: op.opId
|
|
358
362
|
// attachments: fileRegistry
|
|
359
363
|
})
|
|
360
364
|
);
|
package/src/storage/types.ts
CHANGED
|
@@ -39,10 +39,10 @@ export interface IStorage {
|
|
|
39
39
|
callback: (document: DocumentStorage) => Promise<{
|
|
40
40
|
operations: Operation[];
|
|
41
41
|
header: DocumentHeader;
|
|
42
|
-
newState: State<any, any> | undefined
|
|
43
42
|
}>
|
|
44
43
|
): Promise<void>;
|
|
45
44
|
deleteDocument(drive: string, id: string): Promise<void>;
|
|
45
|
+
getOperationResultingState?(drive: string, id: string, index: number, scope: string, branch: string): Promise<unknown>;
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
export interface IDriveStorage extends IStorage {
|
|
@@ -64,4 +64,5 @@ export interface IDriveStorage extends IStorage {
|
|
|
64
64
|
header: DocumentHeader;
|
|
65
65
|
}>
|
|
66
66
|
): Promise<void>;
|
|
67
|
+
getDriveOperationResultingState?(drive: string, index: number, scope: string, branch: string): Promise<unknown>;
|
|
67
68
|
}
|