document-drive 1.0.0-websockets → 1.0.0

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.
Files changed (43) hide show
  1. package/README.md +1 -0
  2. package/package.json +74 -88
  3. package/src/cache/index.ts +2 -2
  4. package/src/cache/memory.ts +22 -13
  5. package/src/cache/redis.ts +43 -16
  6. package/src/cache/types.ts +4 -4
  7. package/src/index.ts +6 -3
  8. package/src/queue/base.ts +276 -214
  9. package/src/queue/index.ts +2 -2
  10. package/src/queue/redis.ts +138 -127
  11. package/src/queue/types.ts +44 -38
  12. package/src/read-mode/errors.ts +19 -0
  13. package/src/read-mode/index.ts +125 -0
  14. package/src/read-mode/service.ts +207 -0
  15. package/src/read-mode/types.ts +108 -0
  16. package/src/server/error.ts +61 -26
  17. package/src/server/index.ts +2160 -1785
  18. package/src/server/listener/index.ts +2 -2
  19. package/src/server/listener/manager.ts +475 -437
  20. package/src/server/listener/transmitter/index.ts +4 -5
  21. package/src/server/listener/transmitter/internal.ts +77 -79
  22. package/src/server/listener/transmitter/pull-responder.ts +363 -329
  23. package/src/server/listener/transmitter/switchboard-push.ts +72 -55
  24. package/src/server/listener/transmitter/types.ts +19 -25
  25. package/src/server/types.ts +536 -349
  26. package/src/server/utils.ts +26 -27
  27. package/src/storage/base.ts +81 -0
  28. package/src/storage/browser.ts +233 -216
  29. package/src/storage/filesystem.ts +257 -256
  30. package/src/storage/index.ts +2 -1
  31. package/src/storage/memory.ts +206 -214
  32. package/src/storage/prisma.ts +575 -568
  33. package/src/storage/sequelize.ts +460 -471
  34. package/src/storage/types.ts +83 -67
  35. package/src/utils/default-drives-manager.ts +341 -0
  36. package/src/utils/document-helpers.ts +19 -18
  37. package/src/utils/graphql.ts +288 -34
  38. package/src/utils/index.ts +61 -59
  39. package/src/utils/logger.ts +39 -37
  40. package/src/utils/migrations.ts +58 -0
  41. package/src/utils/run-asap.ts +156 -0
  42. package/CHANGELOG.md +0 -818
  43. package/src/server/listener/transmitter/subscription.ts +0 -364
@@ -1,420 +1,607 @@
1
1
  import type {
2
- DocumentDriveAction,
3
- DocumentDriveDocument,
4
- DocumentDriveLocalState,
5
- DocumentDriveState,
6
- ListenerCallInfo,
7
- ListenerFilter
8
- } from 'document-model-libs/document-drive';
2
+ DocumentDriveAction,
3
+ DocumentDriveDocument,
4
+ DocumentDriveLocalState,
5
+ DocumentDriveState,
6
+ ListenerCallInfo,
7
+ ListenerFilter,
8
+ Trigger,
9
+ } from "document-model-libs/document-drive";
9
10
  import type {
10
- Action,
11
- ActionContext,
12
- BaseAction,
13
- CreateChildDocumentInput,
14
- Document,
15
- Operation,
16
- OperationScope,
17
- ReducerOptions,
18
- Signal,
19
- State
20
- } from 'document-model/document';
21
- import { Unsubscribe } from 'nanoevents';
22
- import { OperationError } from './error';
23
- import { ITransmitter } from './listener/transmitter/types';
11
+ Action,
12
+ ActionContext,
13
+ BaseAction,
14
+ CreateChildDocumentInput,
15
+ Document,
16
+ DocumentModel,
17
+ Operation,
18
+ OperationScope,
19
+ ReducerOptions,
20
+ Signal,
21
+ State,
22
+ } from "document-model/document";
23
+ import { Unsubscribe } from "nanoevents";
24
+ import { BaseDocumentDriveServer } from ".";
25
+ import { IReadModeDriveServer } from "../read-mode/types";
26
+ import { RunAsap } from "../utils";
27
+ import { IDefaultDrivesManager } from "../utils/default-drives-manager";
28
+ import { DriveInfo } from "../utils/graphql";
29
+ import { OperationError, SynchronizationUnitNotFoundError } from "./error";
30
+ import {
31
+ ITransmitter,
32
+ PullResponderTrigger,
33
+ StrandUpdateSource,
34
+ } from "./listener/transmitter/types";
35
+
36
+ export type Constructor<T = object> = new (...args: any[]) => T;
37
+
38
+ export type DocumentDriveServerConstructor =
39
+ Constructor<BaseDocumentDriveServer>;
40
+
41
+ // Mixin type that returns a type extending both the base class and the interface
42
+ export type Mixin<T extends Constructor, I> = T &
43
+ Constructor<InstanceType<T> & I>;
44
+
45
+ export type DocumentDriveServerMixin<I> = Mixin<
46
+ typeof BaseDocumentDriveServer,
47
+ I
48
+ >;
24
49
 
