document-drive 0.0.27 → 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 +18 -10
- package/src/server/error.ts +18 -0
- package/src/server/index.ts +753 -85
- package/src/server/listener/index.ts +2 -0
- package/src/server/listener/manager.ts +382 -0
- package/src/server/listener/transmitter/index.ts +3 -0
- package/src/server/listener/transmitter/pull-responder.ts +325 -0
- package/src/server/listener/transmitter/switchboard-push.ts +63 -0
- package/src/server/listener/transmitter/types.ts +18 -0
- package/src/server/types.ts +209 -23
- package/src/storage/browser.ts +9 -3
- package/src/storage/filesystem.ts +11 -5
- package/src/storage/index.ts +0 -4
- package/src/storage/memory.ts +14 -5
- package/src/storage/prisma.ts +79 -24
- package/src/storage/types.ts +2 -1
- package/src/utils/graphql.ts +46 -0
- package/src/utils/index.ts +77 -0
- package/src/utils.ts +0 -32
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import stringify from 'json-stringify-deterministic';
|
|
2
|
+
import { gql, requestGraphql } from '../../../utils/graphql';
|
|
3
|
+
import {
|
|
4
|
+
BaseDocumentDriveServer,
|
|
5
|
+
Listener,
|
|
6
|
+
ListenerRevision,
|
|
7
|
+
StrandUpdate
|
|
8
|
+
} from '../../types';
|
|
9
|
+
import { ITransmitter } from './types';
|
|
10
|
+
|
|
11
|
+
export class SwitchboardPushTransmitter implements ITransmitter {
|
|
12
|
+
private drive: BaseDocumentDriveServer;
|
|
13
|
+
private listener: Listener;
|
|
14
|
+
private targetURL: string;
|
|
15
|
+
|
|
16
|
+
constructor(listener: Listener, drive: BaseDocumentDriveServer) {
|
|
17
|
+
this.listener = listener;
|
|
18
|
+
this.drive = drive;
|
|
19
|
+
this.targetURL = listener.callInfo!.data!;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async transmit(strands: StrandUpdate[]): Promise<ListenerRevision[]> {
|
|
23
|
+
// Send Graphql mutation to switchboard
|
|
24
|
+
try {
|
|
25
|
+
const { pushUpdates } = await requestGraphql<{
|
|
26
|
+
pushUpdates: ListenerRevision[];
|
|
27
|
+
}>(
|
|
28
|
+
this.targetURL,
|
|
29
|
+
gql`
|
|
30
|
+
mutation pushUpdates($strands: [InputStrandUpdate!]) {
|
|
31
|
+
pushUpdates(strands: $strands) {
|
|
32
|
+
driveId
|
|
33
|
+
documentId
|
|
34
|
+
scope
|
|
35
|
+
branch
|
|
36
|
+
status
|
|
37
|
+
revision
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
`,
|
|
41
|
+
{
|
|
42
|
+
strands: strands.map(strand => ({
|
|
43
|
+
...strand,
|
|
44
|
+
operations: strand.operations.map(op => ({
|
|
45
|
+
...op,
|
|
46
|
+
input: stringify(op.input)
|
|
47
|
+
}))
|
|
48
|
+
}))
|
|
49
|
+
}
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
if (!pushUpdates) {
|
|
53
|
+
throw new Error("Couldn't update listener revision");
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return pushUpdates;
|
|
57
|
+
} catch (e) {
|
|
58
|
+
console.error(e);
|
|
59
|
+
throw e;
|
|
60
|
+
}
|
|
61
|
+
return [];
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import {
|
|
2
|
+
PullResponderTriggerData,
|
|
3
|
+
Trigger
|
|
4
|
+
} from 'document-model-libs/document-drive';
|
|
5
|
+
import { ListenerRevision, StrandUpdate } from '../..';
|
|
6
|
+
|
|
7
|
+
export interface ITransmitter {
|
|
8
|
+
transmit(strands: StrandUpdate[]): Promise<ListenerRevision[]>;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface InternalTransmitterService extends ITransmitter {
|
|
12
|
+
getName(): string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export type PullResponderTrigger = Omit<Trigger, 'data' | 'type'> & {
|
|
16
|
+
data: PullResponderTriggerData;
|
|
17
|
+
type: 'PullResponder';
|
|
18
|
+
};
|
package/src/server/types.ts
CHANGED
|
@@ -2,68 +2,254 @@ import type {
|
|
|
2
2
|
DocumentDriveAction,
|
|
3
3
|
DocumentDriveDocument,
|
|
4
4
|
DocumentDriveLocalState,
|
|
5
|
-
DocumentDriveState
|
|
5
|
+
DocumentDriveState,
|
|
6
|
+
ListenerCallInfo,
|
|
7
|
+
ListenerFilter
|
|
6
8
|
} from 'document-model-libs/document-drive';
|
|
7
9
|
import type {
|
|
8
10
|
BaseAction,
|
|
11
|
+
CreateChildDocumentInput,
|
|
9
12
|
Document,
|
|
10
13
|
Operation,
|
|
14
|
+
OperationScope,
|
|
11
15
|
Signal,
|
|
12
16
|
State
|
|
13
17
|
} from 'document-model/document';
|
|
18
|
+
import { OperationError } from './error';
|
|
19
|
+
import { ITransmitter } from './listener/transmitter/types';
|
|
14
20
|
|
|
15
21
|
export type DriveInput = State<
|
|
16
|
-
Omit<DocumentDriveState, '__typename' | 'nodes'
|
|
22
|
+
Omit<DocumentDriveState, '__typename' | 'id' | 'nodes'> & { id?: string },
|
|
17
23
|
DocumentDriveLocalState
|
|
18
24
|
>;
|
|
19
25
|
|
|
20
|
-
export type
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
26
|
+
export type RemoteDriveOptions = DocumentDriveLocalState & {
|
|
27
|
+
// TODO make local state optional
|
|
28
|
+
pullFilter?: ListenerFilter;
|
|
29
|
+
pullInterval?: number;
|
|
24
30
|
};
|
|
25
31
|
|
|
32
|
+
export type CreateDocumentInput = CreateChildDocumentInput;
|
|
33
|
+
|
|
26
34
|
export type SignalResult = {
|
|
27
35
|
signal: Signal;
|
|
28
36
|
result: unknown; // infer from return types on document-model
|
|
29
37
|
};
|
|
30
38
|
|
|
31
39
|
export type IOperationResult<T extends Document = Document> = {
|
|
32
|
-
|
|
33
|
-
error?:
|
|
40
|
+
status: UpdateStatus;
|
|
41
|
+
error?: OperationError;
|
|
34
42
|
operations: Operation[];
|
|
35
43
|
document: T | undefined;
|
|
36
44
|
signals: SignalResult[];
|
|
37
45
|
};
|
|
38
46
|
|
|
39
|
-
export
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
47
|
+
export type SynchronizationUnit = {
|
|
48
|
+
syncId: string;
|
|
49
|
+
driveId: string;
|
|
50
|
+
documentId: string;
|
|
51
|
+
documentType: string;
|
|
52
|
+
scope: string;
|
|
53
|
+
branch: string;
|
|
54
|
+
lastUpdated: string;
|
|
55
|
+
revision: number;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export type Listener = {
|
|
59
|
+
driveId: string;
|
|
60
|
+
listenerId: string;
|
|
61
|
+
label?: string;
|
|
62
|
+
block: boolean;
|
|
63
|
+
system: boolean;
|
|
64
|
+
filter: ListenerFilter;
|
|
65
|
+
callInfo?: ListenerCallInfo;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
export type CreateListenerInput = {
|
|
69
|
+
driveId: string;
|
|
70
|
+
label?: string;
|
|
71
|
+
block: boolean;
|
|
72
|
+
system: boolean;
|
|
73
|
+
filter: ListenerFilter;
|
|
74
|
+
callInfo?: ListenerCallInfo;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
export enum TransmitterType {
|
|
78
|
+
Internal,
|
|
79
|
+
SwitchboardPush,
|
|
80
|
+
PullResponder,
|
|
81
|
+
SecureConnect,
|
|
82
|
+
MatrixConnect,
|
|
83
|
+
RESTWebhook
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export type ListenerRevision = {
|
|
87
|
+
driveId: string;
|
|
88
|
+
documentId: string;
|
|
89
|
+
scope: string;
|
|
90
|
+
branch: string;
|
|
91
|
+
status: UpdateStatus;
|
|
92
|
+
revision: number;
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
export type UpdateStatus = 'SUCCESS' | 'CONFLICT' | 'MISSING' | 'ERROR';
|
|
96
|
+
export type ErrorStatus = Exclude<UpdateStatus, 'SUCCESS'>;
|
|
97
|
+
|
|
98
|
+
export type OperationUpdate = {
|
|
99
|
+
timestamp: string;
|
|
100
|
+
index: number;
|
|
101
|
+
skip: number;
|
|
102
|
+
type: string;
|
|
103
|
+
input: object;
|
|
104
|
+
hash: string;
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
export type StrandUpdate = {
|
|
108
|
+
driveId: string;
|
|
109
|
+
documentId: string;
|
|
110
|
+
scope: OperationScope;
|
|
111
|
+
branch: string;
|
|
112
|
+
operations: OperationUpdate[];
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
export type SyncStatus = 'SYNCING' | UpdateStatus;
|
|
116
|
+
|
|
117
|
+
export abstract class BaseDocumentDriveServer {
|
|
118
|
+
/** Public methods **/
|
|
119
|
+
abstract getDrives(): Promise<string[]>;
|
|
120
|
+
abstract addDrive(drive: DriveInput): Promise<void>;
|
|
121
|
+
abstract addRemoteDrive(
|
|
122
|
+
url: string,
|
|
123
|
+
options: RemoteDriveOptions
|
|
124
|
+
): Promise<void>;
|
|
125
|
+
abstract deleteDrive(id: string): Promise<void>;
|
|
126
|
+
abstract getDrive(id: string): Promise<DocumentDriveDocument>;
|
|
44
127
|
|
|
45
|
-
getDocuments
|
|
46
|
-
getDocument
|
|
47
|
-
createDocument(drive: string, document: CreateDocumentInput): Promise<void>;
|
|
48
|
-
deleteDocument(drive: string, id: string): Promise<void>;
|
|
128
|
+
abstract getDocuments(drive: string): Promise<string[]>;
|
|
129
|
+
abstract getDocument(drive: string, id: string): Promise<Document>;
|
|
49
130
|
|
|
50
|
-
addOperation(
|
|
131
|
+
abstract addOperation(
|
|
51
132
|
drive: string,
|
|
52
133
|
id: string,
|
|
53
134
|
operation: Operation
|
|
54
|
-
): Promise<IOperationResult
|
|
55
|
-
addOperations(
|
|
135
|
+
): Promise<IOperationResult<Document>>;
|
|
136
|
+
abstract addOperations(
|
|
56
137
|
drive: string,
|
|
57
138
|
id: string,
|
|
58
139
|
operations: Operation[]
|
|
59
|
-
): Promise<IOperationResult
|
|
140
|
+
): Promise<IOperationResult<Document>>;
|
|
60
141
|
|
|
61
|
-
addDriveOperation(
|
|
142
|
+
abstract addDriveOperation(
|
|
62
143
|
drive: string,
|
|
63
144
|
operation: Operation<DocumentDriveAction | BaseAction>
|
|
64
145
|
): Promise<IOperationResult<DocumentDriveDocument>>;
|
|
65
|
-
addDriveOperations(
|
|
146
|
+
abstract addDriveOperations(
|
|
66
147
|
drive: string,
|
|
67
148
|
operations: Operation<DocumentDriveAction | BaseAction>[]
|
|
68
149
|
): Promise<IOperationResult<DocumentDriveDocument>>;
|
|
150
|
+
|
|
151
|
+
abstract getSyncStatus(drive: string): SyncStatus;
|
|
152
|
+
|
|
153
|
+
/** Synchronization methods */
|
|
154
|
+
abstract getSynchronizationUnits(
|
|
155
|
+
driveId: string,
|
|
156
|
+
documentId?: string[],
|
|
157
|
+
scope?: string[],
|
|
158
|
+
branch?: string[]
|
|
159
|
+
): Promise<SynchronizationUnit[]>;
|
|
160
|
+
|
|
161
|
+
abstract getSynchronizationUnit(
|
|
162
|
+
driveId: string,
|
|
163
|
+
syncId: string
|
|
164
|
+
): Promise<SynchronizationUnit>;
|
|
165
|
+
|
|
166
|
+
abstract getOperationData(
|
|
167
|
+
driveId: string,
|
|
168
|
+
syncId: string,
|
|
169
|
+
filter: {
|
|
170
|
+
since?: string;
|
|
171
|
+
fromRevision?: number;
|
|
172
|
+
}
|
|
173
|
+
): Promise<OperationUpdate[]>;
|
|
174
|
+
|
|
175
|
+
/** Internal methods **/
|
|
176
|
+
protected abstract createDocument(
|
|
177
|
+
drive: string,
|
|
178
|
+
document: CreateDocumentInput
|
|
179
|
+
): Promise<Document>;
|
|
180
|
+
protected abstract deleteDocument(drive: string, id: string): Promise<void>;
|
|
181
|
+
|
|
182
|
+
abstract getTransmitter(
|
|
183
|
+
driveId: string,
|
|
184
|
+
listenerId: string
|
|
185
|
+
): Promise<ITransmitter | undefined>;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export abstract class BaseListenerManager {
|
|
189
|
+
protected drive: BaseDocumentDriveServer;
|
|
190
|
+
protected listenerState: Map<string, Map<string, ListenerState>> =
|
|
191
|
+
new Map();
|
|
192
|
+
protected transmitters: Record<
|
|
193
|
+
DocumentDriveState['id'],
|
|
194
|
+
Record<Listener['listenerId'], ITransmitter>
|
|
195
|
+
> = {};
|
|
196
|
+
|
|
197
|
+
constructor(
|
|
198
|
+
drive: BaseDocumentDriveServer,
|
|
199
|
+
listenerState: Map<string, Map<string, ListenerState>> = new Map()
|
|
200
|
+
) {
|
|
201
|
+
this.drive = drive;
|
|
202
|
+
this.listenerState = listenerState;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
abstract init(): Promise<void>;
|
|
206
|
+
abstract addListener(listener: Listener): Promise<ITransmitter>;
|
|
207
|
+
abstract removeListener(
|
|
208
|
+
driveId: string,
|
|
209
|
+
listenerId: string
|
|
210
|
+
): Promise<boolean>;
|
|
211
|
+
abstract getTransmitter(
|
|
212
|
+
driveId: string,
|
|
213
|
+
listenerId: string
|
|
214
|
+
): Promise<ITransmitter | undefined>;
|
|
215
|
+
abstract updateSynchronizationRevision(
|
|
216
|
+
driveId: string,
|
|
217
|
+
syncId: string,
|
|
218
|
+
syncRev: number,
|
|
219
|
+
lastUpdated: string
|
|
220
|
+
): Promise<void>;
|
|
221
|
+
|
|
222
|
+
abstract updateListenerRevision(
|
|
223
|
+
listenerId: string,
|
|
224
|
+
driveId: string,
|
|
225
|
+
syncId: string,
|
|
226
|
+
listenerRev: number
|
|
227
|
+
): Promise<void>;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
export type IDocumentDriveServer = Pick<
|
|
231
|
+
BaseDocumentDriveServer,
|
|
232
|
+
keyof BaseDocumentDriveServer
|
|
233
|
+
>;
|
|
234
|
+
|
|
235
|
+
export type ListenerStatus =
|
|
236
|
+
| 'CREATED'
|
|
237
|
+
| 'PENDING'
|
|
238
|
+
| 'SUCCESS'
|
|
239
|
+
| 'MISSING'
|
|
240
|
+
| 'CONFLICT'
|
|
241
|
+
| 'ERROR';
|
|
242
|
+
|
|
243
|
+
export interface ListenerState {
|
|
244
|
+
driveId: string;
|
|
245
|
+
block: boolean;
|
|
246
|
+
pendingTimeout: string;
|
|
247
|
+
listener: Listener;
|
|
248
|
+
syncUnits: SyncronizationUnitState[];
|
|
249
|
+
listenerStatus: ListenerStatus;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
export interface SyncronizationUnitState extends SynchronizationUnit {
|
|
253
|
+
listenerRev: number;
|
|
254
|
+
syncRev: number;
|
|
69
255
|
}
|
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
|
|
|
@@ -9,9 +9,10 @@ import {
|
|
|
9
9
|
writeFileSync
|
|
10
10
|
} from 'fs';
|
|
11
11
|
import fs from 'fs/promises';
|
|
12
|
+
import stringify from 'json-stringify-deterministic';
|
|
12
13
|
import path from 'path';
|
|
13
14
|
import sanitize from 'sanitize-filename';
|
|
14
|
-
import { mergeOperations } from '..';
|
|
15
|
+
import { applyUpdatedOperations, mergeOperations } from '..';
|
|
15
16
|
import { DocumentDriveStorage, DocumentStorage, IDriveStorage } from './types';
|
|
16
17
|
|
|
17
18
|
type FSError = {
|
|
@@ -83,7 +84,6 @@ export class FilesystemStorage implements IDriveStorage {
|
|
|
83
84
|
});
|
|
84
85
|
return JSON.parse(content) as Promise<DocumentStorage>;
|
|
85
86
|
} catch (error) {
|
|
86
|
-
console.error(error);
|
|
87
87
|
throw new Error(`Document with id ${id} not found`);
|
|
88
88
|
}
|
|
89
89
|
}
|
|
@@ -91,7 +91,7 @@ export class FilesystemStorage implements IDriveStorage {
|
|
|
91
91
|
async createDocument(drive: string, id: string, document: DocumentStorage) {
|
|
92
92
|
const documentPath = this._buildDocumentPath(drive, id);
|
|
93
93
|
await ensureDir(path.dirname(documentPath));
|
|
94
|
-
await writeFileSync(documentPath,
|
|
94
|
+
await writeFileSync(documentPath, stringify(document), {
|
|
95
95
|
encoding: 'utf-8'
|
|
96
96
|
});
|
|
97
97
|
}
|
|
@@ -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/index.ts
CHANGED
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 {
|
|
@@ -30,6 +30,7 @@ export class MemoryStorage implements IDriveStorage {
|
|
|
30
30
|
if (!document) {
|
|
31
31
|
throw new Error(`Document with id ${id} not found`);
|
|
32
32
|
}
|
|
33
|
+
|
|
33
34
|
return document;
|
|
34
35
|
}
|
|
35
36
|
|
|
@@ -47,7 +48,8 @@ export class MemoryStorage implements IDriveStorage {
|
|
|
47
48
|
revision,
|
|
48
49
|
documentType,
|
|
49
50
|
created,
|
|
50
|
-
lastModified
|
|
51
|
+
lastModified,
|
|
52
|
+
clipboard
|
|
51
53
|
} = document;
|
|
52
54
|
this.documents[drive]![id] = {
|
|
53
55
|
operations,
|
|
@@ -56,7 +58,8 @@ export class MemoryStorage implements IDriveStorage {
|
|
|
56
58
|
revision,
|
|
57
59
|
documentType,
|
|
58
60
|
created,
|
|
59
|
-
lastModified
|
|
61
|
+
lastModified,
|
|
62
|
+
clipboard
|
|
60
63
|
};
|
|
61
64
|
}
|
|
62
65
|
|
|
@@ -64,7 +67,8 @@ export class MemoryStorage implements IDriveStorage {
|
|
|
64
67
|
drive: string,
|
|
65
68
|
id: string,
|
|
66
69
|
operations: Operation[],
|
|
67
|
-
header: DocumentHeader
|
|
70
|
+
header: DocumentHeader,
|
|
71
|
+
updatedOperations: Operation[] = []
|
|
68
72
|
): Promise<void> {
|
|
69
73
|
const document = await this.getDocument(drive, id);
|
|
70
74
|
if (!document) {
|
|
@@ -76,10 +80,15 @@ export class MemoryStorage implements IDriveStorage {
|
|
|
76
80
|
operations
|
|
77
81
|
);
|
|
78
82
|
|
|
83
|
+
const mergedUpdatedOperations = applyUpdatedOperations(
|
|
84
|
+
mergedOperations,
|
|
85
|
+
updatedOperations
|
|
86
|
+
);
|
|
87
|
+
|
|
79
88
|
this.documents[drive]![id] = {
|
|
80
89
|
...document,
|
|
81
90
|
...header,
|
|
82
|
-
operations:
|
|
91
|
+
operations: mergedUpdatedOperations
|
|
83
92
|
};
|
|
84
93
|
}
|
|
85
94
|
|