document-drive 1.0.0-experimental.8 → 1.0.0-websockets
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 +148 -0
- package/package.json +15 -10
- package/src/queue/base.ts +19 -34
- package/src/queue/redis.ts +15 -25
- package/src/queue/types.ts +32 -16
- package/src/server/index.ts +773 -197
- package/src/server/listener/manager.ts +60 -22
- package/src/server/listener/transmitter/index.ts +1 -0
- package/src/server/listener/transmitter/internal.ts +5 -0
- package/src/server/listener/transmitter/pull-responder.ts +11 -15
- package/src/server/listener/transmitter/subscription.ts +364 -0
- package/src/server/listener/transmitter/types.ts +15 -0
- package/src/server/types.ts +55 -6
- package/src/storage/browser.ts +69 -5
- package/src/storage/filesystem.ts +60 -2
- package/src/storage/memory.ts +67 -4
- package/src/storage/prisma.ts +316 -81
- package/src/storage/sequelize.ts +69 -7
- package/src/storage/types.ts +19 -5
- package/src/queue/manager.ts +0 -22
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';
|
|
@@ -58,6 +59,8 @@ export type SynchronizationUnit = {
|
|
|
58
59
|
revision: number;
|
|
59
60
|
};
|
|
60
61
|
|
|
62
|
+
export type SynchronizationUnitQuery = Omit<SynchronizationUnit, "revision" | "lastUpdated">;
|
|
63
|
+
|
|
61
64
|
export type Listener = {
|
|
62
65
|
driveId: string;
|
|
63
66
|
listenerId: string;
|
|
@@ -116,6 +119,7 @@ export type OperationUpdate = {
|
|
|
116
119
|
input: object;
|
|
117
120
|
hash: string;
|
|
118
121
|
context?: ActionContext;
|
|
122
|
+
id?: string;
|
|
119
123
|
};
|
|
120
124
|
|
|
121
125
|
export type StrandUpdate = {
|
|
@@ -140,7 +144,7 @@ export type PartialRecord<K extends keyof any, T> = {
|
|
|
140
144
|
|
|
141
145
|
export type RevisionsFilter = PartialRecord<OperationScope, number>;
|
|
142
146
|
|
|
143
|
-
export type GetDocumentOptions = {
|
|
147
|
+
export type GetDocumentOptions = ReducerOptions & {
|
|
144
148
|
revisions?: RevisionsFilter;
|
|
145
149
|
checkHashes?: boolean;
|
|
146
150
|
};
|
|
@@ -159,6 +163,10 @@ export abstract class BaseDocumentDriveServer {
|
|
|
159
163
|
options?: GetDocumentOptions
|
|
160
164
|
): Promise<DocumentDriveDocument>;
|
|
161
165
|
|
|
166
|
+
abstract getDriveBySlug(
|
|
167
|
+
slug: string,
|
|
168
|
+
): Promise<DocumentDriveDocument>;
|
|
169
|
+
|
|
162
170
|
abstract getDocuments(drive: string): Promise<string[]>;
|
|
163
171
|
abstract getDocument(
|
|
164
172
|
drive: string,
|
|
@@ -172,6 +180,7 @@ export abstract class BaseDocumentDriveServer {
|
|
|
172
180
|
operation: Operation,
|
|
173
181
|
forceSync?: boolean
|
|
174
182
|
): Promise<IOperationResult>;
|
|
183
|
+
|
|
175
184
|
abstract addOperations(
|
|
176
185
|
drive: string,
|
|
177
186
|
id: string,
|
|
@@ -185,6 +194,7 @@ export abstract class BaseDocumentDriveServer {
|
|
|
185
194
|
operation: Operation,
|
|
186
195
|
forceSync?: boolean
|
|
187
196
|
): Promise<IOperationResult>;
|
|
197
|
+
|
|
188
198
|
abstract queueOperations(
|
|
189
199
|
drive: string,
|
|
190
200
|
id: string,
|
|
@@ -192,6 +202,20 @@ export abstract class BaseDocumentDriveServer {
|
|
|
192
202
|
forceSync?: boolean
|
|
193
203
|
): Promise<IOperationResult>;
|
|
194
204
|
|
|
205
|
+
abstract queueAction(
|
|
206
|
+
drive: string,
|
|
207
|
+
id: string,
|
|
208
|
+
action: Action,
|
|
209
|
+
forceSync?: boolean
|
|
210
|
+
): Promise<IOperationResult>;
|
|
211
|
+
|
|
212
|
+
abstract queueActions(
|
|
213
|
+
drive: string,
|
|
214
|
+
id: string,
|
|
215
|
+
actions: Action[],
|
|
216
|
+
forceSync?: boolean
|
|
217
|
+
): Promise<IOperationResult>;
|
|
218
|
+
|
|
195
219
|
abstract addDriveOperation(
|
|
196
220
|
drive: string,
|
|
197
221
|
operation: Operation<DocumentDriveAction | BaseAction>,
|
|
@@ -214,24 +238,40 @@ export abstract class BaseDocumentDriveServer {
|
|
|
214
238
|
forceSync?: boolean
|
|
215
239
|
): Promise<IOperationResult<DocumentDriveDocument>>;
|
|
216
240
|
|
|
241
|
+
abstract queueDriveAction(
|
|
242
|
+
drive: string,
|
|
243
|
+
action: DocumentDriveAction | BaseAction,
|
|
244
|
+
forceSync?: boolean
|
|
245
|
+
): Promise<IOperationResult<DocumentDriveDocument>>;
|
|
246
|
+
|
|
247
|
+
abstract queueDriveActions(
|
|
248
|
+
drive: string,
|
|
249
|
+
actions: Array<DocumentDriveAction | BaseAction>,
|
|
250
|
+
forceSync?: boolean
|
|
251
|
+
): Promise<IOperationResult<DocumentDriveDocument>>;
|
|
252
|
+
|
|
217
253
|
abstract addAction(
|
|
218
254
|
drive: string,
|
|
219
255
|
id: string,
|
|
220
|
-
action: Action
|
|
256
|
+
action: Action,
|
|
257
|
+
forceSync?: boolean
|
|
221
258
|
): Promise<IOperationResult>;
|
|
222
259
|
abstract addActions(
|
|
223
260
|
drive: string,
|
|
224
261
|
id: string,
|
|
225
|
-
actions: Action[]
|
|
262
|
+
actions: Action[],
|
|
263
|
+
forceSync?: boolean
|
|
226
264
|
): Promise<IOperationResult>;
|
|
227
265
|
|
|
228
266
|
abstract addDriveAction(
|
|
229
267
|
drive: string,
|
|
230
|
-
action: DocumentDriveAction | BaseAction
|
|
268
|
+
action: DocumentDriveAction | BaseAction,
|
|
269
|
+
forceSync?: boolean
|
|
231
270
|
): Promise<IOperationResult<DocumentDriveDocument>>;
|
|
232
271
|
abstract addDriveActions(
|
|
233
272
|
drive: string,
|
|
234
|
-
actions: (DocumentDriveAction | BaseAction)[]
|
|
273
|
+
actions: (DocumentDriveAction | BaseAction)[],
|
|
274
|
+
forceSync?: boolean
|
|
235
275
|
): Promise<IOperationResult<DocumentDriveDocument>>;
|
|
236
276
|
|
|
237
277
|
abstract getSyncStatus(drive: string): SyncStatus;
|
|
@@ -248,7 +288,15 @@ export abstract class BaseDocumentDriveServer {
|
|
|
248
288
|
abstract getSynchronizationUnit(
|
|
249
289
|
driveId: string,
|
|
250
290
|
syncId: string
|
|
251
|
-
): Promise<SynchronizationUnit>;
|
|
291
|
+
): Promise<SynchronizationUnit | undefined>;
|
|
292
|
+
|
|
293
|
+
abstract getSynchronizationUnitsIds(
|
|
294
|
+
driveId: string,
|
|
295
|
+
documentId?: string[],
|
|
296
|
+
scope?: string[],
|
|
297
|
+
branch?: string[],
|
|
298
|
+
documentType?: string[]
|
|
299
|
+
): Promise<SynchronizationUnitQuery[]>;
|
|
252
300
|
|
|
253
301
|
abstract getOperationData(
|
|
254
302
|
driveId: string,
|
|
@@ -303,6 +351,7 @@ export abstract class BaseListenerManager {
|
|
|
303
351
|
}
|
|
304
352
|
|
|
305
353
|
abstract initDrive(drive: DocumentDriveDocument): Promise<void>;
|
|
354
|
+
abstract removeDrive(driveId: DocumentDriveState["id"]): Promise<void>;
|
|
306
355
|
|
|
307
356
|
abstract addListener(listener: Listener): Promise<ITransmitter>;
|
|
308
357
|
abstract removeListener(
|
package/src/storage/browser.ts
CHANGED
|
@@ -3,10 +3,15 @@ import {
|
|
|
3
3
|
BaseAction,
|
|
4
4
|
Document,
|
|
5
5
|
DocumentHeader,
|
|
6
|
-
Operation
|
|
6
|
+
Operation,
|
|
7
|
+
OperationScope
|
|
7
8
|
} from 'document-model/document';
|
|
8
|
-
import { mergeOperations } from '..';
|
|
9
|
-
import {
|
|
9
|
+
import { mergeOperations, type SynchronizationUnitQuery } from '..';
|
|
10
|
+
import {
|
|
11
|
+
DocumentDriveStorage,
|
|
12
|
+
DocumentStorage,
|
|
13
|
+
IDriveStorage,
|
|
14
|
+
} from './types';
|
|
10
15
|
|
|
11
16
|
export class BrowserStorage implements IDriveStorage {
|
|
12
17
|
private db: Promise<LocalForage>;
|
|
@@ -18,7 +23,9 @@ export class BrowserStorage implements IDriveStorage {
|
|
|
18
23
|
constructor(namespace?: string) {
|
|
19
24
|
this.db = import('localforage').then(localForage =>
|
|
20
25
|
localForage.default.createInstance({
|
|
21
|
-
name: namespace
|
|
26
|
+
name: namespace
|
|
27
|
+
? `${namespace}:${BrowserStorage.DBName}`
|
|
28
|
+
: BrowserStorage.DBName
|
|
22
29
|
})
|
|
23
30
|
);
|
|
24
31
|
}
|
|
@@ -68,7 +75,7 @@ export class BrowserStorage implements IDriveStorage {
|
|
|
68
75
|
drive: string,
|
|
69
76
|
id: string,
|
|
70
77
|
operations: Operation[],
|
|
71
|
-
header: DocumentHeader
|
|
78
|
+
header: DocumentHeader
|
|
72
79
|
): Promise<void> {
|
|
73
80
|
const document = await this.getDocument(drive, id);
|
|
74
81
|
if (!document) {
|
|
@@ -154,4 +161,61 @@ export class BrowserStorage implements IDriveStorage {
|
|
|
154
161
|
});
|
|
155
162
|
return;
|
|
156
163
|
}
|
|
164
|
+
|
|
165
|
+
async getSynchronizationUnitsRevision(
|
|
166
|
+
units: SynchronizationUnitQuery[]
|
|
167
|
+
): Promise<
|
|
168
|
+
{
|
|
169
|
+
driveId: string;
|
|
170
|
+
documentId: string;
|
|
171
|
+
scope: string;
|
|
172
|
+
branch: string;
|
|
173
|
+
lastUpdated: string;
|
|
174
|
+
revision: number;
|
|
175
|
+
}[]
|
|
176
|
+
> {
|
|
177
|
+
const results = await Promise.allSettled(
|
|
178
|
+
units.map(async unit => {
|
|
179
|
+
try {
|
|
180
|
+
const document = await (unit.documentId
|
|
181
|
+
? this.getDocument(unit.driveId, unit.documentId)
|
|
182
|
+
: this.getDrive(unit.driveId));
|
|
183
|
+
if (!document) {
|
|
184
|
+
return undefined;
|
|
185
|
+
}
|
|
186
|
+
const operation =
|
|
187
|
+
document.operations[unit.scope as OperationScope]?.at(
|
|
188
|
+
-1
|
|
189
|
+
);
|
|
190
|
+
if (operation) {
|
|
191
|
+
return {
|
|
192
|
+
driveId: unit.driveId,
|
|
193
|
+
documentId: unit.documentId,
|
|
194
|
+
scope: unit.scope,
|
|
195
|
+
branch: unit.branch,
|
|
196
|
+
lastUpdated: operation.timestamp,
|
|
197
|
+
revision: operation.index
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
} catch {
|
|
201
|
+
return undefined;
|
|
202
|
+
}
|
|
203
|
+
})
|
|
204
|
+
);
|
|
205
|
+
return results.reduce<
|
|
206
|
+
{
|
|
207
|
+
driveId: string;
|
|
208
|
+
documentId: string;
|
|
209
|
+
scope: string;
|
|
210
|
+
branch: string;
|
|
211
|
+
lastUpdated: string;
|
|
212
|
+
revision: number;
|
|
213
|
+
}[]
|
|
214
|
+
>((acc, curr) => {
|
|
215
|
+
if (curr.status === 'fulfilled' && curr.value !== undefined) {
|
|
216
|
+
acc.push(curr.value);
|
|
217
|
+
}
|
|
218
|
+
return acc;
|
|
219
|
+
}, []);
|
|
220
|
+
}
|
|
157
221
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { DocumentDriveAction } from 'document-model-libs/document-drive';
|
|
2
|
-
import { BaseAction, DocumentHeader, Operation } from 'document-model/document';
|
|
2
|
+
import { BaseAction, DocumentHeader, Operation, OperationScope } from 'document-model/document';
|
|
3
3
|
import type { Dirent } from 'fs';
|
|
4
4
|
import {
|
|
5
5
|
existsSync,
|
|
@@ -12,7 +12,8 @@ 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 {
|
|
15
|
+
import type { SynchronizationUnitQuery } from '../server/types';
|
|
16
|
+
import { mergeOperations } from '../utils';
|
|
16
17
|
import { DocumentDriveStorage, DocumentStorage, IDriveStorage } from './types';
|
|
17
18
|
|
|
18
19
|
type FSError = {
|
|
@@ -235,4 +236,61 @@ export class FilesystemStorage implements IDriveStorage {
|
|
|
235
236
|
operations: mergedOperations
|
|
236
237
|
});
|
|
237
238
|
}
|
|
239
|
+
|
|
240
|
+
async getSynchronizationUnitsRevision(
|
|
241
|
+
units: SynchronizationUnitQuery[]
|
|
242
|
+
): Promise<
|
|
243
|
+
{
|
|
244
|
+
driveId: string;
|
|
245
|
+
documentId: string;
|
|
246
|
+
scope: string;
|
|
247
|
+
branch: string;
|
|
248
|
+
lastUpdated: string;
|
|
249
|
+
revision: number;
|
|
250
|
+
}[]
|
|
251
|
+
> {
|
|
252
|
+
const results = await Promise.allSettled(
|
|
253
|
+
units.map(async unit => {
|
|
254
|
+
try {
|
|
255
|
+
const document = await (unit.documentId
|
|
256
|
+
? this.getDocument(unit.driveId, unit.documentId)
|
|
257
|
+
: this.getDrive(unit.driveId));
|
|
258
|
+
if (!document) {
|
|
259
|
+
return undefined;
|
|
260
|
+
}
|
|
261
|
+
const operation =
|
|
262
|
+
document.operations[unit.scope as OperationScope]?.at(
|
|
263
|
+
-1
|
|
264
|
+
);
|
|
265
|
+
if (operation) {
|
|
266
|
+
return {
|
|
267
|
+
driveId: unit.driveId,
|
|
268
|
+
documentId: unit.documentId,
|
|
269
|
+
scope: unit.scope,
|
|
270
|
+
branch: unit.branch,
|
|
271
|
+
lastUpdated: operation.timestamp,
|
|
272
|
+
revision: operation.index
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
} catch {
|
|
276
|
+
return undefined;
|
|
277
|
+
}
|
|
278
|
+
})
|
|
279
|
+
);
|
|
280
|
+
return results.reduce<
|
|
281
|
+
{
|
|
282
|
+
driveId: string;
|
|
283
|
+
documentId: string;
|
|
284
|
+
scope: string;
|
|
285
|
+
branch: string;
|
|
286
|
+
lastUpdated: string;
|
|
287
|
+
revision: number;
|
|
288
|
+
}[]
|
|
289
|
+
>((acc, curr) => {
|
|
290
|
+
if (curr.status === 'fulfilled' && curr.value !== undefined) {
|
|
291
|
+
acc.push(curr.value);
|
|
292
|
+
}
|
|
293
|
+
return acc;
|
|
294
|
+
}, []);
|
|
295
|
+
}
|
|
238
296
|
}
|
package/src/storage/memory.ts
CHANGED
|
@@ -3,10 +3,16 @@ import {
|
|
|
3
3
|
BaseAction,
|
|
4
4
|
Document,
|
|
5
5
|
DocumentHeader,
|
|
6
|
-
Operation
|
|
6
|
+
Operation,
|
|
7
|
+
OperationScope
|
|
7
8
|
} from 'document-model/document';
|
|
8
|
-
import {
|
|
9
|
-
|
|
9
|
+
import {
|
|
10
|
+
DocumentDriveStorage,
|
|
11
|
+
DocumentStorage,
|
|
12
|
+
IDriveStorage,
|
|
13
|
+
} from './types';
|
|
14
|
+
import type { SynchronizationUnitQuery } from '../server/types';
|
|
15
|
+
import { mergeOperations } from '../utils';
|
|
10
16
|
|
|
11
17
|
export class MemoryStorage implements IDriveStorage {
|
|
12
18
|
private documents: Record<string, Record<string, DocumentStorage>>;
|
|
@@ -19,7 +25,7 @@ export class MemoryStorage implements IDriveStorage {
|
|
|
19
25
|
}
|
|
20
26
|
|
|
21
27
|
checkDocumentExists(drive: string, id: string): Promise<boolean> {
|
|
22
|
-
return Promise.resolve(this.documents[drive]?.[id] !== undefined)
|
|
28
|
+
return Promise.resolve(this.documents[drive]?.[id] !== undefined);
|
|
23
29
|
}
|
|
24
30
|
|
|
25
31
|
async getDocuments(drive: string) {
|
|
@@ -153,4 +159,61 @@ export class MemoryStorage implements IDriveStorage {
|
|
|
153
159
|
delete this.documents[id];
|
|
154
160
|
delete this.drives[id];
|
|
155
161
|
}
|
|
162
|
+
|
|
163
|
+
async getSynchronizationUnitsRevision(
|
|
164
|
+
units: SynchronizationUnitQuery[]
|
|
165
|
+
): Promise<
|
|
166
|
+
{
|
|
167
|
+
driveId: string;
|
|
168
|
+
documentId: string;
|
|
169
|
+
scope: string;
|
|
170
|
+
branch: string;
|
|
171
|
+
lastUpdated: string;
|
|
172
|
+
revision: number;
|
|
173
|
+
}[]
|
|
174
|
+
> {
|
|
175
|
+
const results = await Promise.allSettled(
|
|
176
|
+
units.map(async unit => {
|
|
177
|
+
try {
|
|
178
|
+
const document = await (unit.documentId
|
|
179
|
+
? this.getDocument(unit.driveId, unit.documentId)
|
|
180
|
+
: this.getDrive(unit.driveId));
|
|
181
|
+
if (!document) {
|
|
182
|
+
return undefined;
|
|
183
|
+
}
|
|
184
|
+
const operation =
|
|
185
|
+
document.operations[unit.scope as OperationScope]?.at(
|
|
186
|
+
-1
|
|
187
|
+
);
|
|
188
|
+
if (operation) {
|
|
189
|
+
return {
|
|
190
|
+
driveId: unit.driveId,
|
|
191
|
+
documentId: unit.documentId,
|
|
192
|
+
scope: unit.scope,
|
|
193
|
+
branch: unit.branch,
|
|
194
|
+
lastUpdated: operation.timestamp,
|
|
195
|
+
revision: operation.index
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
} catch {
|
|
199
|
+
return undefined;
|
|
200
|
+
}
|
|
201
|
+
})
|
|
202
|
+
);
|
|
203
|
+
return results.reduce<
|
|
204
|
+
{
|
|
205
|
+
driveId: string;
|
|
206
|
+
documentId: string;
|
|
207
|
+
scope: string;
|
|
208
|
+
branch: string;
|
|
209
|
+
lastUpdated: string;
|
|
210
|
+
revision: number;
|
|
211
|
+
}[]
|
|
212
|
+
>((acc, curr) => {
|
|
213
|
+
if (curr.status === 'fulfilled' && curr.value !== undefined) {
|
|
214
|
+
acc.push(curr.value);
|
|
215
|
+
}
|
|
216
|
+
return acc;
|
|
217
|
+
}, []);
|
|
218
|
+
}
|
|
156
219
|
}
|