document-drive 1.17.2 → 1.19.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.
@@ -1,5 +1,5 @@
1
1
  import { ListenerFilter, Trigger } from "document-model-libs/document-drive";
2
- import { Operation, OperationScope } from "document-model/document";
2
+ import { Operation } from "document-model/document";
3
3
  import { PULL_DRIVE_INTERVAL } from "../..";
4
4
  import { generateUUID } from "../../../utils";
5
5
  import { gql, requestGraphql } from "../../../utils/graphql";
@@ -7,7 +7,7 @@ import { logger as defaultLogger } from "../../../utils/logger";
7
7
  import { OperationError } from "../../error";
8
8
  import {
9
9
  GetStrandsOptions,
10
- IBaseDocumentDriveServer,
10
+ IListenerManager,
11
11
  IOperationResult,
12
12
  Listener,
13
13
  ListenerRevision,
@@ -16,13 +16,14 @@ import {
16
16
  RemoteDriveOptions,
17
17
  StrandUpdate,
18
18
  } from "../../types";
19
- import { ListenerManager } from "../manager";
20
19
  import {
21
20
  ITransmitter,
22
21
  PullResponderTrigger,
23
22
  StrandUpdateSource,
24
23
  } from "./types";
25
24
 
25
+ const ENABLE_SYNC_DEBUG = false;
26
+
26
27
  export type OperationUpdateGraphQL = Omit<OperationUpdate, "input"> & {
27
28
  input: string;
28
29
  };
@@ -45,22 +46,52 @@ export interface IPullResponderTransmitter extends ITransmitter {
45
46
  getStrands(options?: GetStrandsOptions): Promise<StrandUpdate[]>;
46
47
  }
47
48
 
49
+ const STATIC_DEBUG_ID = `[PRT #static]`;
50
+
51
+ function staticDebugLog(...data: any[]) {
52
+ if (!ENABLE_SYNC_DEBUG) {
53
+ return;
54
+ }
55
+
56
+ if (data.length > 0 && typeof data[0] === "string") {
57
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
58
+ console.log(`${STATIC_DEBUG_ID} ${data[0]}`, ...data.slice(1));
59
+ } else {
60
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
61
+ console.log(STATIC_DEBUG_ID, ...data);
62
+ }
63
+ }
64
+
48
65
  export class PullResponderTransmitter implements IPullResponderTransmitter {
49
- private drive: IBaseDocumentDriveServer;
66
+ private debugID = `[PRT #${Math.floor(Math.random() * 999)}]`;
50
67
  private listener: Listener;
51
- private manager: ListenerManager;
68
+ private manager: IListenerManager;
52
69
 
53
- constructor(
54
- listener: Listener,
55
- drive: IBaseDocumentDriveServer,
56
- manager: ListenerManager,
57
- ) {
70
+ constructor(listener: Listener, manager: IListenerManager) {
58
71
  this.listener = listener;
59
- this.drive = drive;
60
72
  this.manager = manager;
73
+ this.debugLog(`constructor(listener: ${listener.listenerId})`);
74
+ }
75
+
76
+ private debugLog(...data: any[]) {
77
+ if (!ENABLE_SYNC_DEBUG) {
78
+ return;
79
+ }
80
+
81
+ if (data.length > 0 && typeof data[0] === "string") {
82
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
83
+ console.log(`${this.debugID} ${data[0]}`, ...data.slice(1));
84
+ } else {
85
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
86
+ console.log(this.debugID, ...data);
87
+ }
61
88
  }
62
89
 
63
90
  getStrands(options?: GetStrandsOptions): Promise<StrandUpdate[]> {
91
+ this.debugLog(
92
+ `getStrands(drive: ${this.listener.driveId}, listener: ${this.listener.listenerId})`,
93
+ );
94
+
64
95
  return this.manager.getStrands(
65
96
  this.listener.driveId,
66
97
  this.listener.listenerId,
@@ -78,6 +109,11 @@ export class PullResponderTransmitter implements IPullResponderTransmitter {
78
109
  listenerId: string,
79
110
  revisions: ListenerRevision[],
80
111
  ): Promise<boolean> {
112
+ this.debugLog(
113
+ `processAcknowledge(drive: ${driveId}, listener: ${listenerId})`,
114
+ revisions,
115
+ );
116
+
81
117
  const syncUnits = await this.manager.getListenerSyncUnitIds(
82
118
  driveId,
83
119
  listenerId,
@@ -113,6 +149,7 @@ export class PullResponderTransmitter implements IPullResponderTransmitter {
113
149
  url: string,
114
150
  filter: ListenerFilter,
115
151
  ): Promise<Listener["listenerId"]> {
152
+ staticDebugLog(`registerPullResponder(url: ${url})`, filter);
116
153
  // graphql request to switchboard
117
154
  const result = await requestGraphql<{
118
155
  registerPullResponderListener: {
@@ -148,6 +185,7 @@ export class PullResponderTransmitter implements IPullResponderTransmitter {
148
185
  listenerId: string,
149
186
  options?: GetStrandsOptions, // TODO add support for since
150
187
  ): Promise<StrandUpdate[]> {
188
+ staticDebugLog(`pullStrands(url: ${url}, listener: ${listenerId})`);
151
189
  const result = await requestGraphql<PullStrandsGraphQL>(
152
190
  url,
153
191
  gql`
@@ -214,6 +252,11 @@ export class PullResponderTransmitter implements IPullResponderTransmitter {
214
252
  listenerId: string,
215
253
  revisions: ListenerRevision[],
216
254
  ): Promise<boolean> {
255
+ staticDebugLog(
256
+ `acknowledgeStrands(url: ${url}, listener: ${listenerId})`,
257
+ revisions,
258
+ );
259
+
217
260
  const result = await requestGraphql<{ acknowledge: boolean }>(
218
261
  url,
219
262
  gql`
@@ -248,6 +291,8 @@ export class PullResponderTransmitter implements IPullResponderTransmitter {
248
291
  onRevisions?: (revisions: ListenerRevisionWithError[]) => void,
249
292
  onAcknowledge?: (success: boolean) => void,
250
293
  ) {
294
+ staticDebugLog(`executePull(driveId: ${driveId}), trigger:`, trigger);
295
+
251
296
  try {
252
297
  const { url, listenerId } = trigger.data;
253
298
  const strands = await PullResponderTransmitter.pullStrands(
@@ -330,6 +375,8 @@ export class PullResponderTransmitter implements IPullResponderTransmitter {
330
375
  onRevisions?: (revisions: ListenerRevisionWithError[]) => void,
331
376
  onAcknowledge?: (success: boolean) => void,
332
377
  ): CancelPullLoop {
378
+ staticDebugLog(`setupPull(drive: ${driveId}), trigger:`, trigger);
379
+
333
380
  const { interval } = trigger.data;
334
381
  let loopInterval = PULL_DRIVE_INTERVAL;
335
382
  if (interval) {
@@ -348,6 +395,7 @@ export class PullResponderTransmitter implements IPullResponderTransmitter {
348
395
 
349
396
  const executeLoop = async () => {
350
397
  while (!isCancelled) {
398
+ staticDebugLog("Execute loop...");
351
399
  await this.executePull(
352
400
  driveId,
353
401
  trigger,
@@ -357,6 +405,7 @@ export class PullResponderTransmitter implements IPullResponderTransmitter {
357
405
  onAcknowledge,
358
406
  );
359
407
  await new Promise((resolve) => {
408
+ staticDebugLog(`Scheduling next pull in ${loopInterval} ms`);
360
409
  timeout = setTimeout(resolve, loopInterval) as unknown as number;
361
410
  });
362
411
  }
@@ -377,6 +426,10 @@ export class PullResponderTransmitter implements IPullResponderTransmitter {
377
426
  url: string,
378
427
  options: Pick<RemoteDriveOptions, "pullInterval" | "pullFilter">,
379
428
  ): Promise<PullResponderTrigger> {
429
+ staticDebugLog(
430
+ `createPullResponderTrigger(drive: ${driveId}, url: ${url})`,
431
+ );
432
+
380
433
  const { pullFilter, pullInterval } = options;
381
434
  const listenerId = await PullResponderTransmitter.registerPullResponder(
382
435
  driveId,
@@ -1,23 +1,32 @@
1
1
  import stringify from "json-stringify-deterministic";
2
2
  import { gql, requestGraphql } from "../../../utils/graphql";
3
3
  import { logger } from "../../../utils/logger";
4
- import {
5
- IBaseDocumentDriveServer,
6
- Listener,
7
- ListenerRevision,
8
- StrandUpdate,
9
- } from "../../types";
4
+ import { ListenerRevision, StrandUpdate } from "../../types";
10
5
  import { ITransmitter, StrandUpdateSource } from "./types";
11
6
 
7
+ const ENABLE_SYNC_DEBUG = false;
8
+ const SYNC_OPS_BATCH_LIMIT = 10;
9
+
12
10
  export class SwitchboardPushTransmitter implements ITransmitter {
13
- private drive: IBaseDocumentDriveServer;
14
- private listener: Listener;
15
11
  private targetURL: string;
12
+ private debugID = `[SPT #${Math.floor(Math.random() * 999)}]`;
13
+
14
+ constructor(targetURL: string) {
15
+ this.targetURL = targetURL;
16
+ }
17
+
18
+ private debugLog(...data: any[]) {
19
+ if (!ENABLE_SYNC_DEBUG) {
20
+ return false;
21
+ }
16
22
 
17
- constructor(listener: Listener, drive: IBaseDocumentDriveServer) {
18
- this.listener = listener;
19
- this.drive = drive;
20
- this.targetURL = listener.callInfo!.data!;
23
+ if (data.length > 0 && typeof data[0] === "string") {
24
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
25
+ console.log(`${this.debugID} ${data[0]}`, ...data.slice(1));
26
+ } else {
27
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
28
+ console.log(this.debugID, ...data);
29
+ }
21
30
  }
22
31
 
23
32
  async transmit(
@@ -28,6 +37,7 @@ export class SwitchboardPushTransmitter implements ITransmitter {
28
37
  source.type === "trigger" &&
29
38
  source.trigger.data?.url === this.targetURL
30
39
  ) {
40
+ this.debugLog(`Cutting trigger loop from ${this.targetURL}.`);
31
41
  return strands.map((strand) => ({
32
42
  driveId: strand.driveId,
33
43
  documentId: strand.documentId,
@@ -38,6 +48,39 @@ export class SwitchboardPushTransmitter implements ITransmitter {
38
48
  }));
39
49
  }
40
50
 
51
+ const culledStrands: StrandUpdate[] = [];
52
+ let opsCounter = 0;
53
+
54
+ for (
55
+ let s = 0;
56
+ opsCounter <= SYNC_OPS_BATCH_LIMIT && s < strands.length;
57
+ s++
58
+ ) {
59
+ const currentStrand = strands.at(s);
60
+ if (!currentStrand) {
61
+ break;
62
+ }
63
+ const newOps = Math.min(
64
+ SYNC_OPS_BATCH_LIMIT - opsCounter,
65
+ currentStrand.operations.length,
66
+ );
67
+
68
+ culledStrands.push({
69
+ ...currentStrand,
70
+ operations: currentStrand.operations.slice(0, newOps),
71
+ });
72
+
73
+ opsCounter += newOps;
74
+ }
75
+
76
+ this.debugLog(
77
+ ` Total update: [${strands.map((s) => s.operations.length).join(", ")}] operations`,
78
+ );
79
+
80
+ this.debugLog(
81
+ `Culled update: [${culledStrands.map((s) => s.operations.length).join(", ")}] operations`,
82
+ );
83
+
41
84
  // Send Graphql mutation to switchboard
42
85
  try {
43
86
  const { pushUpdates } = await requestGraphql<{
@@ -58,7 +101,7 @@ export class SwitchboardPushTransmitter implements ITransmitter {
58
101
  }
59
102
  `,
60
103
  {
61
- strands: strands.map((strand) => ({
104
+ strands: culledStrands.map((strand) => ({
62
105
  ...strand,
63
106
  operations: strand.operations.map((op) => ({
64
107
  ...op,
@@ -22,15 +22,15 @@ import type {
22
22
  } from "document-model/document";
23
23
  import { Unsubscribe } from "nanoevents";
24
24
  import { BaseDocumentDriveServer } from ".";
25
- import {
26
- IReceiver as IInternalListener,
27
- IInternalTransmitter,
28
- } from "./listener/transmitter/internal";
29
25
  import { IReadModeDriveServer } from "../read-mode/types";
30
26
  import { RunAsap } from "../utils";
31
27
  import { IDefaultDrivesManager } from "../utils/default-drives-manager";
32
28
  import { DriveInfo } from "../utils/graphql";
33
29
  import { OperationError, SynchronizationUnitNotFoundError } from "./error";
30
+ import {
31
+ IReceiver as IInternalListener,
32
+ IInternalTransmitter,
33
+ } from "./listener/transmitter/internal";
34
34
  import {
35
35
  ITransmitter,
36
36
  PullResponderTrigger,
@@ -105,6 +105,7 @@ export type Listener = {
105
105
  system: boolean;
106
106
  filter: ListenerFilter;
107
107
  callInfo?: ListenerCallInfo;
108
+ transmitter?: ITransmitter;
108
109
  };
109
110
 
110
111
  export type CreateListenerInput = {
@@ -317,137 +318,150 @@ export type GetStrandsOptions = {
317
318
  fromRevision?: number;
318
319
  };
319
320
 
320
- export abstract class AbstractDocumentDriveServer {
321
- /** Public methods **/
322
- abstract initialize(): Promise<Error[] | null>;
323
- abstract setDocumentModels(models: DocumentModel[]): void;
324
- abstract getDrives(): Promise<string[]>;
325
- abstract addDrive(drive: DriveInput): Promise<DocumentDriveDocument>;
326
- abstract addRemoteDrive(
321
+ export type ListenerManagerOptions = {
322
+ sequentialUpdates?: boolean;
323
+ };
324
+
325
+ export const DefaultListenerManagerOptions = {
326
+ sequentialUpdates: true,
327
+ };
328
+
329
+ type PublicKeys<T> = {
330
+ [K in keyof T]: T extends { [P in K]: T[K] } ? K : never;
331
+ }[keyof T];
332
+
333
+ type PublicPart<T> = Pick<T, PublicKeys<T>>;
334
+
335
+ export interface IBaseDocumentDriveServer {
336
+ initialize(): Promise<Error[] | null>;
337
+ setDocumentModels(models: DocumentModel[]): void;
338
+ getDrives(): Promise<string[]>;
339
+ addDrive(input: DriveInput): Promise<DocumentDriveDocument>;
340
+ addRemoteDrive(
327
341
  url: string,
328
342
  options: RemoteDriveOptions,
329
343
  ): Promise<DocumentDriveDocument>;
330
- abstract deleteDrive(id: string): Promise<void>;
331
- abstract getDrive(
332
- id: string,
344
+ deleteDrive(driveId: string): Promise<void>;
345
+ getDrive(
346
+ driveId: string,
333
347
  options?: GetDocumentOptions,
334
348
  ): Promise<DocumentDriveDocument>;
335
349
 
336
- abstract getDriveBySlug(slug: string): Promise<DocumentDriveDocument>;
350
+ getDriveBySlug(slug: string): Promise<DocumentDriveDocument>;
337
351
 
338
- abstract getDocuments(drive: string): Promise<string[]>;
339
- abstract getDocument(
340
- drive: string,
341
- id: string,
352
+ getDocuments(driveId: string): Promise<string[]>;
353
+ getDocument(
354
+ driveId: string,
355
+ documentId: string,
342
356
  options?: GetDocumentOptions,
343
357
  ): Promise<Document>;
344
358
 
345
- abstract addOperation(
346
- drive: string,
347
- id: string,
359
+ addOperation(
360
+ driveId: string,
361
+ documentId: string,
348
362
  operation: Operation,
349
363
  options?: AddOperationOptions,
350
364
  ): Promise<IOperationResult>;
351
365
 
352
- abstract addOperations(
353
- drive: string,
354
- id: string,
366
+ addOperations(
367
+ driveId: string,
368
+ documentId: string,
355
369
  operations: Operation[],
356
370
  options?: AddOperationOptions,
357
371
  ): Promise<IOperationResult>;
358
372
 
359
- abstract queueOperation(
360
- drive: string,
361
- id: string,
373
+ queueOperation(
374
+ driveId: string,
375
+ documentId: string,
362
376
  operation: Operation,
363
377
  options?: AddOperationOptions,
364
378
  ): Promise<IOperationResult>;
365
379
 
366
- abstract queueOperations(
367
- drive: string,
368
- id: string,
380
+ queueOperations(
381
+ driveId: string,
382
+ documentId: string,
369
383
  operations: Operation[],
370
384
  options?: AddOperationOptions,
371
385
  ): Promise<IOperationResult>;
372
386
 
373
- abstract queueAction(
374
- drive: string,
375
- id: string,
387
+ queueAction(
388
+ driveId: string,
389
+ documentId: string,
376
390
  action: Action,
377
391
  options?: AddOperationOptions,
378
392
  ): Promise<IOperationResult>;
379
393
 
380
- abstract queueActions(
381
- drive: string,
382
- id: string,
394
+ queueActions(
395
+ driveId: string,
396
+ documentId: string,
383
397
  actions: Action[],
384
398
  options?: AddOperationOptions,
385
399
  ): Promise<IOperationResult>;
386
400
 
387
- abstract addDriveOperation(
388
- drive: string,
401
+ addDriveOperation(
402
+ driveId: string,
389
403
  operation: Operation<DocumentDriveAction | BaseAction>,
390
404
  options?: AddOperationOptions,
391
405
  ): Promise<IOperationResult<DocumentDriveDocument>>;
392
- abstract addDriveOperations(
393
- drive: string,
406
+ addDriveOperations(
407
+ driveId: string,
394
408
  operations: Operation<DocumentDriveAction | BaseAction>[],
395
409
  options?: AddOperationOptions,
396
410
  ): Promise<IOperationResult<DocumentDriveDocument>>;
397
411
 
398
- abstract queueDriveOperation(
399
- drive: string,
412
+ queueDriveOperation(
413
+ driveId: string,
400
414
  operation: Operation<DocumentDriveAction | BaseAction>,
401
415
  options?: AddOperationOptions,
402
416
  ): Promise<IOperationResult<DocumentDriveDocument>>;
403
417
 
404
- abstract queueDriveOperations(
405
- drive: string,
418
+ queueDriveOperations(
419
+ driveId: string,
406
420
  operations: Operation<DocumentDriveAction | BaseAction>[],
407
421
  options?: AddOperationOptions,
408
422
  ): Promise<IOperationResult<DocumentDriveDocument>>;
409
423
 
410
- abstract queueDriveAction(
411
- drive: string,
424
+ queueDriveAction(
425
+ driveId: string,
412
426
  action: DocumentDriveAction | BaseAction,
413
427
  options?: AddOperationOptions,
414
428
  ): Promise<IOperationResult<DocumentDriveDocument>>;
415
429
 
416
- abstract queueDriveActions(
417
- drive: string,
430
+ queueDriveActions(
431
+ driveId: string,
418
432
  actions: Array<DocumentDriveAction | BaseAction>,
419
433
  options?: AddOperationOptions,
420
434
  ): Promise<IOperationResult<DocumentDriveDocument>>;
421
435
 
422
- abstract addAction(
423
- drive: string,
424
- id: string,
436
+ addAction(
437
+ driveId: string,
438
+ documentId: string,
425
439
  action: Action,
426
440
  options?: AddOperationOptions,
427
441
  ): Promise<IOperationResult>;
428
- abstract addActions(
429
- drive: string,
430
- id: string,
442
+ addActions(
443
+ driveId: string,
444
+ documentId: string,
431
445
  actions: Action[],
432
446
  options?: AddOperationOptions,
433
447
  ): Promise<IOperationResult>;
434
448
 
435
- abstract addDriveAction(
436
- drive: string,
449
+ addDriveAction(
450
+ driveId: string,
437
451
  action: DocumentDriveAction | BaseAction,
438
452
  options?: AddOperationOptions,
439
453
  ): Promise<IOperationResult<DocumentDriveDocument>>;
440
- abstract addDriveActions(
441
- drive: string,
454
+ addDriveActions(
455
+ driveId: string,
442
456
  actions: (DocumentDriveAction | BaseAction)[],
443
457
  options?: AddOperationOptions,
444
458
  ): Promise<IOperationResult<DocumentDriveDocument>>;
445
459
 
446
- abstract getSyncStatus(
460
+ getSyncStatus(
447
461
  syncUnitId: string,
448
462
  ): SyncStatus | SynchronizationUnitNotFoundError;
449
463
 
450
- abstract addInternalListener(
464
+ addInternalListener(
451
465
  driveId: string,
452
466
  receiver: IInternalListener,
453
467
  options: {
@@ -459,7 +473,7 @@ export abstract class AbstractDocumentDriveServer {
459
473
  ): Promise<IInternalTransmitter>;
460
474
 
461
475
  /** Synchronization methods */
462
- abstract getSynchronizationUnits(
476
+ getSynchronizationUnits(
463
477
  driveId: string,
464
478
  documentId?: string[],
465
479
  scope?: string[],
@@ -468,13 +482,13 @@ export abstract class AbstractDocumentDriveServer {
468
482
  loadedDrive?: DocumentDriveDocument,
469
483
  ): Promise<SynchronizationUnit[]>;
470
484
 
471
- abstract getSynchronizationUnit(
485
+ getSynchronizationUnit(
472
486
  driveId: string,
473
487
  syncId: string,
474
488
  loadedDrive?: DocumentDriveDocument,
475
489
  ): Promise<SynchronizationUnit | undefined>;
476
490
 
477
- abstract getSynchronizationUnitsIds(
491
+ getSynchronizationUnitsIds(
478
492
  driveId: string,
479
493
  documentId?: string[],
480
494
  scope?: string[],
@@ -482,7 +496,7 @@ export abstract class AbstractDocumentDriveServer {
482
496
  documentType?: string[],
483
497
  ): Promise<SynchronizationUnitQuery[]>;
484
498
 
485
- abstract getOperationData(
499
+ getOperationData(
486
500
  driveId: string,
487
501
  syncId: string,
488
502
  filter: GetStrandsOptions,
@@ -490,117 +504,73 @@ export abstract class AbstractDocumentDriveServer {
490
504
  ): Promise<OperationUpdate[]>;
491
505
 
492
506
  /** Internal methods **/
493
- protected abstract createDocument(
494
- drive: string,
495
- document: CreateDocumentInput,
496
- ): Promise<Document>;
497
- protected abstract deleteDocument(drive: string, id: string): Promise<void>;
498
-
499
- protected abstract getDocumentModel(documentType: string): DocumentModel;
500
- abstract getDocumentModels(): DocumentModel[];
501
-
502
- /** Event methods **/
503
- protected abstract emit<K extends keyof DriveEvents>(
504
- event: K,
505
- ...args: Parameters<DriveEvents[K]>
506
- ): void;
507
- abstract on<K extends keyof DriveEvents>(
508
- event: K,
509
- cb: DriveEvents[K],
510
- ): Unsubscribe;
511
-
512
- abstract getTransmitter(
507
+ getDocumentModels(): DocumentModel[];
508
+
509
+ getTransmitter(
513
510
  driveId: string,
514
511
  listenerId: string,
515
512
  ): Promise<ITransmitter | undefined>;
516
513
 
517
- abstract clearStorage(): Promise<void>;
514
+ clearStorage(): Promise<void>;
518
515
 
519
- abstract registerPullResponderTrigger(
516
+ registerPullResponderTrigger(
520
517
  id: string,
521
518
  url: string,
522
519
  options: Pick<RemoteDriveOptions, "pullFilter" | "pullInterval">,
523
520
  ): Promise<PullResponderTrigger>;
524
- }
525
-
526
- export type ListenerManagerOptions = {
527
- sequentialUpdates?: boolean;
528
- };
529
-
530
- export const DefaultListenerManagerOptions = {
531
- sequentialUpdates: true,
532
- };
533
-
534
- type PublicKeys<T> = {
535
- [K in keyof T]: T extends { [P in K]: T[K] } ? K : never;
536
- }[keyof T];
537
-
538
- type PublicPart<T> = Pick<T, PublicKeys<T>>;
539
521
 
540
- export type IBaseDocumentDriveServer = PublicPart<AbstractDocumentDriveServer>;
522
+ on<K extends keyof DriveEvents>(event: K, cb: DriveEvents[K]): Unsubscribe;
523
+ }
541
524
 
542
525
  export type IDocumentDriveServer = IBaseDocumentDriveServer &
543
526
  IDefaultDrivesManager &
544
527
  IReadModeDriveServer;
545
528
 
546
- export abstract class BaseListenerManager {
547
- protected drive: IBaseDocumentDriveServer;
548
- protected listenerState = new Map<string, Map<string, ListenerState>>();
549
- protected options: ListenerManagerOptions;
550
- protected transmitters: Record<
551
- DocumentDriveState["id"],
552
- Record<Listener["listenerId"], ITransmitter>
553
- > = {};
554
-
555
- constructor(
556
- drive: IBaseDocumentDriveServer,
557
- listenerState = new Map<string, Map<string, ListenerState>>(),
558
- options: ListenerManagerOptions = DefaultListenerManagerOptions,
559
- ) {
560
- this.drive = drive;
561
- this.listenerState = listenerState;
562
- this.options = { ...DefaultListenerManagerOptions, ...options };
563
- }
564
-
565
- abstract initDrive(drive: DocumentDriveDocument): Promise<void>;
566
- abstract removeDrive(driveId: DocumentDriveState["id"]): Promise<void>;
567
-
568
- abstract driveHasListeners(driveId: string): boolean;
569
- abstract addListener(listener: Listener): Promise<ITransmitter>;
570
- abstract removeListener(
571
- driveId: string,
572
- listenerId: string,
573
- ): Promise<boolean>;
574
- abstract getListener(
575
- driveId: string,
576
- listenerId: string,
577
- ): Promise<ListenerState | undefined>;
529
+ export type DriveUpdateErrorHandler = (
530
+ error: Error,
531
+ driveId: string,
532
+ listener: ListenerState,
533
+ ) => void;
578
534
 
579
- abstract getTransmitter(
580
- driveId: string,
581
- listenerId: string,
582
- ): Promise<ITransmitter | undefined>;
535
+ export interface IListenerManager {
536
+ initialize(handler: DriveUpdateErrorHandler): Promise<void>;
583
537
 
584
- abstract getStrands(
538
+ removeDrive(driveId: DocumentDriveState["id"]): Promise<void>;
539
+ driveHasListeners(driveId: string): boolean;
540
+
541
+ setListener(driveId: string, listener: Listener): Promise<void>;
542
+ removeListener(driveId: string, listenerId: string): Promise<boolean>;
543
+ getListenerState(driveId: string, listenerId: string): ListenerState;
544
+
545
+ getStrands(
585
546
  driveId: string,
586
547
  listenerId: string,
587
548
  options?: GetStrandsOptions,
588
549
  ): Promise<StrandUpdate[]>;
589
-
590
- abstract updateSynchronizationRevisions(
550
+ updateSynchronizationRevisions(
591
551
  driveId: string,
592
552
  syncUnits: SynchronizationUnit[],
593
553
  source: StrandUpdateSource,
594
554
  willUpdate?: (listeners: Listener[]) => void,
595
555
  onError?: (error: Error, driveId: string, listener: ListenerState) => void,
556
+ forceSync?: boolean,
596
557
  ): Promise<ListenerUpdate[]>;
597
-
598
- abstract updateListenerRevision(
558
+ updateListenerRevision(
599
559
  listenerId: string,
600
560
  driveId: string,
601
561
  syncId: string,
602
562
  listenerRev: number,
603
563
  ): Promise<void>;
564
+
565
+ // todo: re-evaluate
566
+ getListenerSyncUnitIds(
567
+ driveId: string,
568
+ listenerId: string,
569
+ ): Promise<SynchronizationUnitQuery[]>;
570
+ removeSyncUnits(
571
+ driveId: string,
572
+ syncUnits: Pick<SynchronizationUnit, "syncId">[],
573
+ ): Promise<void>;
604
574
  }
605
575
 
606
576
  export type ListenerStatus =