25
50
  export type DriveInput = State<
26
- Omit<DocumentDriveState, '__typename' | 'id' | 'nodes'> & { id?: string },
27
- DocumentDriveLocalState
51
+ Omit<DocumentDriveState, "__typename" | "id" | "nodes"> & { id?: string },
52
+ DocumentDriveLocalState
28
53
  >;
29
54
 
55
+ export type RemoteDriveAccessLevel = "READ" | "WRITE";
56
+
30
57
  export type RemoteDriveOptions = DocumentDriveLocalState & {
31
- // TODO make local state optional
32
- pullFilter?: ListenerFilter;
33
- pullInterval?: number;
58
+ // TODO make local state optional
59
+ pullFilter?: ListenerFilter;
60
+ pullInterval?: number;
61
+ expectedDriveInfo?: DriveInfo;
62
+ accessLevel?: RemoteDriveAccessLevel;
34
63
  };
35
64
 
36
65
  export type CreateDocumentInput = CreateChildDocumentInput;
37
66
 
38
67
  export type SignalResult = {
39
- signal: Signal;
40
- result: unknown; // infer from return types on document-model
68
+ signal: Signal;
69
+ result: unknown; // infer from return types on document-model
41
70
  };
42
71
 
43
72
  export type IOperationResult<T extends Document = Document> = {
44
- status: UpdateStatus;
45
- error?: OperationError;
46
- operations: Operation[];
47
- document: T | undefined;
48
- signals: SignalResult[];
73
+ status: UpdateStatus;
74
+ error?: OperationError;
75
+ operations: Operation[];
76
+ document: T | undefined;
77
+ signals: SignalResult[];
49
78
  };
50
79
 
51
80
  export type SynchronizationUnit = {
52
- syncId: string;
53
- driveId: string;
54
- documentId: string;
55
- documentType: string;
56
- scope: string;
57
- branch: string;
58
- lastUpdated: string;
59
- revision: number;
81
+ syncId: string;
82
+ driveId: string;
83
+ documentId: string;
84
+ documentType: string;
85
+ scope: string;
86
+ branch: string;
87
+ lastUpdated: string;
88
+ revision: number;
60
89
  };
61
90
 
62
- export type SynchronizationUnitQuery = Omit<SynchronizationUnit, "revision" | "lastUpdated">;
91
+ export type SynchronizationUnitQuery = Omit<
92
+ SynchronizationUnit,
93
+ "revision" | "lastUpdated"
94
+ >;
63
95
 
64
96
  export type Listener = {
65
- driveId: string;
66
- listenerId: string;
67
- label?: string;
68
- block: boolean;
69
- system: boolean;
70
- filter: ListenerFilter;
71
- callInfo?: ListenerCallInfo;
97
+ driveId: string;
98
+ listenerId: string;
99
+ label?: string;
100
+ block: boolean;
101
+ system: boolean;
102
+ filter: ListenerFilter;
103
+ callInfo?: ListenerCallInfo;
72
104
  };
73
105
 
74
106
  export type CreateListenerInput = {
75
- driveId: string;
76
- label?: string;
77
- block: boolean;
78
- system: boolean;
79
- filter: ListenerFilter;
80
- callInfo?: ListenerCallInfo;
107
+ driveId: string;
108
+ label?: string;
109
+ block: boolean;
110
+ system: boolean;
111
+ filter: ListenerFilter;
112
+ callInfo?: ListenerCallInfo;
81
113
  };
82
114
 
83
115
  export enum TransmitterType {
84
- Internal,
85
- SwitchboardPush,
86
- PullResponder,
87
- SecureConnect,
88
- MatrixConnect,
89
- RESTWebhook
116
+ Internal,
117
+ SwitchboardPush,
118
+ PullResponder,
119
+ SecureConnect,
120
+ MatrixConnect,
121
+ RESTWebhook,
90
122
  }
91
123
 
