document-drive 1.0.0-alpha.26 → 1.0.0-alpha.28

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": "1.0.0-alpha.26",
3
+ "version": "1.0.0-alpha.28",
4
4
  "license": "AGPL-3.0-only",
5
5
  "type": "module",
6
6
  "module": "./src/index.ts",
@@ -31,7 +31,7 @@
31
31
  "peerDependencies": {
32
32
  "@prisma/client": "5.11.0",
33
33
  "document-model": "^1.0.35",
34
- "document-model-libs": "^1.17.1",
34
+ "document-model-libs": "^1.18.1",
35
35
  "localforage": "^1.10.0",
36
36
  "sequelize": "^6.35.2",
37
37
  "sqlite3": "^5.1.7"
@@ -53,9 +53,9 @@
53
53
  "@types/node": "^20.11.16",
54
54
  "@typescript-eslint/eslint-plugin": "^6.18.1",
55
55
  "@typescript-eslint/parser": "^6.18.1",
56
- "@vitest/coverage-v8": "^0.34.6",
56
+ "@vitest/coverage-v8": "^1.4.0",
57
57
  "document-model": "^1.0.34",
58
- "document-model-libs": "^1.17.1",
58
+ "document-model-libs": "^1.18.1",
59
59
  "eslint": "^8.56.0",
60
60
  "eslint-config-prettier": "^9.1.0",
61
61
  "fake-indexeddb": "^5.0.1",
@@ -68,6 +68,6 @@
68
68
  "sequelize": "^6.35.2",
69
69
  "sqlite3": "^5.1.7",
70
70
  "typescript": "^5.3.2",
71
- "vitest": "^1.2.2"
71
+ "vitest": "^1.4.0"
72
72
  }
73
73
  }
