document-drive 0.0.29 → 1.0.0-alpha.1

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "document-drive",
3
- "version": "0.0.29",
3
+ "version": "1.0.0-alpha.1",
4
4
  "license": "AGPL-3.0-only",
5
5
  "type": "module",
6
6
  "module": "./src/index.ts",
@@ -24,12 +24,13 @@
24
24
  "lint": "eslint src --ext .js,.jsx,.ts,.tsx && yarn check-types",
25
25
  "lint:fix": "eslint src --ext .js,.jsx,.ts,.tsx --fix",
26
26
  "format": "prettier . --write",
27
+ "release": "semantic-release",
27
28
  "test": "vitest run --coverage",
28
29
  "test:watch": "vitest watch"
29
30
  },
30
31
  "peerDependencies": {
31
32
  "@prisma/client": "5.8.1",
32
- "document-model": "^1.0.28",
33
+ "document-model": "^1.0.29",
33
34
  "document-model-libs": "^1.1.44",
34
35
  "localforage": "^1.10.0",
35
36
  "sequelize": "^6.35.2",
@@ -39,16 +40,21 @@
39
40
  "graphql": "^16.8.1",
40
41
  "graphql-request": "^6.1.0",
41
42
  "json-stringify-deterministic": "^1.0.12",
43
+ "nanoevents": "^9.0.0",
42
44
  "sanitize-filename": "^1.6.3"
43
45
  },