92
124
  export type ListenerRevision = {
93
- driveId: string;
94
- documentId: string;
95
- scope: string;
96
- branch: string;
97
- status: UpdateStatus;
98
- revision: number;
99
- error?: string;
125
+ driveId: string;
126
+ documentId: string;
127
+ scope: string;
128
+ branch: string;
129
+ status: UpdateStatus;
130
+ revision: number;
131
+ error?: string;
100
132
  };
101
133
 
102
- export type ListenerRevisionWithError = Omit<ListenerRevision, 'error'> & {
103
- error?: Error;
134
+ export type ListenerRevisionWithError = Omit<ListenerRevision, "error"> & {
135
+ error?: Error;
104
136
  };
105
137
 
106
138
  export type ListenerUpdate = {
107
- listenerId: string;
108
- listenerRevisions: ListenerRevision[];
139
+ listenerId: string;
140
+ listenerRevisions: ListenerRevision[];
109
141
  };
110
142
 
111
- export type UpdateStatus = 'SUCCESS' | 'CONFLICT' | 'MISSING' | 'ERROR';
112
- export type ErrorStatus = Exclude<UpdateStatus, 'SUCCESS'>;
143
+ export type UpdateStatus = "SUCCESS" | "CONFLICT" | "MISSING" | "ERROR";
144
+ export type ErrorStatus = Exclude<UpdateStatus, "SUCCESS">;
113
145
 
114
146
  export type OperationUpdate = {
115
- timestamp: string;
116
- index: number;
117
- skip: number;
118
- type: string;
119
- input: object;
120
- hash: string;
121
- context?: ActionContext;
122
- id?: string;
147
+ timestamp: string;
148
+ index: number;
149
+ skip: number;
150
+ type: string;
151
+ input: object;
152
+ hash: string;
153
+ context?: ActionContext;
154
+ id?: string;
123
155
  };
124
156
 
125
157
  export type StrandUpdate = {
126
- driveId: string;
127
- documentId: string;
128
- scope: OperationScope;
129
- branch: string;
130
- operations: OperationUpdate[];
158
+ driveId: string;
159
+ documentId: string;
160
+ scope: OperationScope;
161
+ branch: string;
162
+ operations: OperationUpdate[];
131
163
  };
132
164
 
133
- export type SyncStatus = 'SYNCING' | UpdateStatus;
165
+ export type SyncStatus = "INITIAL_SYNC" | "SYNCING" | UpdateStatus;
166
+
167
+ export type PullSyncStatus = SyncStatus;
168
+ export type PushSyncStatus = SyncStatus;
169
+
170
+ export type SyncUnitStatusObject = {
171
+ push?: PushSyncStatus;
172
+ pull?: PullSyncStatus;
173
+ };
174
+
175
+ export type AddRemoteDriveStatus =
176
+ | "SUCCESS"
177
+ | "ERROR"
178
+ | "PENDING"
179
+ | "ADDING"
180
+ | "ALREADY_ADDED";
134
181
 
135
182
  export interface DriveEvents {
136
- syncStatus: (driveId: string, status: SyncStatus, error?: Error) => void;
137
- strandUpdate: (update: StrandUpdate) => void;
183
+ syncStatus: (
184
+ driveId: string,
185
+ status: SyncStatus,
186
+ error?: Error,
187
+ syncUnitStatus?: SyncUnitStatusObject,
188
+ ) => void;
189
+ defaultRemoteDrive: (
190
+ status: AddRemoteDriveStatus,
191
+ defaultDrives: Map<string, DefaultRemoteDriveInfo>,
192
+ driveInput: DefaultRemoteDriveInput,
193
+ driveId?: string,
194
+ driveName?: string,
195
+ error?: Error,
196
+ ) => void;
197
+ strandUpdate: (update: StrandUpdate) => void;
198
+ clientStrandsError: (
199
+ driveId: string,
200
+ trigger: Trigger,
201
+ status: number,
202
+ errorMessage: string,
203
+ ) => void;
204
+ documentModels: (documentModels: DocumentModel[]) => void;
138
205
  }
139
206
 
140
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
141
207
  export type PartialRecord<K extends keyof any, T> = {
142
- [P in K]?: T;
208
+ [P in K]?: T;
143
209
  };
144
210
 
145
211
  export type RevisionsFilter = PartialRecord<OperationScope, number>;
146
212
 
147
213
  export type GetDocumentOptions = ReducerOptions & {
148
- revisions?: RevisionsFilter;
149
- checkHashes?: boolean;
214
+ revisions?: RevisionsFilter;
215
+ checkHashes?: boolean;
150
216
  };
151
217
 
152
- export abstract class BaseDocumentDriveServer {
153
- /** Public methods **/
154
- abstract getDrives(): Promise<string[]>;
155
- abstract addDrive(drive: DriveInput): Promise<DocumentDriveDocument>;
156
- abstract addRemoteDrive(
157
- url: string,
158
- options: RemoteDriveOptions
159
- ): Promise<DocumentDriveDocument>;
160
- abstract deleteDrive(id: string): Promise<void>;
161
- abstract getDrive(
162
- id: string,
163
- options?: GetDocumentOptions
164
- ): Promise<DocumentDriveDocument>;
165
-
166
- abstract getDriveBySlug(
167
- slug: string,
168
- ): Promise<DocumentDriveDocument>;
169
-
170
- abstract getDocuments(drive: string): Promise<string[]>;
171
- abstract getDocument(
172
- drive: string,
173
- id: string,
174
- options?: GetDocumentOptions
175
- ): Promise<Document>;
176
-
177
- abstract addOperation(
178
- drive: string,
179
- id: string,
180
- operation: Operation,
181
- forceSync?: boolean
182
- ): Promise<IOperationResult>;
183
-
184
- abstract addOperations(
185
- drive: string,
186
- id: string,
187
- operations: Operation[],
188
- forceSync?: boolean
189
- ): Promise<IOperationResult>;
190
-
191
- abstract queueOperation(
192
- drive: string,
193
- id: string,
194
- operation: Operation,
195
- forceSync?: boolean
196
- ): Promise<IOperationResult>;
197
-
198
- abstract queueOperations(
199
- drive: string,
200
- id: string,
201
- operations: Operation[],
202
- forceSync?: boolean
203
- ): Promise<IOperationResult>;
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
-
219
- abstract addDriveOperation(
220
- drive: string,
221
- operation: Operation<DocumentDriveAction | BaseAction>,
222
- forceSync?: boolean
223
- ): Promise<IOperationResult<DocumentDriveDocument>>;
224
- abstract addDriveOperations(
225
- drive: string,
226
- operations: Operation<DocumentDriveAction | BaseAction>[],
227
- forceSync?: boolean
228
- ): Promise<IOperationResult<DocumentDriveDocument>>;
229
-
230
- abstract queueDriveOperation(
231
- drive: string,
232
- operation: Operation<DocumentDriveAction | BaseAction>,
233
- forceSync?: boolean
234
- ): Promise<IOperationResult<DocumentDriveDocument>>;
235
- abstract queueDriveOperations(
236
- drive: string,
237
- operations: Operation<DocumentDriveAction | BaseAction>[],
238
- forceSync?: boolean
239
- ): Promise<IOperationResult<DocumentDriveDocument>>;
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
-
253
- abstract addAction(
254
- drive: string,
255
- id: string,
256
- action: Action,
257
- forceSync?: boolean
258
- ): Promise<IOperationResult>;
259
- abstract addActions(
260
- drive: string,
261
- id: string,
262
- actions: Action[],
263
- forceSync?: boolean
264
- ): Promise<IOperationResult>;
265
-
266
- abstract addDriveAction(
267
- drive: string,
268
- action: DocumentDriveAction | BaseAction,
269
- forceSync?: boolean
270
- ): Promise<IOperationResult<DocumentDriveDocument>>;
271
- abstract addDriveActions(
272
- drive: string,
273
- actions: (DocumentDriveAction | BaseAction)[],
274
- forceSync?: boolean
275
- ): Promise<IOperationResult<DocumentDriveDocument>>;
276
-
277
- abstract getSyncStatus(drive: string): SyncStatus;
278
-
279
- /** Synchronization methods */
280
- abstract getSynchronizationUnits(
281
- driveId: string,
282
- documentId?: string[],
283
- scope?: string[],
284
- branch?: string[],
285
- documentType?: string[]
286
- ): Promise<SynchronizationUnit[]>;
287
-
288
- abstract getSynchronizationUnit(
289
- driveId: string,
290
- syncId: string
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[]>;
300
-
301
- abstract getOperationData(
302
- driveId: string,
303
- syncId: string,
304
- filter: {
305
- since?: string;
306
- fromRevision?: number;
307
- }
308
- ): Promise<OperationUpdate[]>;
309
-
310
- /** Internal methods **/
311
- protected abstract createDocument(
312
- drive: string,
313
- document: CreateDocumentInput
314
- ): Promise<Document>;
315
- protected abstract deleteDocument(drive: string, id: string): Promise<void>;
316
-
317
- /** Event methods **/
318
- protected abstract emit<K extends keyof DriveEvents>(
319
- this: this,
320
- event: K,
321
- ...args: Parameters<DriveEvents[K]>
322
- ): void;
323
- abstract on<K extends keyof DriveEvents>(
324
- this: this,
325
- event: K,
326
- cb: DriveEvents[K]
327
- ): Unsubscribe;
328
-
329
- abstract getTransmitter(
330
- driveId: string,
331
- listenerId: string
332
- ): Promise<ITransmitter | undefined>;
333
-
334
- abstract clearStorage(): Promise<void>;
335
- }
218
+ export type AddOperationOptions = {
219
+ forceSync?: boolean;
220
+ source: StrandUpdateSource;
221
+ };
336
222
 
337
- export abstract class BaseListenerManager {
338
- protected drive: BaseDocumentDriveServer;
339
- protected listenerState = new Map<string, Map<string, ListenerState>>();
340
- protected transmitters: Record<
341
- DocumentDriveState['id'],
342
- Record<Listener['listenerId'], ITransmitter>
343
- > = {};
344
-
345
- constructor(
346
- drive: BaseDocumentDriveServer,
347
- listenerState = new Map<string, Map<string, ListenerState>>()
348
- ) {
349
- this.drive = drive;
350
- this.listenerState = listenerState;
223
+ export type DefaultRemoteDriveInput = {
224
+ url: string;
225
+ options: RemoteDriveOptions;
226
+ };
227
+
228
+ export type DefaultRemoteDriveInfo = DefaultRemoteDriveInput & {
229
+ status: AddRemoteDriveStatus;
230
+ metadata?: DriveInfo;
231
+ };
232
+
233
+ export type RemoveDriveStrategy = "remove" | "detach";
234
+
235
+ /**
236
+ * Options for removing old remote drives.
237
+ *
238
+ * Allows specifying different strategies for handling old remote drives:
239
+ *
240
+ * - `remove-all`: Remove all remote drives.
241
+ * - `preserve-all`: Preserve all remote drives (this is the default behavior).
242
+ * - `remove-by-id`: Remove the remote drives specified by their IDs.
243
+ * - `remove-by-url`: Remove the remote drives specified by their URLs.
244
+ * - `preserve-by-id`: Preserve remote drives by their IDs and remove the rest.
245
+ * - `preserve-by-url`: Preserve remote drives by their URLs and remove the rest.
246
+ * - `detach-by-id`: Detach remote drives by their IDs (changes the remote drive to a local drive).
247
+ * - `detach-by-url`: Detach remote drives by their URLs (changes the remote drive to a local drive).
248
+ * - `preserve-by-id-and-detach`: Preserve the remote drives specified by their IDs and detach the rest.
249
+ * - `preserve-by-url-and-detach`: Preserve the remote drives specified by their URLs and detach the rest.
250
+ *
251
+ * Each strategy is represented by an object with a `strategy` property and,
252
+ * depending on the strategy, additional properties such as `ids` or `urls`.
253
+ */
254
+ export type RemoveOldRemoteDrivesOption =
255
+ | {
256
+ strategy: "remove-all";
257
+ }
258
+ | {
259
+ strategy: "preserve-all";
260
+ }
261
+ | {
262
+ strategy: "remove-by-id";
263
+ ids: string[];
264
+ }
265
+ | {
266
+ strategy: "remove-by-url";
267
+ urls: string[];
351
268
  }
269
+ | {
270
+ strategy: "preserve-by-id";
271
+ ids: string[];
272
+ }
273
+ | {
274
+ strategy: "preserve-by-url";
275
+ urls: string[];
276
+ }
277
+ | {
278
+ strategy: "detach-by-id";
279
+ ids: string[];
280
+ }
281
+ | {
282
+ strategy: "detach-by-url";
283
+ urls: string[];
284
+ }
285
+ | {
286
+ strategy: "preserve-by-id-and-detach";
287
+ ids: string[];
288
+ }
289
+ | {
290
+ strategy: "preserve-by-url-and-detach";
291
+ urls: string[];
292
+ };
293
+
294
+ export type DocumentDriveServerOptions = {
295
+ defaultDrives: {
296
+ loadOnInit?: boolean; // defaults to true
297
+ remoteDrives?: Array<DefaultRemoteDriveInput>;
298
+ removeOldRemoteDrives?: RemoveOldRemoteDrivesOption;
299
+ };
300
+ /* method to queue heavy tasks that might block the event loop.
301
+ * If set to null then it will queued as micro task.
302
+ * Defaults to the most appropriate method according to the system
303
+ */
304
+ taskQueueMethod?: RunAsap.RunAsap<unknown> | null;
305
+ listenerManager?: ListenerManagerOptions;
306
+ };
307
+
308
+ export type GetStrandsOptions = {
309
+ limit?: number;
310
+ since?: string;
311
+ fromRevision?: number;
312
+ };
352
313
 
353
- abstract initDrive(drive: DocumentDriveDocument): Promise<void>;
354
- abstract removeDrive(driveId: DocumentDriveState["id"]): Promise<void>;
355
-
356
- abstract addListener(listener: Listener): Promise<ITransmitter>;
357
- abstract removeListener(
358
- driveId: string,
359
- listenerId: string
360
- ): Promise<boolean>;
361
- abstract getListener(
362
- driveId: string,
363
- listenerId: string
364
- ): Promise<ListenerState | undefined>;
365
-
366
- abstract getTransmitter(
367
- driveId: string,
368
- listenerId: string
369
- ): Promise<ITransmitter | undefined>;
370
-
371
- abstract getStrands(
372
- listenerId: string,
373
- since?: string
374
- ): Promise<StrandUpdate[]>;
375
-
376
- abstract updateSynchronizationRevisions(
377
- driveId: string,
378
- syncUnits: SynchronizationUnit[],
379
- willUpdate?: (listeners: Listener[]) => void,
380
- onError?: (
381
- error: Error,
382
- driveId: string,
383
- listener: ListenerState
384
- ) => void
385
- ): Promise<ListenerUpdate[]>;
386
-
387
- abstract updateListenerRevision(
388
- listenerId: string,
389
- driveId: string,
390
- syncId: string,
391
- listenerRev: number
392
- ): Promise<void>;
314
+ export abstract class AbstractDocumentDriveServer {
315
+ /** Public methods **/
316
+ abstract initialize(): Promise<Error[] | null>;
317
+ abstract setDocumentModels(models: DocumentModel[]): void;
318
+ abstract getDrives(): Promise<string[]>;
319
+ abstract addDrive(drive: DriveInput): Promise<DocumentDriveDocument>;
320
+ abstract addRemoteDrive(
321
+ url: string,
322
+ options: RemoteDriveOptions,
323
+ ): Promise<DocumentDriveDocument>;
324
+ abstract deleteDrive(id: string): Promise<void>;
325
+ abstract getDrive(
326
+ id: string,
327
+ options?: GetDocumentOptions,
328
+ ): Promise<DocumentDriveDocument>;
329
+
330
+ abstract getDriveBySlug(slug: string): Promise<DocumentDriveDocument>;
331
+
332
+ abstract getDocuments(drive: string): Promise<string[]>;
333
+ abstract getDocument(
334
+ drive: string,
335
+ id: string,
336
+ options?: GetDocumentOptions,
337
+ ): Promise<Document>;
338
+
339
+ abstract addOperation(
340
+ drive: string,
341
+ id: string,
342
+ operation: Operation,
343
+ options?: AddOperationOptions,
344
+ ): Promise<IOperationResult>;
345
+
346
+ abstract addOperations(
347
+ drive: string,
348
+ id: string,
349
+ operations: Operation[],
350
+ options?: AddOperationOptions,
351
+ ): Promise<IOperationResult>;
352
+
353
+ abstract queueOperation(
354
+ drive: string,
355
+ id: string,
356
+ operation: Operation,
357
+ options?: AddOperationOptions,
358
+ ): Promise<IOperationResult>;
359
+
360
+ abstract queueOperations(
361
+ drive: string,
362
+ id: string,
363
+ operations: Operation[],
364
+ options?: AddOperationOptions,
365
+ ): Promise<IOperationResult>;
366
+
367
+ abstract queueAction(
368
+ drive: string,
369
+ id: string,
370
+ action: Action,
371
+ options?: AddOperationOptions,
372
+ ): Promise<IOperationResult>;
373
+
374
+ abstract queueActions(
375
+ drive: string,
376
+ id: string,
377
+ actions: Action[],
378
+ options?: AddOperationOptions,
379
+ ): Promise<IOperationResult>;
380
+
381
+ abstract addDriveOperation(
382
+ drive: string,
383
+ operation: Operation<DocumentDriveAction | BaseAction>,
384
+ options?: AddOperationOptions,
385
+ ): Promise<IOperationResult<DocumentDriveDocument>>;
386
+ abstract addDriveOperations(
387
+ drive: string,
388
+ operations: Operation<DocumentDriveAction | BaseAction>[],
389
+ options?: AddOperationOptions,
390
+ ): Promise<IOperationResult<DocumentDriveDocument>>;
391
+
392
+ abstract queueDriveOperation(
393
+ drive: string,
394
+ operation: Operation<DocumentDriveAction | BaseAction>,
395
+ options?: AddOperationOptions,
396
+ ): Promise<IOperationResult<DocumentDriveDocument>>;
397
+
398
+ abstract queueDriveOperations(
399
+ drive: string,
400
+ operations: Operation<DocumentDriveAction | BaseAction>[],
401
+ options?: AddOperationOptions,
402
+ ): Promise<IOperationResult<DocumentDriveDocument>>;
403
+
404
+ abstract queueDriveAction(
405
+ drive: string,
406
+ action: DocumentDriveAction | BaseAction,
407
+ options?: AddOperationOptions,
408
+ ): Promise<IOperationResult<DocumentDriveDocument>>;
409
+
410
+ abstract queueDriveActions(
411
+ drive: string,
412
+ actions: Array<DocumentDriveAction | BaseAction>,
413
+ options?: AddOperationOptions,
414
+ ): Promise<IOperationResult<DocumentDriveDocument>>;
415
+
416
+ abstract addAction(
417
+ drive: string,
418
+ id: string,
419
+ action: Action,
420
+ options?: AddOperationOptions,
421
+ ): Promise<IOperationResult>;
422
+ abstract addActions(
423
+ drive: string,
424
+ id: string,
425
+ actions: Action[],
426
+ options?: AddOperationOptions,
427
+ ): Promise<IOperationResult>;
428
+
429
+ abstract addDriveAction(
430
+ drive: string,
431
+ action: DocumentDriveAction | BaseAction,
432
+ options?: AddOperationOptions,
433
+ ): Promise<IOperationResult<DocumentDriveDocument>>;
434
+ abstract addDriveActions(
435
+ drive: string,
436
+ actions: (DocumentDriveAction | BaseAction)[],
437
+ options?: AddOperationOptions,
438
+ ): Promise<IOperationResult<DocumentDriveDocument>>;
439
+
440
+ abstract getSyncStatus(
441
+ syncUnitId: string,
442
+ ): SyncStatus | SynchronizationUnitNotFoundError;
443
+
444
+ /** Synchronization methods */
445
+ abstract getSynchronizationUnits(
446
+ driveId: string,
447
+ documentId?: string[],
448
+ scope?: string[],
449
+ branch?: string[],
450
+ documentType?: string[],
451
+ loadedDrive?: DocumentDriveDocument,
452
+ ): Promise<SynchronizationUnit[]>;
453
+
454
+ abstract getSynchronizationUnit(
455
+ driveId: string,
456
+ syncId: string,
457
+ loadedDrive?: DocumentDriveDocument,
458
+ ): Promise<SynchronizationUnit | undefined>;
459
+
460
+ abstract getSynchronizationUnitsIds(
461
+ driveId: string,
462
+ documentId?: string[],
463
+ scope?: string[],
464
+ branch?: string[],
465
+ documentType?: string[],
466
+ ): Promise<SynchronizationUnitQuery[]>;
467
+
468
+ abstract getOperationData(
469
+ driveId: string,
470
+ syncId: string,
471
+ filter: GetStrandsOptions,
472
+ loadedDrive?: DocumentDriveDocument,
473
+ ): Promise<OperationUpdate[]>;
474
+
475
+ /** Internal methods **/
476
+ protected abstract createDocument(
477
+ drive: string,
478
+ document: CreateDocumentInput,
479
+ ): Promise<Document>;
480
+ protected abstract deleteDocument(drive: string, id: string): Promise<void>;
481
+
482
+ protected abstract getDocumentModel(documentType: string): DocumentModel;
483
+
484
+ /** Event methods **/
485
+ protected abstract emit<K extends keyof DriveEvents>(
486
+ this: this,
487
+ event: K,
488
+ ...args: Parameters<DriveEvents[K]>
489
+ ): void;
490
+ abstract on<K extends keyof DriveEvents>(
491
+ this: this,
492
+ event: K,
493
+ cb: DriveEvents[K],
494
+ ): Unsubscribe;
495
+
496
+ abstract getTransmitter(
497
+ driveId: string,
498
+ listenerId: string,
499
+ ): Promise<ITransmitter | undefined>;
500
+
501
+ abstract clearStorage(): Promise<void>;
502
+
503
+ abstract registerPullResponderTrigger(
504
+ id: string,
505
+ url: string,
506
+ options: Pick<RemoteDriveOptions, "pullFilter" | "pullInterval">,
507
+ ): Promise<PullResponderTrigger>;
393
508
  }
394
509
 
395
- export type IDocumentDriveServer = Pick<
396
- BaseDocumentDriveServer,
397
- keyof BaseDocumentDriveServer
510
+ export type ListenerManagerOptions = {
511
+ sequentialUpdates?: boolean;
512
+ };
513
+
514
+ export const DefaultListenerManagerOptions = {
515
+ sequentialUpdates: true,
516
+ };
517
+
518
+ export type IBaseDocumentDriveServer = Pick<
519
+ AbstractDocumentDriveServer,
520
+ keyof AbstractDocumentDriveServer
398
521
  >;
399
522
 
523
+ export type IDocumentDriveServer = IBaseDocumentDriveServer &
524
+ IDefaultDrivesManager &
525
+ IReadModeDriveServer;
526
+
527
+ export abstract class BaseListenerManager {
528
+ protected drive: IBaseDocumentDriveServer;
529
+ protected listenerState = new Map<string, Map<string, ListenerState>>();
530
+ protected options: ListenerManagerOptions;
531
+ protected transmitters: Record<
532
+ DocumentDriveState["id"],
533
+ Record<Listener["listenerId"], ITransmitter>
534
+ > = {};
535
+
536
+ constructor(
537
+ drive: IBaseDocumentDriveServer,
538
+ listenerState = new Map<string, Map<string, ListenerState>>(),
539
+ options: ListenerManagerOptions = DefaultListenerManagerOptions,
540
+ ) {
541
+ this.drive = drive;
542
+ this.listenerState = listenerState;
543
+ this.options = { ...DefaultListenerManagerOptions, ...options };
544
+ }
545
+
546
+ abstract initDrive(drive: DocumentDriveDocument): Promise<void>;
547
+ abstract removeDrive(driveId: DocumentDriveState["id"]): Promise<void>;
548
+
549
+ abstract driveHasListeners(driveId: string): boolean;
550
+ abstract addListener(listener: Listener): Promise<ITransmitter>;
551
+ abstract removeListener(
552
+ driveId: string,
553
+ listenerId: string,
554
+ ): Promise<boolean>;
555
+ abstract getListener(
556
+ driveId: string,
557
+ listenerId: string,
558
+ ): Promise<ListenerState | undefined>;
559
+
560
+ abstract getTransmitter(
561
+ driveId: string,
562
+ listenerId: string,
563
+ ): Promise<ITransmitter | undefined>;
564
+
565
+ abstract getStrands(
566
+ driveId: string,
567
+ listenerId: string,
568
+ options?: GetStrandsOptions,
569
+ ): Promise<StrandUpdate[]>;
570
+
571
+ abstract updateSynchronizationRevisions(
572
+ driveId: string,
573
+ syncUnits: SynchronizationUnit[],
574
+ source: StrandUpdateSource,
575
+ willUpdate?: (listeners: Listener[]) => void,
576
+ onError?: (error: Error, driveId: string, listener: ListenerState) => void,
577
+ ): Promise<ListenerUpdate[]>;
578
+
579
+ abstract updateListenerRevision(
580
+ listenerId: string,
581
+ driveId: string,
582
+ syncId: string,
583
+ listenerRev: number,
584
+ ): Promise<void>;
585
+ }
586
+
400
587
  export type ListenerStatus =
401
- | 'CREATED'
402
- | 'PENDING'
403
- | 'SUCCESS'
404
- | 'MISSING'
405
- | 'CONFLICT'
406
- | 'ERROR';
588
+ | "CREATED"
589
+ | "PENDING"
590
+ | "SUCCESS"
591
+ | "MISSING"
592
+ | "CONFLICT"
593
+ | "ERROR";
407
594
 
408
595
  export interface ListenerState {
409
- driveId: string;
410
- block: boolean;
411
- pendingTimeout: string;
412
- listener: Listener;
413
- syncUnits: Map<SynchronizationUnit['syncId'], SyncronizationUnitState>;
414
- listenerStatus: ListenerStatus;
596
+ driveId: string;
597
+ block: boolean;
598
+ pendingTimeout: string;
599
+ listener: Listener;
600
+ syncUnits: Map<SynchronizationUnit["syncId"], SyncronizationUnitState>;
601
+ listenerStatus: ListenerStatus;
415
602
  }
416
603
 
417
604
  export interface SyncronizationUnitState {
418
- listenerRev: number;
419
- lastUpdated: string;
605
+ listenerRev: number;
606
+ lastUpdated: string;
420
607
  }