@@ -121,12 +121,14 @@ export class DocumentDriveServer extends BaseDocumentDriveServer {
121
121
  const result = await (!strand.documentId
122
122
  ? this.addDriveOperations(
123
123
  strand.driveId,
124
- operations as Operation<DocumentDriveAction | BaseAction>[]
124
+ operations as Operation<DocumentDriveAction | BaseAction>[],
125
+ false
125
126
  )
126
127
  : this.addOperations(
127
128
  strand.driveId,
128
129
  strand.documentId,
129
- operations
130
+ operations,
131
+ false
130
132
  ));
131
133
 
132
134
  if (result.status === 'ERROR') {
@@ -229,7 +231,8 @@ export class DocumentDriveServer extends BaseDocumentDriveServer {
229
231
  driveId: string,
230
232
  documentId?: string[],
231
233
  scope?: string[],
232
- branch?: string[]
234
+ branch?: string[],
235
+ documentType?: string[]
233
236
  ) {
234
237
  const drive = await this.getDrive(driveId);
235
238
 
@@ -238,21 +241,23 @@ export class DocumentDriveServer extends BaseDocumentDriveServer {
238
241
  isFileNode(node) &&
239
242
  (!documentId?.length ||
240
243
  documentId.includes(node.id) ||
241
- documentId.includes('*'))
244
+ documentId.includes('*')) &&
245
+ (!documentType?.length ||
246
+ documentType.includes(node.documentType) ||
247
+ documentType.includes('*'))
242
248
  ) as Pick<
243
249
  FileNode,
244
250
  'id' | 'documentType' | 'scopes' | 'synchronizationUnits'
245
251
  >[];
246
252
 
247
- if (documentId && !nodes.length) {
248
- throw new Error('File node not found');
249
- }
250
-
251
253
  // checks if document drive synchronization unit should be added
252
254
  if (
253
- !documentId ||
254
- documentId.includes('*') ||
255
- documentId.includes('')
255
+ (!documentId ||
256
+ documentId.includes('*') ||
257
+ documentId.includes('')) &&
258
+ (!documentType?.length ||
259
+ documentType.includes('powerhouse/document-drive') ||
260
+ documentType.includes('*'))
256
261
  ) {
257
262
  nodes.unshift({
258
263
  id: '',
@@ -275,8 +280,12 @@ export class DocumentDriveServer extends BaseDocumentDriveServer {
275
280
  scope?.length || branch?.length
276
281
  ? node.synchronizationUnits.filter(
277
282
  unit =>
278
- (!scope?.length || scope.includes(unit.scope)) &&
279
- (!branch?.length || branch.includes(unit.branch))
283
+ (!scope?.length ||
284
+ scope.includes(unit.scope) ||
285
+ scope.includes('*')) &&
286
+ (!branch?.length ||
287
+ branch.includes(unit.branch) ||
288
+ branch.includes('*'))
280
289
  )
281
290
  : node.synchronizationUnits;
282
291
  if (!nodeUnits.length) {
@@ -506,31 +515,15 @@ export class DocumentDriveServer extends BaseDocumentDriveServer {
506
515
 
507
516
  await this.storage.createDocument(driveId, input.id, document);
508
517
 
509
- await this.listenerStateManager.addSyncUnits(
510
- input.synchronizationUnits.map(({ syncId, scope, branch }) => {
511
- const lastOperation = document.operations[scope].slice().pop();
512
- return {
513
- syncId,
514
- scope,
515
- branch,
516
- driveId,
517
- documentId: input.id,
518
- documentType: document.documentType,
519
- lastUpdated:
520
- lastOperation?.timestamp ?? document.lastModified,
521
- revision: lastOperation?.index ?? 0
522
- };
523
- })
524
- );
525
518
  return document;
526
519
  }
527
520
 
528
521
  async deleteDocument(driveId: string, id: string) {
529
522
  try {
530
523
  const syncUnits = await this.getSynchronizationUnits(driveId, [id]);
531
- this.listenerStateManager.removeSyncUnits(syncUnits);
532
- } catch {
533
- /* empty */
524
+ await this.listenerStateManager.removeSyncUnits(driveId, syncUnits);
525
+ } catch (error) {
526
+ console.warn('Error deleting document', error);
534
527
  }
535
528
  return this.storage.deleteDocument(driveId, id);
536
529
  }
@@ -788,7 +781,12 @@ export class DocumentDriveServer extends BaseDocumentDriveServer {
788
781
  }
789
782
  }
790
783
 
791
- async addOperations(drive: string, id: string, operations: Operation[]) {
784
+ async addOperations(
785
+ drive: string,
786
+ id: string,
787
+ operations: Operation[],
788
+ forceSync = true
789
+ ) {
792
790
  let document: Document | undefined;
793
791
  const operationsApplied: Operation[] = [];
794
792
  const updatedOperations: Operation[] = [];
@@ -841,29 +839,26 @@ export class DocumentDriveServer extends BaseDocumentDriveServer {
841
839
  branches
842
840
  );
843
841
  // update listener cache
844
- for (const syncUnit of syncUnits) {
845
- this.listenerStateManager
846
- .updateSynchronizationRevision(
847
- drive,
848
- syncUnit.syncId,
849
- syncUnit.revision,
850
- syncUnit.lastUpdated,
851
- () => this.updateSyncStatus(drive, 'SYNCING'),
852
- this.handleListenerError.bind(this)
853
- )
854
- .then(
855
- updates =>
856
- updates.length &&
857
- this.updateSyncStatus(drive, 'SUCCESS')
858
- )
859
- .catch(error => {
860
- console.error(
861
- 'Non handled error updating sync revision',
862
- error
863
- );
864
- this.updateSyncStatus(drive, 'ERROR', error as Error);
865
- });
866
- }
842
+ this.listenerStateManager
843
+ .updateSynchronizationRevisions(
844
+ drive,
845
+ syncUnits,
846
+ () => this.updateSyncStatus(drive, 'SYNCING'),
847
+ this.handleListenerError.bind(this),
848
+ forceSync
849
+ )
850
+ .then(
851
+ updates =>
852
+ updates.length &&
853
+ this.updateSyncStatus(drive, 'SUCCESS')
854
+ )
855
+ .catch(error => {
856
+ console.error(
857
+ 'Non handled error updating sync revision',
858
+ error
859
+ );
860
+ this.updateSyncStatus(drive, 'ERROR', error as Error);
861
+ });
867
862
 
868
863
  // after applying all the valid operations,throws
869
864
  // an error if there was an invalid operation
@@ -943,7 +938,8 @@ export class DocumentDriveServer extends BaseDocumentDriveServer {
943
938
 
944
939
  async addDriveOperations(
945
940
  drive: string,
946
- operations: Operation<DocumentDriveAction | BaseAction>[]
941
+ operations: Operation<DocumentDriveAction | BaseAction>[],
942
+ forceSync = true
947
943
  ) {
948
944
  let document: DocumentDriveDocument | undefined;
949
945
  const operationsApplied: Operation<DocumentDriveAction | BaseAction>[] =
@@ -994,13 +990,23 @@ export class DocumentDriveServer extends BaseDocumentDriveServer {
994
990
  .pop();
995
991
  if (lastOperation) {
996
992
  this.listenerStateManager
997
- .updateSynchronizationRevision(
993
+ .updateSynchronizationRevisions(
998
994
  drive,
999
- '0',
1000
- lastOperation.index,
1001
- lastOperation.timestamp,
995
+ [
996
+ {
997
+ syncId: '0',
998
+ driveId: drive,
999
+ documentId: '',
1000
+ scope: 'global',
1001
+ branch: 'main',
1002
+ documentType: 'powerhouse/document-drive',
1003
+ lastUpdated: lastOperation.timestamp,
1004
+ revision: lastOperation.index
1005
+ }
1006
+ ],
1002
1007
  () => this.updateSyncStatus(drive, 'SYNCING'),
1003
- this.handleListenerError.bind(this)
1008
+ this.handleListenerError.bind(this),
1009
+ forceSync
1004
1010
  )
1005
1011
  .then(
1006
1012
  updates =>
@@ -19,7 +19,35 @@ import { InternalTransmitter } from './transmitter/internal';
19
19
  import { SwitchboardPushTransmitter } from './transmitter/switchboard-push';
20
20
  import { ITransmitter } from './transmitter/types';
21
21
 
22
+ function debounce<T extends unknown[], R>(
23
+ func: (...args: T) => Promise<R>,
24
+ delay = 250
25
+ ) {
26
+ let timer: number;
27
+ return (immediate: boolean, ...args: T) => {
28
+ if (timer) {
29
+ clearTimeout(timer);
30
+ }
31
+ return new Promise<R>((resolve, reject) => {
32
+ if (immediate) {
33
+ func(...args)
34
+ .then(resolve)
35
+ .catch(reject);
36
+ } else {
37
+ timer = setTimeout(() => {
38
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-call
39
+ func(...args)
40
+ .then(resolve)
41
+ .catch(reject);
42
+ }, delay) as unknown as number;
43
+ }
44
+ });
45
+ };
46
+ }
47
+
22
48
  export class ListenerManager extends BaseListenerManager {
49
+ static LISTENER_UPDATE_DELAY = 250;
50
+
23
51
  async getTransmitter(
24
52
  driveId: string,
25
53
  listenerId: string
@@ -30,14 +58,6 @@ export class ListenerManager extends BaseListenerManager {
30
58
  async addListener(listener: Listener) {
31
59
  const drive = listener.driveId;
32
60
 
33
- const syncUnits = await this.drive.getSynchronizationUnits(drive);
34
- const filteredSyncUnits = [];
35
- for (const syncUnit of syncUnits) {
36
- if (this._checkFilter(listener.filter, syncUnit)) {
37
- filteredSyncUnits.push(syncUnit);
38
- }
39
- }
40
-
41
61
  if (!this.listenerState.has(drive)) {
42
62
  this.listenerState.set(drive, new Map());
43
63
  }
@@ -50,11 +70,7 @@ export class ListenerManager extends BaseListenerManager {
50
70
  pendingTimeout: '0',
51
71
  listener,
52
72
  listenerStatus: 'CREATED',
53
- syncUnits: filteredSyncUnits.map(e => ({
54
- ...e,
55
- listenerRev: -1,
56
- syncRev: e.revision
57
- }))
73
+ syncUnits: new Map()
58
74
  });
59
75
 
60
76
  let transmitter: ITransmitter | undefined;
@@ -101,17 +117,28 @@ export class ListenerManager extends BaseListenerManager {
101
117
  return driveMap.delete(listenerId);
102
118
  }
103
119
 
104
- async updateSynchronizationRevision(
120
+ async removeSyncUnits(driveId: string, syncUnits: SynchronizationUnit[]) {
121
+ const listeners = this.listenerState.get(driveId);
122
+ if (!listeners) {
123
+ return;
124
+ }
125
+ for (const [, listener] of listeners) {
126
+ for (const syncUnit of syncUnits) {
127
+ listener.syncUnits.delete(syncUnit.syncId);
128
+ }
129
+ }
130
+ }
131
+
132
+ async updateSynchronizationRevisions(
105
133
  driveId: string,
106
- syncId: string,
107
- syncRev: number,
108
- lastUpdated: string,
134
+ syncUnits: SynchronizationUnit[],
109
135
  willUpdate?: (listeners: Listener[]) => void,
110
136
  onError?: (
111
137
  error: Error,
112
138
  driveId: string,
113
139
  listener: ListenerState
114
- ) => void
140
+ ) => void,
141
+ forceSync = false
115
142
  ) {
116
143
  const drive = this.listenerState.get(driveId);
117
144
  if (!drive) {
@@ -120,100 +147,37 @@ export class ListenerManager extends BaseListenerManager {
120
147
 
121
148
  const outdatedListeners: Listener[] = [];
122
149
  for (const [, listener] of drive) {
123
- const syncUnits = listener.syncUnits.filter(
124
- e => e.syncId === syncId
125
- );
126
- if (listener.driveId !== driveId) {
150
+ if (
151
+ outdatedListeners.find(
152
+ l => l.listenerId === listener.listener.listenerId
153
+ )
154
+ ) {
127
155
  continue;
128
156
  }
129
-
130
157
  for (const syncUnit of syncUnits) {
131
- if (syncUnit.syncId !== syncId) {
158
+ if (!this._checkFilter(listener.listener.filter, syncUnit)) {
132
159
  continue;
133
160
  }
134
161
 
135
- syncUnit.syncRev = syncRev;
136
- syncUnit.lastUpdated = lastUpdated;
162
+ const listenerRev = listener.syncUnits.get(syncUnit.syncId);
163
+
137
164
  if (
138
- !outdatedListeners.find(
139
- l => l.listenerId === listener.listener.listenerId
140
- )
165
+ !listenerRev ||
166
+ listenerRev.listenerRev < syncUnit.revision
141
167
  ) {
142
168
  outdatedListeners.push(listener.listener);
169
+ break;
143
170
  }
144
171
  }
145
172
  }
146
173
 
147
174
  if (outdatedListeners.length) {
148
175
  willUpdate?.(outdatedListeners);
149
- return this.triggerUpdate(onError);
176
+ return this.triggerUpdate(forceSync, onError);
150
177
  }
151
178
  return [];
152
179
  }
153
180
 
154
- async addSyncUnits(syncUnits: SynchronizationUnit[]) {
155
- for (const [driveId, drive] of this.listenerState) {
156
- for (const [id, listenerState] of drive) {
157
- const transmitter = await this.getTransmitter(driveId, id);
158
- if (!transmitter) {
159
- continue;
160
- }
161
- const filteredSyncUnits = [];
162
- const { listener } = listenerState;
163
- for (const syncUnit of syncUnits) {
164
- if (!this._checkFilter(listener.filter, syncUnit)) {
165
- continue;
166
- }
167
- const existingSyncUnit = listenerState.syncUnits.find(
168
- unit => unit.syncId === syncUnit.syncId
169
- );
170
- if (existingSyncUnit) {
171
- existingSyncUnit.syncRev = syncUnit.revision;
172
- existingSyncUnit.lastUpdated = syncUnit.lastUpdated;
173
- } else {
174
- filteredSyncUnits.push(syncUnit);
175
- }
176
- }
177
-
178
- // TODO is this possible?
179
- if (!this.listenerState.has(driveId)) {
180
- this.listenerState.set(driveId, new Map());
181
- }
182
-
183
- const driveMap = this.listenerState.get(driveId)!;
184
-
185
- // TODO reuse existing state
186
- driveMap.set(listener.listenerId, {
187
- block: listener.block,
188
- driveId: listener.driveId,
189
- pendingTimeout: '0',
190
- listener,
191
- listenerStatus: 'CREATED',
192
- syncUnits: listenerState.syncUnits.concat(
193
- filteredSyncUnits.map(e => ({
194
- ...e,
195
- listenerRev: -1,
196
- syncRev: e.revision
197
- }))
198
- )
199
- });
200
- }
201
- }
202
- }
203
-
204
- removeSyncUnits(syncUnits: SynchronizationUnit[]) {
205
- for (const [driveId, drive] of this.listenerState) {
206
- const syncIds = syncUnits
207
- .filter(s => s.driveId === driveId)
208
- .map(s => s.syncId);
209
- for (const [, listenerState] of drive) {
210
- listenerState.syncUnits = listenerState.syncUnits.filter(
211
- s => !syncIds.includes(s.syncId)
212
- );
213
- }
214
- }
215
- }
216
-
217
181
  async updateListenerRevision(
218
182
  listenerId: string,
219
183
  driveId: string,
@@ -230,14 +194,22 @@ export class ListenerManager extends BaseListenerManager {
230
194
  return;
231
195
  }
232
196
 
233
- const entry = listener.syncUnits.find(s => s.syncId === syncId);
197
+ const lastUpdated = new Date().toISOString();
198
+ const entry = listener.syncUnits.get(syncId);
234
199
  if (entry) {
235
200
  entry.listenerRev = listenerRev;
236
- entry.lastUpdated = new Date().toISOString();
201
+ entry.lastUpdated = lastUpdated;
202
+ } else {
203
+ listener.syncUnits.set(syncId, { listenerRev, lastUpdated });
237
204
  }
238
205
  }
239
206
 
240
- async triggerUpdate(
207
+ triggerUpdate = debounce(
208
+ this._triggerUpdate.bind(this),
209
+ ListenerManager.LISTENER_UPDATE_DELAY
210
+ );
211
+
212
+ private async _triggerUpdate(
241
213
  onError?: (
242
214
  error: Error,
243
215
  driveId: string,
@@ -252,18 +224,19 @@ export class ListenerManager extends BaseListenerManager {
252
224
  continue;
253
225
  }
254
226
 
227
+ const syncUnits = await this.getListenerSyncUnits(
228
+ driveId,
229
+ listener.listener.listenerId
230
+ );
231
+
255
232
  const strandUpdates: StrandUpdate[] = [];
256
- for (const unit of listener.syncUnits) {
257
- const {
258
- syncRev,
259
- syncId,
260
- listenerRev,
261
- driveId,
262
- documentId,
263
- scope,
264
- branch
265
- } = unit;
266
- if (listenerRev >= syncRev) {
233
+ for (const syncUnit of syncUnits) {
234
+ const unitState = listener.syncUnits.get(syncUnit.syncId);
235
+
236
+ if (
237
+ unitState &&
238
+ unitState.listenerRev >= syncUnit.revision
239
+ ) {
267
240
  continue;
268
241
  }
269
242
 
@@ -271,9 +244,9 @@ export class ListenerManager extends BaseListenerManager {
271
244
  try {
272
245
  const data = await this.drive.getOperationData(
273
246
  driveId,
274
- syncId,
247
+ syncUnit.syncId,
275
248
  {
276
- fromRevision: listenerRev
249
+ fromRevision: unitState?.listenerRev
277
250
  }
278
251
  );
279
252
  opData.push(...data);
@@ -287,10 +260,10 @@ export class ListenerManager extends BaseListenerManager {
287
260
 
288
261
  strandUpdates.push({
289
262
  driveId,
290
- documentId,
291
- branch,
263
+ documentId: syncUnit.documentId,
264
+ branch: syncUnit.branch,
292
265
  operations: opData,
293
- scope: scope as OperationScope
266
+ scope: syncUnit.scope as OperationScope
294
267
  });
295
268
  }
296
269
 
@@ -311,15 +284,25 @@ export class ListenerManager extends BaseListenerManager {
311
284
  listener.pendingTimeout = '0';
312
285
  listener.listenerStatus = 'PENDING';
313
286
 
314
- for (const unit of listener.syncUnits) {
315
- const revision = listenerRevisions.find(
316
- e =>
317
- e.documentId === unit.documentId &&
318
- e.scope === unit.scope &&
319
- e.branch === unit.branch
287
+ const lastUpdated = new Date().toISOString();
288
+
289
+ for (const revision of listenerRevisions) {
290
+ const syncUnit = syncUnits.find(
291
+ unit =>
292
+ revision.documentId === unit.documentId &&
293
+ revision.scope === unit.scope &&
294
+ revision.branch === unit.branch
320
295
  );
321
- if (revision) {
322
- unit.listenerRev = revision.revision;
296
+ if (syncUnit) {
297
+ listener.syncUnits.set(syncUnit.syncId, {
298
+ lastUpdated,
299
+ listenerRev: revision.revision
300
+ });
301
+ } else {
302
+ console.warn(
303
+ `Received revision for untracked unit for listener ${listener.listener.listenerId}`,
304
+ revision
305
+ );
323
306
  }
324
307
  }
325
308
  const revisionError = listenerRevisions.find(
@@ -328,7 +311,9 @@ export class ListenerManager extends BaseListenerManager {
328
311
  if (revisionError) {
329
312
  throw new OperationError(
330
313
  revisionError.status as ErrorStatus,
331
- undefined
314
+ undefined,
315
+ revisionError.error,
316
+ revisionError.error
332
317
  );
333
318
  }
334
319
  listener.listenerStatus = 'SUCCESS';
@@ -372,6 +357,21 @@ export class ListenerManager extends BaseListenerManager {
372
357
  return false;
373
358
  }
374
359
 
360
+ getListenerSyncUnits(driveId: string, listenerId: string) {
361
+ const listener = this.listenerState.get(driveId)?.get(listenerId);
362
+ if (!listener) {
363
+ return [];
364
+ }
365
+ const filter = listener.listener.filter;
366
+ return this.drive.getSynchronizationUnits(
367
+ driveId,
368
+ filter.documentId ?? ['*'],
369
+ filter.scope ?? ['*'],
370
+ filter.branch ?? ['*'],
371
+ filter.documentType ?? ['*']
372
+ );
373
+ }
374
+
375
375
  async initDrive(drive: DocumentDriveDocument) {
376
376
  const {
377
377
  state: {
@@ -411,23 +411,26 @@ export class ListenerManager extends BaseListenerManager {
411
411
  since?: string
412
412
  ): Promise<StrandUpdate[]> {
413
413
  // fetch listenerState from listenerManager
414
- const entries = await this.getListener(driveId, listenerId);
414
+ const listener = await this.getListener(driveId, listenerId);
415
415
 
416
416
  // fetch operations from drive and prepare strands
417
417
  const strands: StrandUpdate[] = [];
418
418
 
419
- for (const entry of entries.syncUnits) {
420
- if (entry.listenerRev >= entry.syncRev) {
419
+ const syncUnits = await this.getListenerSyncUnits(driveId, listenerId);
420
+
421
+ for (const syncUnit of syncUnits) {
422
+ const entry = listener.syncUnits.get(syncUnit.syncId);
423
+ if (entry && entry.listenerRev >= syncUnit.revision) {
421
424
  continue;
422
425
  }
423
426
 
424
- const { documentId, driveId, scope, branch } = entry;
427
+ const { documentId, driveId, scope, branch } = syncUnit;
425
428
  const operations = await this.drive.getOperationData(
426
- entry.driveId,
427
- entry.syncId,
429
+ driveId,
430
+ syncUnit.syncId,
428
431
  {
429
432
  since,
430
- fromRevision: entry.listenerRev
433
+ fromRevision: entry?.listenerRev
431
434
  }
432
435
  );
433
436
 
@@ -71,11 +71,13 @@ export class PullResponderTransmitter implements IPullResponderTransmitter {
71
71
  listenerId: string,
72
72
  revisions: ListenerRevision[]
73
73
  ): Promise<boolean> {
74
- const listener = await this.manager.getListener(driveId, listenerId);
75
-
74
+ const syncUnits = await this.manager.getListenerSyncUnits(
75
+ driveId,
76
+ listenerId
77
+ );
76
78
  let success = true;
77
79
  for (const revision of revisions) {
78
- const syncUnit = listener.syncUnits.find(
80
+ const syncUnit = syncUnits.find(
79
81
  s =>
80
82
  s.scope === revision.scope &&
81
83
  s.branch === revision.branch &&
@@ -83,7 +85,7 @@ export class PullResponderTransmitter implements IPullResponderTransmitter {
83
85
  s.documentId == revision.documentId
84
86
  );
85
87
  if (!syncUnit) {
86
- console.log('Sync unit not found', revision);
88
+ console.warn('Unknown sync unit was acknowledged', revision);
87
89
  success = false;
88
90
  continue;
89
91
  }
@@ -254,11 +256,6 @@ export class PullResponderTransmitter implements IPullResponderTransmitter {
254
256
  : 'SUCCESS',
255
257
  error
256
258
  });
257
-
258
- // TODO: Should try to parse remaining strands?
259
- // if (error) {
260
- // break;
261
- // }
262
259
  }
263
260
 
264
261
  onRevisions?.(listenerRevisions);
@@ -35,6 +35,7 @@ export class SwitchboardPushTransmitter implements ITransmitter {
35
35
  branch
36
36
  status
37
37
  revision
38
+ error
38
39
  }
39
40
  }
40
41
  `,
@@ -92,9 +92,12 @@ export type ListenerRevision = {
92
92
  branch: string;
93
93
  status: UpdateStatus;
94
94
  revision: number;
95
+ error?: string;
95
96
  };
96
97
 
97
- export type ListenerRevisionWithError = ListenerRevision & { error?: Error };
98
+ export type ListenerRevisionWithError = Omit<ListenerRevision, 'error'> & {
99
+ error?: Error;
100
+ };
98
101
 
99
102
  export type ListenerUpdate = {
100
103
  listenerId: string;
@@ -163,12 +166,14 @@ export abstract class BaseDocumentDriveServer {
163
166
  abstract addOperation(
164
167
  drive: string,
165
168
  id: string,
166
- operation: Operation
169
+ operation: Operation,
170
+ forceSync?: boolean
167
171
  ): Promise<IOperationResult>;
168
172
  abstract addOperations(
169
173
  drive: string,
170
174
  id: string,
171
- operations: Operation[]
175
+ operations: Operation[],
176
+ forceSync?: boolean
172
177
  ): Promise<IOperationResult>;
173
178
 
174
179
  abstract addDriveOperation(
@@ -207,7 +212,8 @@ export abstract class BaseDocumentDriveServer {
207
212
  driveId: string,
208
213
  documentId?: string[],
209
214
  scope?: string[],
210
- branch?: string[]
215
+ branch?: string[],
216
+ documentType?: string[]
211
217
  ): Promise<SynchronizationUnit[]>;
212
218
 
213
219
  abstract getSynchronizationUnit(
@@ -289,11 +295,9 @@ export abstract class BaseListenerManager {
289
295
  since?: string
290
296
  ): Promise<StrandUpdate[]>;
291
297
 
292
- abstract updateSynchronizationRevision(
298
+ abstract updateSynchronizationRevisions(
293
299
  driveId: string,
294
- syncId: string,
295
- syncRev: number,
296
- lastUpdated: string,
300
+ syncUnits: SynchronizationUnit[],
297
301
  willUpdate?: (listeners: Listener[]) => void,
298
302
  onError?: (
299
303
  error: Error,
@@ -328,11 +332,11 @@ export interface ListenerState {
328
332
  block: boolean;
329
333
  pendingTimeout: string;
330
334
  listener: Listener;
331
- syncUnits: SyncronizationUnitState[];
335
+ syncUnits: Map<SynchronizationUnit['syncId'], SyncronizationUnitState>;
332
336
  listenerStatus: ListenerStatus;
333
337
  }
334
338
 
335
- export interface SyncronizationUnitState extends SynchronizationUnit {
339
+ export interface SyncronizationUnitState {
336
340
  listenerRev: number;
337
- syncRev: number;
341
+ lastUpdated: string;
338
342
  }