44
46
  "devDependencies": {
47
+ "@commitlint/cli": "^18.6.1",
48
+ "@commitlint/config-conventional": "^18.6.2",
45
49
  "@prisma/client": "5.8.1",
50
+ "@semantic-release/changelog": "^6.0.3",
51
+ "@semantic-release/git": "^10.0.1",
46
52
  "@total-typescript/ts-reset": "^0.5.1",
47
53
  "@types/node": "^20.11.16",
48
54
  "@typescript-eslint/eslint-plugin": "^6.18.1",
49
55
  "@typescript-eslint/parser": "^6.18.1",
50
56
  "@vitest/coverage-v8": "^0.34.6",
51
- "document-model": "^1.0.28",
57
+ "document-model": "^1.0.29",
52
58
  "document-model-libs": "^1.1.44",
53
59
  "eslint": "^8.56.0",
54
60
  "eslint-config-prettier": "^9.1.0",
@@ -57,6 +63,7 @@
57
63
  "msw": "^2.1.2",
58
64
  "prettier": "^3.1.1",
59
65
  "prettier-plugin-organize-imports": "^3.2.4",
66
+ "semantic-release": "^23.0.2",
60
67
  "sequelize": "^6.35.2",
61
68
  "sqlite3": "^5.1.7",
62
69
  "typescript": "^5.3.2",
@@ -3,19 +3,20 @@ import {
3
3
  DocumentDriveDocument,
4
4
  DocumentDriveState,
5
5
  FileNode,
6
- Trigger,
7
6
  isFileNode,
7
+ Trigger,
8
8
  utils
9
9
  } from 'document-model-libs/document-drive';
10
10
  import {
11
11
  Action,
12
12
  BaseAction,
13
+ utils as baseUtils,
13
14
  Document,
14
15
  DocumentModel,
15
16
  Operation,
16
- OperationScope,
17
- utils as baseUtils
17
+ OperationScope
18
18
  } from 'document-model/document';
19
+ import { createNanoEvents, Unsubscribe } from 'nanoevents';
19
20
  import { MemoryStorage } from '../storage/memory';
20
21
  import type { DocumentStorage, IDriveStorage } from '../storage/types';
21
22
  import { generateUUID, isDocumentDrive, isNoopUpdate } from '../utils';
@@ -26,7 +27,9 @@ import { PullResponderTransmitter } from './listener/transmitter';
26
27
  import type { ITransmitter } from './listener/transmitter/types';
27
28
  import {
28
29
  BaseDocumentDriveServer,
30
+ DriveEvents,
29
31
  IOperationResult,
32
+ ListenerState,
30
33
  RemoteDriveOptions,
31
34
  StrandUpdate,
32
35
  SyncStatus,
@@ -43,6 +46,7 @@ export type * from './types';
43
46
  export const PULL_DRIVE_INTERVAL = 5000;
44
47
 
45
48
  export class DocumentDriveServer extends BaseDocumentDriveServer {
49
+ private emitter = createNanoEvents<DriveEvents>();
46
50
  private documentModels: DocumentModel[];
47
51
  private storage: IDriveStorage;
48
52
  private listenerStateManager: ListenerManager;
@@ -62,6 +66,15 @@ export class DocumentDriveServer extends BaseDocumentDriveServer {
62
66
  this.storage = storage;
63
67
  }
64
68
 
69
+ private updateSyncStatus(
70
+ driveId: string,
71
+ status: SyncStatus,
72
+ error?: Error
73
+ ) {
74
+ this.syncStatus.set(driveId, status);
75
+ this.emit('syncStatus', driveId, status, error);
76
+ }
77
+
65
78
  private async saveStrand(strand: StrandUpdate) {
66
79
  const operations: Operation[] = strand.operations.map(
67
80
  ({ index, type, hash, input, skip, timestamp }) => ({
@@ -86,10 +99,27 @@ export class DocumentDriveServer extends BaseDocumentDriveServer {
86
99
  strand.documentId,
87
100
  operations
88
101
  ));
89
- this.syncStatus.set(strand.driveId, result.status);
102
+
103
+ this.updateSyncStatus(strand.driveId, result.status);
104
+ this.emit('strandUpdate', strand);
90
105
  return result;
91
106
  }
92
107
 
108
+ private handleListenerError(
109
+ error: Error,
110
+ driveId: string,
111
+ listener: ListenerState
112
+ ) {
113
+ console.error(
114
+ `Listener ${listener.listener.label ?? listener.listener.listenerId} error: ${error.message}`
115
+ );
116
+ this.updateSyncStatus(
117
+ driveId,
118
+ error instanceof OperationError ? error.status : 'ERROR',
119
+ error
120
+ );
121
+ }
122
+
93
123
  private shouldSyncRemoteDrive(drive: DocumentDriveDocument) {
94
124
  return (
95
125
  drive.state.local.availableOffline &&
@@ -108,16 +138,16 @@ export class DocumentDriveServer extends BaseDocumentDriveServer {
108
138
 
109
139
  if (!driveTriggers) {
110
140
  driveTriggers = new Map();
111
- this.syncStatus.set(driveId, 'SYNCING');
112
141
  }
113
142
 
143
+ this.updateSyncStatus(driveId, 'SYNCING');
114
144
  if (PullResponderTransmitter.isPullResponderTrigger(trigger)) {
115
145
  const intervalId = PullResponderTransmitter.setupPull(
116
146
  driveId,
117
147
  trigger,
118
148
  this.saveStrand.bind(this),
119
149
  error => {
120
- this.syncStatus.set(
150
+ this.updateSyncStatus(
121
151
  driveId,
122
152
  error instanceof OperationError
123
153
  ? error.status
@@ -265,7 +295,7 @@ export class DocumentDriveServer extends BaseDocumentDriveServer {
265
295
  (filter.since !== undefined &&
266
296
  filter.since <= operation.timestamp) ||
267
297
  (filter.fromRevision !== undefined &&
268
- operation.index >= filter.fromRevision)
298
+ operation.index > filter.fromRevision)
269
299
  );
270
300
 
271
301
  return filteredOperations.map(operation => ({
@@ -456,6 +486,12 @@ export class DocumentDriveServer extends BaseDocumentDriveServer {
456
486
  }
457
487
 
458
488
  async deleteDocument(driveId: string, id: string) {
489
+ try {
490
+ const syncUnits = await this.getSynchronizationUnits(driveId, [id]);
491
+ this.listenerStateManager.removeSyncUnits(syncUnits);
492
+ } catch {
493
+ /* empty */
494
+ }
459
495
  return this.storage.deleteDocument(driveId, id);
460
496
  }
461
497
 
@@ -664,10 +700,10 @@ export class DocumentDriveServer extends BaseDocumentDriveServer {
664
700
  );
665
701
  }
666
702
 
667
- const results = await Promise.all(
668
- operationSignals.map(handler => handler())
669
- );
670
- signalResults.push(...results);
703
+ for (const signalHandler of operationSignals) {
704
+ const result = await signalHandler();
705
+ signalResults.push(result);
706
+ }
671
707
 
672
708
  return {
673
709
  document: newDocument,
@@ -749,10 +785,21 @@ export class DocumentDriveServer extends BaseDocumentDriveServer {
749
785
  drive,
750
786
  syncUnit.syncId,
751
787
  syncUnit.revision,
752
- syncUnit.lastUpdated
788
+ syncUnit.lastUpdated,
789
+ () => this.updateSyncStatus(drive, 'SYNCING'),
790
+ this.handleListenerError.bind(this)
791
+ )
792
+ .then(
793
+ updates =>
794
+ updates.length &&
795
+ this.updateSyncStatus(drive, 'SUCCESS')
753
796
  )
754
797
  .catch(error => {
755
- console.error('Error updating sync revision', error);
798
+ console.error(
799
+ 'Non handled error updating sync revision',
800
+ error
801
+ );
802
+ this.updateSyncStatus(drive, 'ERROR', error as Error);
756
803
  });
757
804
  }
758
805
 
@@ -875,10 +922,21 @@ export class DocumentDriveServer extends BaseDocumentDriveServer {
875
922
  drive,
876
923
  '0',
877
924
  lastOperation.index,
878
- lastOperation.timestamp
925
+ lastOperation.timestamp,
926
+ () => this.updateSyncStatus(drive, 'SYNCING'),
927
+ this.handleListenerError.bind(this)
928
+ )
929
+ .then(
930
+ updates =>
931
+ updates.length &&
932
+ this.updateSyncStatus(drive, 'SUCCESS')
879
933
  )
880
934
  .catch(error => {
881
- console.error('Error updating sync revision', error);
935
+ console.error(
936
+ 'Non handled error updating sync revision',
937
+ error
938
+ );
939
+ this.updateSyncStatus(drive, 'ERROR', error as Error);
882
940
  });
883
941
  }
884
942
 
@@ -935,4 +993,15 @@ export class DocumentDriveServer extends BaseDocumentDriveServer {
935
993
  }
936
994
  return status;
937
995
  }
996
+
997
+ on<K extends keyof DriveEvents>(event: K, cb: DriveEvents[K]): Unsubscribe {
998
+ return this.emitter.on(event, cb);
999
+ }
1000
+
1001
+ protected emit<K extends keyof DriveEvents>(
1002
+ event: K,
1003
+ ...args: Parameters<DriveEvents[K]>
1004
+ ): void {
1005
+ return this.emitter.emit(event, ...args);
1006
+ }
938
1007
  }
@@ -9,6 +9,8 @@ import {
9
9
  ErrorStatus,
10
10
  Listener,
11
11
  ListenerState,
12
+ ListenerUpdate,
13
+ OperationUpdate,
12
14
  StrandUpdate,
13
15
  SynchronizationUnit
14
16
  } from '../types';
@@ -120,14 +122,20 @@ export class ListenerManager extends BaseListenerManager {
120
122
  driveId: string,
121
123
  syncId: string,
122
124
  syncRev: number,
123
- lastUpdated: string
125
+ lastUpdated: string,
126
+ willUpdate?: (listeners: Listener[]) => void,
127
+ onError?: (
128
+ error: Error,
129
+ driveId: string,
130
+ listener: ListenerState
131
+ ) => void
124
132
  ) {
125
133
  const drive = this.listenerState.get(driveId);
126
134
  if (!drive) {
127
- return;
135
+ return [];
128
136
  }
129
137
 
130
- let newRevision = false;
138
+ const outdatedListeners: Listener[] = [];
131
139
  for (const [, listener] of drive) {
132
140
  const syncUnits = listener.syncUnits.filter(
133
141
  e => e.syncId === syncId
@@ -143,13 +151,21 @@ export class ListenerManager extends BaseListenerManager {
143
151
 
144
152
  syncUnit.syncRev = syncRev;
145
153
  syncUnit.lastUpdated = lastUpdated;
146
- newRevision = true;
154
+ if (
155
+ !outdatedListeners.find(
156
+ l => l.listenerId === listener.listener.listenerId
157
+ )
158
+ ) {
159
+ outdatedListeners.push(listener.listener);
160
+ }
147
161
  }
148
162
  }
149
163
 
150
- if (newRevision) {
151
- return this.triggerUpdate();
164
+ if (outdatedListeners.length) {
165
+ willUpdate?.(outdatedListeners);
166
+ return this.triggerUpdate(onError);
152
167
  }
168
+ return [];
153
169
  }
154
170
 
155
171
  async addSyncUnits(syncUnits: SynchronizationUnit[]) {
@@ -202,6 +218,19 @@ export class ListenerManager extends BaseListenerManager {
202
218
  }
203
219
  }
204
220
 
221
+ removeSyncUnits(syncUnits: SynchronizationUnit[]) {
222
+ for (const [driveId, drive] of this.listenerState) {
223
+ const syncIds = syncUnits
224
+ .filter(s => s.driveId === driveId)
225
+ .map(s => s.syncId);
226
+ for (const [, listenerState] of drive) {
227
+ listenerState.syncUnits = listenerState.syncUnits.filter(
228
+ s => !syncIds.includes(s.syncId)
229
+ );
230
+ }
231
+ }
232
+ }
233
+
205
234
  async updateListenerRevision(
206
235
  listenerId: string,
207
236
  driveId: string,
@@ -225,7 +254,14 @@ export class ListenerManager extends BaseListenerManager {
225
254
  }
226
255
  }
227
256
 
228
- async triggerUpdate() {
257
+ async triggerUpdate(
258
+ onError?: (
259
+ error: Error,
260
+ driveId: string,
261
+ listener: ListenerState
262
+ ) => void
263
+ ) {
264
+ const listenerUpdates: ListenerUpdate[] = [];
229
265
  for (const [driveId, drive] of this.listenerState) {
230
266
  for (const [id, listener] of drive) {
231
267
  const transmitter = await this.getTransmitter(driveId, id);
@@ -248,13 +284,19 @@ export class ListenerManager extends BaseListenerManager {
248
284
  continue;
249
285
  }
250
286
 
251
- const opData = await this.drive.getOperationData(
252
- driveId,
253
- syncId,
254
- {
255
- fromRevision: listenerRev
256
- }
257
- );
287
+ const opData: OperationUpdate[] = [];
288
+ try {
289
+ const data = await this.drive.getOperationData(
290
+ driveId,
291
+ syncId,
292
+ {
293
+ fromRevision: listenerRev
294
+ }
295
+ );
296
+ opData.push(...data);
297
+ } catch (e) {
298
+ console.error(e);
299
+ }
258
300
 
259
301
  if (!opData.length) {
260
302
  continue;
@@ -307,14 +349,19 @@ export class ListenerManager extends BaseListenerManager {
307
349
  );
308
350
  }
309
351
  listener.listenerStatus = 'SUCCESS';
352
+ listenerUpdates.push({
353
+ listenerId: listener.listener.listenerId,
354
+ listenerRevisions
355
+ });
310
356
  } catch (e) {
311
357
  // TODO: Handle error based on listener params (blocking, retry, etc)
358
+ onError?.(e as Error, driveId, listener);
312
359
  listener.listenerStatus =
313
360
  e instanceof OperationError ? e.status : 'ERROR';
314
- throw e;
315
361
  }
316
362
  }
317
363
  }
364
+ return listenerUpdates;
318
365
  }
319
366
 
320
367
  private _checkFilter(
@@ -218,6 +218,87 @@ export class PullResponderTransmitter implements ITransmitter {
218
218
  return result.acknowledge;
219
219
  }
220
220
 
221
+ private static async executePull(
222
+ driveId: string,
223
+ trigger: PullResponderTrigger,
224
+ onStrandUpdate: (strand: StrandUpdate) => Promise<IOperationResult>,
225
+ onError: (error: Error) => void,
226
+ onAcknowledge?: (success: boolean) => void
227
+ ) {
228
+ try {
229
+ const { url, listenerId } = trigger.data;
230
+ const strands = await PullResponderTransmitter.pullStrands(
231
+ driveId,
232
+ url,
233
+ listenerId
234
+ // since ?
235
+ );
236
+
237
+ // if there are no new strands then do nothing
238
+ if (!strands.length) {
239
+ return;
240
+ }
241
+
242
+ const listenerRevisions: ListenerRevision[] = [];
243
+
244
+ for (const strand of strands) {
245
+ const operations: Operation[] = strand.operations.map(
246
+ ({ index, type, hash, input, skip, timestamp }) => ({
247
+ index,
248
+ type,
249
+ hash,
250
+ input,
251
+ skip,
252
+ timestamp,
253
+ scope: strand.scope,
254
+ branch: strand.branch
255
+ })
256
+ );
257
+
258
+ let error: Error | undefined = undefined;
259
+
260
+ try {
261
+ const result = await onStrandUpdate(strand);
262
+ if (result.error) {
263
+ throw result.error;
264
+ }
265
+ } catch (e) {
266
+ error = e as Error;
267
+ onError(error);
268
+ }
269
+
270
+ listenerRevisions.push({
271
+ branch: strand.branch,
272
+ documentId: strand.documentId || '',
273
+ driveId: strand.driveId,
274
+ revision: operations.pop()?.index ?? -1,
275
+ scope: strand.scope as OperationScope,
276
+ status: error
277
+ ? error instanceof OperationError
278
+ ? error.status
279
+ : 'ERROR'
280
+ : 'SUCCESS'
281
+ });
282
+
283
+ // TODO: Should try to parse remaining strands?
284
+ if (error) {
285
+ break;
286
+ }
287
+ }
288
+
289
+ await PullResponderTransmitter.acknowledgeStrands(
290
+ driveId,
291
+ url,
292
+ listenerId,
293
+ listenerRevisions
294
+ )
295
+ .then(result => onAcknowledge?.(result))
296
+ .catch(error => console.error('ACK error', error));
297
+ } catch (error) {
298
+ onError(error as Error);
299
+ }
300
+ }
301
+
221
302
  static setupPull(
222
303
  driveId: string,
223
304
  trigger: PullResponderTrigger,
@@ -225,7 +306,7 @@ export class PullResponderTransmitter implements ITransmitter {
225
306
  onError: (error: Error) => void,
226
307
  onAcknowledge?: (success: boolean) => void
227
308
  ): number {
228
- const { url, listenerId, interval } = trigger.data;
309
+ const { interval } = trigger.data;
229
310
  let loopInterval = PULL_DRIVE_INTERVAL;
230
311
  if (interval) {
231
312
  try {
@@ -238,79 +319,25 @@ export class PullResponderTransmitter implements ITransmitter {
238
319
  }
239
320
  }
240
321
 
241
- const timeout = setInterval(async () => {
242
- try {
243
- const strands = await PullResponderTransmitter.pullStrands(
244
- driveId,
245
- url,
246
- listenerId
247
- // since ?
248
- );
249
-
250
- // if there are no new strands then do nothing
251
- if (!strands.length) {
252
- return;
253
- }
254
-
255
- const listenerRevisions: ListenerRevision[] = [];
256
-
257
- for (const strand of strands) {
258
- const operations: Operation[] = strand.operations.map(
259
- ({ index, type, hash, input, skip, timestamp }) => ({
260
- index,
261
- type,
262
- hash,
263
- input,
264
- skip,
265
- timestamp,
266
- scope: strand.scope,
267
- branch: strand.branch
268
- })
269
- );
270
-
271
- let error: Error | undefined = undefined;
272
-
273
- try {
274
- const result = await onStrandUpdate(strand);
275
- if (result.error) {
276
- throw result.error;
277
- }
278
- } catch (e) {
279
- error = e as Error;
280
- onError?.(error);
281
- }
282
-
283
- listenerRevisions.push({
284
- branch: strand.branch,
285
- documentId: strand.documentId ?? '',
286
- driveId: strand.driveId,
287
- revision: operations.pop()?.index ?? -1,
288
- scope: strand.scope as OperationScope,
289
- status: error
290
- ? error instanceof OperationError
291
- ? error.status
292
- : 'ERROR'
293
- : 'SUCCESS'
294
- });
295
-
296
- // TODO: Should try to parse remaining strands?
297
- if (error) {
298
- break;
299
- }
300
- }
322
+ this.executePull(
323
+ driveId,
324
+ trigger,
325
+ onStrandUpdate,
326
+ onError,
327
+ onAcknowledge
328
+ );
301
329
 
302
- await PullResponderTransmitter.acknowledgeStrands(
330
+ const timeout = setInterval(
331
+ async () =>
332
+ this.executePull(
303
333
  driveId,
304
- url,
305
- listenerId,
306
- listenerRevisions
307
- )
308
- .then(result => onAcknowledge?.(result))
309
- .catch(error => console.error('ACK error', error));
310
- } catch (error) {
311
- onError(error as Error);
312
- }
313
- }, loopInterval);
334
+ trigger,
335
+ onStrandUpdate,
336
+ onError,
337
+ onAcknowledge
338
+ ),
339
+ loopInterval
340
+ );
314
341
  return timeout as unknown as number;
315
342
  }
316
343
 
@@ -15,6 +15,7 @@ import type {
15
15
  Signal,
16
16
  State
17
17
  } from 'document-model/document';
18
+ import { Unsubscribe } from 'nanoevents';
18
19
  import { OperationError } from './error';
19
20
  import { ITransmitter } from './listener/transmitter/types';
20
21
 
@@ -92,6 +93,11 @@ export type ListenerRevision = {
92
93
  revision: number;
93
94
  };
94
95
 
96
+ export type ListenerUpdate = {
97
+ listenerId: string;
98
+ listenerRevisions: ListenerRevision[];
99
+ };
100
+
95
101
  export type UpdateStatus = 'SUCCESS' | 'CONFLICT' | 'MISSING' | 'ERROR';
96
102
  export type ErrorStatus = Exclude<UpdateStatus, 'SUCCESS'>;
97
103
 
@@ -114,6 +120,11 @@ export type StrandUpdate = {
114
120
 
115
121
  export type SyncStatus = 'SYNCING' | UpdateStatus;
116
122
 
123
+ export interface DriveEvents {
124
+ syncStatus: (driveId: string, status: SyncStatus, error?: Error) => void;
125
+ strandUpdate: (update: StrandUpdate) => void;
126
+ }
127
+
117
128
  export abstract class BaseDocumentDriveServer {
118
129
  /** Public methods **/
119
130
  abstract getDrives(): Promise<string[]>;
@@ -132,12 +143,12 @@ export abstract class BaseDocumentDriveServer {
132
143
  drive: string,
133
144
  id: string,
134
145
  operation: Operation
135
- ): Promise<IOperationResult<Document>>;
146
+ ): Promise<IOperationResult>;
136
147
  abstract addOperations(
137
148
  drive: string,
138
149
  id: string,
139
150
  operations: Operation[]
140
- ): Promise<IOperationResult<Document>>;
151
+ ): Promise<IOperationResult>;
141
152
 
142
153
  abstract addDriveOperation(
143
154
  drive: string,
@@ -183,12 +194,23 @@ export abstract class BaseDocumentDriveServer {
183
194
  driveId: string,
184
195
  listenerId: string
185
196
  ): Promise<ITransmitter | undefined>;
197
+
198
+ /** Event methods **/
199
+ protected abstract emit<K extends keyof DriveEvents>(
200
+ this: this,
201
+ event: K,
202
+ ...args: Parameters<DriveEvents[K]>
203
+ ): void;
204
+ abstract on<K extends keyof DriveEvents>(
205
+ this: this,
206
+ event: K,
207
+ cb: DriveEvents[K]
208
+ ): Unsubscribe;
186
209
  }
187
210
 
188
211
  export abstract class BaseListenerManager {
189
212
  protected drive: BaseDocumentDriveServer;
190
- protected listenerState: Map<string, Map<string, ListenerState>> =
191
- new Map();
213
+ protected listenerState = new Map<string, Map<string, ListenerState>>();
192
214
  protected transmitters: Record<
193
215
  DocumentDriveState['id'],
194
216
  Record<Listener['listenerId'], ITransmitter>
@@ -196,7 +218,7 @@ export abstract class BaseListenerManager {
196
218
 
197
219
  constructor(
198
220
  drive: BaseDocumentDriveServer,
199
- listenerState: Map<string, Map<string, ListenerState>> = new Map()
221
+ listenerState = new Map<string, Map<string, ListenerState>>()
200
222
  ) {
201
223
  this.drive = drive;
202
224
  this.listenerState = listenerState;
@@ -216,8 +238,14 @@ export abstract class BaseListenerManager {
216
238
  driveId: string,
217
239
  syncId: string,
218
240
  syncRev: number,
219
- lastUpdated: string
220
- ): Promise<void>;
241
+ lastUpdated: string,
242
+ willUpdate?: (listeners: Listener[]) => void,
243
+ onError?: (
244
+ error: Error,
245
+ driveId: string,
246
+ listener: ListenerState
247
+ ) => void
248
+ ): Promise<ListenerUpdate[]>;
221
249
 
222
250
  abstract updateListenerRevision(
223
251
  listenerId: string,
@@ -119,28 +119,6 @@ export class PrismaStorage implements IDriveStorage {
119
119
  } catch (e) {
120
120
  console.log(e);
121
121
  }
122
-
123
- await this.db.document.upsert({
124
- where: {
125
- id_driveId: {
126
- id: 'drives',
127
- driveId: id
128
- }
129
- },
130
- create: {
131
- id: 'drives',
132
- driveId: id,
133
- documentType: header.documentType,
134
- initialState: document.initialState,
135
- lastModified: header.lastModified,
136
- revision: header.revision,
137
- created: header.created
138
- },
139
- update: {
140
- lastModified: header.lastModified,
141
- revision: header.revision
142
- }
143
- });
144
122
  }
145
123
 
146
124
  async getDocuments(drive: string) {