document-drive 1.0.0-alpha.75 → 1.0.0-alpha.78

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/README.md ADDED
@@ -0,0 +1 @@
1
+ # Document Drive
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "document-drive",
3
- "version": "1.0.0-alpha.75",
3
+ "version": "1.0.0-alpha.78",
4
4
  "license": "AGPL-3.0-only",
5
5
  "type": "module",
6
6
  "module": "./src/index.ts",
@@ -1,12 +1,12 @@
1
1
  import { Action, Operation } from "document-model/document";
2
- import { IOperationResult } from "../server";
2
+ import { AddOperationOptions, IOperationResult } from "../server";
3
3
  import type { Unsubscribe } from "nanoevents";
4
4
 
5
5
  export interface BaseJob {
6
6
  driveId: string;
7
7
  documentId?: string
8
8
  actions?: Action[]
9
- forceSync?: boolean
9
+ options?: AddOperationOptions;
10
10
  }
11
11
 
12
12
  export interface OperationJob extends BaseJob {
@@ -60,9 +60,11 @@ import {
60
60
  InternalTransmitter,
61
61
  IReceiver,
62
62
  ITransmitter,
63
- PullResponderTransmitter
63
+ PullResponderTransmitter,
64
+ StrandUpdateSource
64
65
  } from './listener/transmitter';
65
66
  import {
67
+ AddOperationOptions,
66
68
  BaseDocumentDriveServer,
67
69
  DriveEvents,
68
70
  GetDocumentOptions,
@@ -138,7 +140,7 @@ export class DocumentDriveServer extends BaseDocumentDriveServer {
138
140
  }
139
141
  }
140
142
 
141
- private async saveStrand(strand: StrandUpdate) {
143
+ private async saveStrand(strand: StrandUpdate, source: StrandUpdateSource) {
142
144
  const operations: Operation[] = strand.operations.map(op => ({
143
145
  ...op,
144
146
  scope: strand.scope,
@@ -149,13 +151,13 @@ export class DocumentDriveServer extends BaseDocumentDriveServer {
149
151
  ? this.queueDriveOperations(
150
152
  strand.driveId,
151
153
  operations as Operation<DocumentDriveAction | BaseAction>[],
152
- false
154
+ { source }
153
155
  )
154
156
  : this.queueOperations(
155
157
  strand.driveId,
156
158
  strand.documentId,
157
159
  operations,
158
- false
160
+ { source }
159
161
  ));
160
162
 
161
163
  if (result.status === 'ERROR') {
@@ -283,30 +285,30 @@ export class DocumentDriveServer extends BaseDocumentDriveServer {
283
285
  driveId,
284
286
  documentId,
285
287
  operations,
286
- forceSync
288
+ options
287
289
  }: OperationJob) => {
288
290
  return documentId
289
- ? this.addOperations(driveId, documentId, operations, forceSync)
291
+ ? this.addOperations(driveId, documentId, operations, options)
290
292
  : this.addDriveOperations(
291
293
  driveId,
292
294
  operations as Operation<
293
295
  DocumentDriveAction | BaseAction
294
296
  >[],
295
- forceSync
297
+ options
296
298
  );
297
299
  },
298
300
  processActionJob: async ({
299
301
  driveId,
300
302
  documentId,
301
303
  actions,
302
- forceSync
304
+ options
303
305
  }: ActionJob) => {
304
306
  return documentId
305
- ? this.addActions(driveId, documentId, actions, forceSync)
307
+ ? this.addActions(driveId, documentId, actions, options)
306
308
  : this.addDriveActions(
307
309
  driveId,
308
310
  actions as Operation<DocumentDriveAction | BaseAction>[],
309
- forceSync
311
+ options
310
312
  );
311
313
  },
312
314
  processJob: async (job: Job) => {
@@ -335,12 +337,16 @@ export class DocumentDriveServer extends BaseDocumentDriveServer {
335
337
  errors.push(error);
336
338
  });
337
339
 
338
- // if network connect comes online then
339
- // triggers the listeners update
340
+ // if network connect comes back online
341
+ // then triggers the listeners update
340
342
  if (typeof window !== 'undefined') {
341
343
  window.addEventListener('online', () => {
342
344
  this.listenerStateManager
343
- .triggerUpdate(false, this.handleListenerError.bind(this))
345
+ .triggerUpdate(
346
+ false,
347
+ { type: 'local' },
348
+ this.handleListenerError.bind(this)
349
+ )
344
350
  .catch(error => {
345
351
  logger.error(
346
352
  'Non handled error updating listeners',
@@ -1072,9 +1078,9 @@ export class DocumentDriveServer extends BaseDocumentDriveServer {
1072
1078
  drive: string,
1073
1079
  id: string,
1074
1080
  operation: Operation,
1075
- forceSync = true
1081
+ options?: AddOperationOptions
1076
1082
  ): Promise<IOperationResult> {
1077
- return this.addOperations(drive, id, [operation], forceSync);
1083
+ return this.addOperations(drive, id, [operation], options);
1078
1084
  }
1079
1085
 
1080
1086
  private async _addOperations(
@@ -1110,9 +1116,9 @@ export class DocumentDriveServer extends BaseDocumentDriveServer {
1110
1116
  drive: string,
1111
1117
  id: string,
1112
1118
  operation: Operation,
1113
- forceSync = true
1119
+ options?: AddOperationOptions
1114
1120
  ): Promise<IOperationResult> {
1115
- return this.queueOperations(drive, id, [operation], forceSync);
1121
+ return this.queueOperations(drive, id, [operation], options);
1116
1122
  }
1117
1123
 
1118
1124
  private async resultIfExistingOperations(
@@ -1153,7 +1159,7 @@ export class DocumentDriveServer extends BaseDocumentDriveServer {
1153
1159
  drive: string,
1154
1160
  id: string,
1155
1161
  operations: Operation[],
1156
- forceSync = true
1162
+ options?: AddOperationOptions
1157
1163
  ) {
1158
1164
  // if operations are already stored then returns cached document
1159
1165
  const result = await this.resultIfExistingOperations(
@@ -1162,7 +1168,6 @@ export class DocumentDriveServer extends BaseDocumentDriveServer {
1162
1168
  operations
1163
1169
  );
1164
1170
  if (result) {
1165
- console.log('Duplicated operations!');
1166
1171
  return result;
1167
1172
  }
1168
1173
  try {
@@ -1170,7 +1175,7 @@ export class DocumentDriveServer extends BaseDocumentDriveServer {
1170
1175
  driveId: drive,
1171
1176
  documentId: id,
1172
1177
  operations,
1173
- forceSync
1178
+ options
1174
1179
  });
1175
1180
 
1176
1181
  return new Promise<IOperationResult>((resolve, reject) => {
@@ -1205,23 +1210,23 @@ export class DocumentDriveServer extends BaseDocumentDriveServer {
1205
1210
  drive: string,
1206
1211
  id: string,
1207
1212
  action: Action,
1208
- forceSync?: boolean | undefined
1213
+ options?: AddOperationOptions
1209
1214
  ): Promise<IOperationResult> {
1210
- return this.queueActions(drive, id, [action], forceSync);
1215
+ return this.queueActions(drive, id, [action], options);
1211
1216
  }
1212
1217
 
1213
1218
  async queueActions(
1214
1219
  drive: string,
1215
1220
  id: string,
1216
1221
  actions: Action[],
1217
- forceSync?: boolean | undefined
1222
+ options?: AddOperationOptions
1218
1223
  ): Promise<IOperationResult> {
1219
1224
  try {
1220
1225
  const jobId = await this.queueManager.addJob({
1221
1226
  driveId: drive,
1222
1227
  documentId: id,
1223
1228
  actions,
1224
- forceSync
1229
+ options
1225
1230
  });
1226
1231
 
1227
1232
  return new Promise<IOperationResult>((resolve, reject) => {
@@ -1255,20 +1260,20 @@ export class DocumentDriveServer extends BaseDocumentDriveServer {
1255
1260
  async queueDriveAction(
1256
1261
  drive: string,
1257
1262
  action: DocumentDriveAction | BaseAction,
1258
- forceSync?: boolean | undefined
1263
+ options?: AddOperationOptions
1259
1264
  ): Promise<IOperationResult<DocumentDriveDocument>> {
1260
- return this.queueDriveActions(drive, [action], forceSync);
1265
+ return this.queueDriveActions(drive, [action], options);
1261
1266
  }
1262
1267
 
1263
1268
  async queueDriveActions(
1264
1269
  drive: string,
1265
1270
  actions: (DocumentDriveAction | BaseAction)[],
1266
- forceSync?: boolean | undefined
1271
+ options?: AddOperationOptions
1267
1272
  ): Promise<IOperationResult<DocumentDriveDocument>> {
1268
1273
  const jobId = await this.queueManager.addJob({
1269
1274
  driveId: drive,
1270
1275
  actions,
1271
- forceSync
1276
+ options
1272
1277
  });
1273
1278
  return new Promise<IOperationResult<DocumentDriveDocument>>(
1274
1279
  (resolve, reject) => {
@@ -1302,7 +1307,7 @@ export class DocumentDriveServer extends BaseDocumentDriveServer {
1302
1307
  drive: string,
1303
1308
  id: string,
1304
1309
  operations: Operation[],
1305
- forceSync = true
1310
+ options?: AddOperationOptions
1306
1311
  ) {
1307
1312
  // if operations are already stored then returns the result
1308
1313
  const result = await this.resultIfExistingOperations(
@@ -1365,11 +1370,32 @@ export class DocumentDriveServer extends BaseDocumentDriveServer {
1365
1370
  scopes,
1366
1371
  branches
1367
1372
  );
1373
+
1374
+ // checks if any of the provided operations where reshufled
1375
+ const newOp = operationsApplied.find(
1376
+ appliedOp =>
1377
+ !operations.find(
1378
+ o =>
1379
+ o.id === appliedOp.id &&
1380
+ o.index === appliedOp.index &&
1381
+ o.skip === appliedOp.skip &&
1382
+ o.hash === appliedOp.hash
1383
+ )
1384
+ );
1385
+
1386
+ // if there are no new operations then reuses the provided source
1387
+ // otherwise sets it to local so listeners know that there were
1388
+ // new changes originating from this document drive server
1389
+ const source: StrandUpdateSource = newOp
1390
+ ? { type: 'local' }
1391
+ : options?.source ?? { type: 'local' };
1392
+
1368
1393
  // update listener cache
1369
1394
  this.listenerStateManager
1370
1395
  .updateSynchronizationRevisions(
1371
1396
  drive,
1372
1397
  syncUnits,
1398
+ source,
1373
1399
  () => {
1374
1400
  this.updateSyncStatus(drive, 'SYNCING');
1375
1401
 
@@ -1378,7 +1404,7 @@ export class DocumentDriveServer extends BaseDocumentDriveServer {
1378
1404
  }
1379
1405
  },
1380
1406
  this.handleListenerError.bind(this),
1381
- forceSync
1407
+ options?.forceSync ?? source.type === 'local'
1382
1408
  )
1383
1409
  .then(updates => {
1384
1410
  updates.length && this.updateSyncStatus(drive, 'SUCCESS');
@@ -1439,9 +1465,9 @@ export class DocumentDriveServer extends BaseDocumentDriveServer {
1439
1465
  addDriveOperation(
1440
1466
  drive: string,
1441
1467
  operation: Operation<DocumentDriveAction | BaseAction>,
1442
- forceSync = true
1468
+ options?: AddOperationOptions
1443
1469
  ) {
1444
- return this.addDriveOperations(drive, [operation], forceSync);
1470
+ return this.addDriveOperations(drive, [operation], options);
1445
1471
  }
1446
1472
 
1447
1473
  async clearStorage() {
@@ -1482,9 +1508,9 @@ export class DocumentDriveServer extends BaseDocumentDriveServer {
1482
1508
  queueDriveOperation(
1483
1509
  drive: string,
1484
1510
  operation: Operation<DocumentDriveAction | BaseAction>,
1485
- forceSync = true
1511
+ options?: AddOperationOptions
1486
1512
  ): Promise<IOperationResult<DocumentDriveDocument>> {
1487
- return this.queueDriveOperations(drive, [operation], forceSync);
1513
+ return this.queueDriveOperations(drive, [operation], options);
1488
1514
  }
1489
1515
 
1490
1516
  private async resultIfExistingDriveOperations(
@@ -1523,7 +1549,7 @@ export class DocumentDriveServer extends BaseDocumentDriveServer {
1523
1549
  async queueDriveOperations(
1524
1550
  drive: string,
1525
1551
  operations: Operation<DocumentDriveAction | BaseAction>[],
1526
- forceSync = true
1552
+ options?: AddOperationOptions
1527
1553
  ): Promise<IOperationResult<DocumentDriveDocument>> {
1528
1554
  // if operations are already stored then returns cached document
1529
1555
  const result = await this.resultIfExistingDriveOperations(
@@ -1537,7 +1563,7 @@ export class DocumentDriveServer extends BaseDocumentDriveServer {
1537
1563
  const jobId = await this.queueManager.addJob({
1538
1564
  driveId: drive,
1539
1565
  operations,
1540
- forceSync
1566
+ options
1541
1567
  });
1542
1568
  return new Promise<IOperationResult<DocumentDriveDocument>>(
1543
1569
  (resolve, reject) => {
@@ -1570,7 +1596,7 @@ export class DocumentDriveServer extends BaseDocumentDriveServer {
1570
1596
  async addDriveOperations(
1571
1597
  drive: string,
1572
1598
  operations: Operation<DocumentDriveAction | BaseAction>[],
1573
- forceSync = true
1599
+ options?: AddOperationOptions
1574
1600
  ) {
1575
1601
  let document: DocumentDriveDocument | undefined;
1576
1602
  const operationsApplied: Operation<DocumentDriveAction | BaseAction>[] =
@@ -1646,7 +1672,27 @@ export class DocumentDriveServer extends BaseDocumentDriveServer {
1646
1672
  .filter(op => op.scope === 'global')
1647
1673
  .slice()
1648
1674
  .pop();
1675
+
1649
1676
  if (lastOperation) {
1677
+ // checks if any of the provided operations where reshufled
1678
+ const newOp = operationsApplied.find(
1679
+ appliedOp =>
1680
+ !operations.find(
1681
+ o =>
1682
+ o.id === appliedOp.id &&
1683
+ o.index === appliedOp.index &&
1684
+ o.skip === appliedOp.skip &&
1685
+ o.hash === appliedOp.hash
1686
+ )
1687
+ );
1688
+
1689
+ // if there are no new operations then reuses the provided source
1690
+ // otherwise sets it to local so listeners know that there were
1691
+ // new changes originating from this document drive server
1692
+ const source: StrandUpdateSource = newOp
1693
+ ? { type: 'local' }
1694
+ : options?.source ?? { type: 'local' };
1695
+
1650
1696
  this.listenerStateManager
1651
1697
  .updateSynchronizationRevisions(
1652
1698
  drive,
@@ -1662,6 +1708,7 @@ export class DocumentDriveServer extends BaseDocumentDriveServer {
1662
1708
  revision: lastOperation.index
1663
1709
  }
1664
1710
  ],
1711
+ source,
1665
1712
  () => {
1666
1713
  this.updateSyncStatus(drive, 'SYNCING');
1667
1714
 
@@ -1673,7 +1720,7 @@ export class DocumentDriveServer extends BaseDocumentDriveServer {
1673
1720
  }
1674
1721
  },
1675
1722
  this.handleListenerError.bind(this),
1676
- forceSync
1723
+ options?.forceSync ?? source.type === 'local'
1677
1724
  )
1678
1725
  .then(updates => {
1679
1726
  if (updates.length) {
@@ -1768,41 +1815,41 @@ export class DocumentDriveServer extends BaseDocumentDriveServer {
1768
1815
  drive: string,
1769
1816
  id: string,
1770
1817
  action: Action,
1771
- forceSync = true
1818
+ options?: AddOperationOptions
1772
1819
  ): Promise<IOperationResult> {
1773
- return this.addActions(drive, id, [action], forceSync);
1820
+ return this.addActions(drive, id, [action], options);
1774
1821
  }
1775
1822
 
1776
1823
  async addActions(
1777
1824
  drive: string,
1778
1825
  id: string,
1779
1826
  actions: Action[],
1780
- forceSync = true
1827
+ options?: AddOperationOptions
1781
1828
  ): Promise<IOperationResult> {
1782
1829
  const document = await this.getDocument(drive, id);
1783
1830
  const operations = this._buildOperations(document, actions);
1784
- return this.addOperations(drive, id, operations, forceSync);
1831
+ return this.addOperations(drive, id, operations, options);
1785
1832
  }
1786
1833
 
1787
1834
  async addDriveAction(
1788
1835
  drive: string,
1789
1836
  action: DocumentDriveAction | BaseAction,
1790
- forceSync = true
1837
+ options?: AddOperationOptions
1791
1838
  ): Promise<IOperationResult<DocumentDriveDocument>> {
1792
- return this.addDriveActions(drive, [action], forceSync);
1839
+ return this.addDriveActions(drive, [action], options);
1793
1840
  }
1794
1841
 
1795
1842
  async addDriveActions(
1796
1843
  drive: string,
1797
1844
  actions: (DocumentDriveAction | BaseAction)[],
1798
- forceSync = true
1845
+ options?: AddOperationOptions
1799
1846
  ): Promise<IOperationResult<DocumentDriveDocument>> {
1800
1847
  const document = await this.getDrive(drive);
1801
1848
  const operations = this._buildOperations(document, actions);
1802
1849
  const result = await this.addDriveOperations(
1803
1850
  drive,
1804
1851
  operations,
1805
- forceSync
1852
+ options
1806
1853
  );
1807
1854
  return result;
1808
1855
  }
@@ -3,6 +3,7 @@ import {
3
3
  ListenerFilter
4
4
  } from 'document-model-libs/document-drive';
5
5
  import { OperationScope } from 'document-model/document';
6
+ import { logger } from '../../utils/logger';
6
7
  import { OperationError } from '../error';
7
8
  import {
8
9
  BaseListenerManager,
@@ -17,8 +18,7 @@ import {
17
18
  import { PullResponderTransmitter } from './transmitter';
18
19
  import { InternalTransmitter } from './transmitter/internal';
19
20
  import { SwitchboardPushTransmitter } from './transmitter/switchboard-push';
20
- import { ITransmitter } from './transmitter/types';
21
- import { logger } from '../../utils/logger';
21
+ import { ITransmitter, StrandUpdateSource } from './transmitter/types';
22
22
 
23
23
  function debounce<T extends unknown[], R>(
24
24
  func: (...args: T) => Promise<R>,
@@ -118,7 +118,10 @@ export class ListenerManager extends BaseListenerManager {
118
118
  return Promise.resolve(driveMap.delete(listenerId));
119
119
  }
120
120
 
121
- async removeSyncUnits(driveId: string, syncUnits: Pick<SynchronizationUnit, "syncId">[]) {
121
+ async removeSyncUnits(
122
+ driveId: string,
123
+ syncUnits: Pick<SynchronizationUnit, 'syncId'>[]
124
+ ) {
122
125
  const listeners = this.listenerState.get(driveId);
123
126
  if (!listeners) {
124
127
  return;
@@ -134,6 +137,7 @@ export class ListenerManager extends BaseListenerManager {
134
137
  async updateSynchronizationRevisions(
135
138
  driveId: string,
136
139
  syncUnits: SynchronizationUnit[],
140
+ source: StrandUpdateSource,
137
141
  willUpdate?: (listeners: Listener[]) => void,
138
142
  onError?: (
139
143
  error: Error,
@@ -175,7 +179,7 @@ export class ListenerManager extends BaseListenerManager {
175
179
 
176
180
  if (outdatedListeners.length) {
177
181
  willUpdate?.(outdatedListeners);
178
- return this.triggerUpdate(forceSync, onError);
182
+ return this.triggerUpdate(forceSync, source, onError);
179
183
  }
180
184
  return [];
181
185
  }
@@ -214,6 +218,7 @@ export class ListenerManager extends BaseListenerManager {
214
218
  );
215
219
 
216
220
  private async _triggerUpdate(
221
+ source: StrandUpdateSource,
217
222
  onError?: (
218
223
  error: Error,
219
224
  driveId: string,
@@ -246,7 +251,8 @@ export class ListenerManager extends BaseListenerManager {
246
251
 
247
252
  const opData: OperationUpdate[] = [];
248
253
  try {
249
- const data = await this.drive.getOperationData( // TODO - join queries, DEAL WITH INVALID SYNC ID ERROR
254
+ const data = await this.drive.getOperationData(
255
+ // TODO - join queries, DEAL WITH INVALID SYNC ID ERROR
250
256
  driveId,
251
257
  syncUnit.syncId,
252
258
  {
@@ -282,8 +288,10 @@ export class ListenerManager extends BaseListenerManager {
282
288
 
283
289
  // TODO update listeners in parallel, blocking for listeners with block=true
284
290
  try {
285
- const listenerRevisions =
286
- await transmitter?.transmit(strandUpdates);
291
+ const listenerRevisions = await transmitter?.transmit(
292
+ strandUpdates,
293
+ source
294
+ );
287
295
 
288
296
  listener.pendingTimeout = '0';
289
297
  listener.listenerStatus = 'PENDING';
@@ -420,7 +428,9 @@ export class ListenerManager extends BaseListenerManager {
420
428
  this.listenerState.delete(driveId);
421
429
  const transmitters = this.transmitters[driveId];
422
430
  if (transmitters) {
423
- await Promise.all(Object.values(transmitters).map(t => t.disconnect?.()));
431
+ await Promise.all(
432
+ Object.values(transmitters).map(t => t.disconnect?.())
433
+ );
424
434
  }
425
435
  }
426
436
 
@@ -456,7 +466,8 @@ export class ListenerManager extends BaseListenerManager {
456
466
 
457
467
  const { documentId, driveId, scope, branch } = syncUnit;
458
468
  try {
459
- const operations = await this.drive.getOperationData( // DEAL WITH INVALID SYNC ID ERROR
469
+ const operations = await this.drive.getOperationData(
470
+ // DEAL WITH INVALID SYNC ID ERROR
460
471
  driveId,
461
472
  syncUnit.syncId,
462
473
  {
@@ -16,7 +16,11 @@ import {
16
16
  StrandUpdate
17
17
  } from '../../types';
18
18
  import { ListenerManager } from '../manager';
19
- import { ITransmitter, PullResponderTrigger } from './types';
19
+ import {
20
+ ITransmitter,
21
+ PullResponderTrigger,
22
+ StrandUpdateSource
23
+ } from './types';
20
24
 
21
25
  export type OperationUpdateGraphQL = Omit<OperationUpdate, 'input'> & {
22
26
  input: string;
@@ -215,7 +219,10 @@ export class PullResponderTransmitter implements IPullResponderTransmitter {
215
219
  private static async executePull(
216
220
  driveId: string,
217
221
  trigger: PullResponderTrigger,
218
- onStrandUpdate: (strand: StrandUpdate) => Promise<IOperationResult>,
222
+ onStrandUpdate: (
223
+ strand: StrandUpdate,
224
+ source: StrandUpdateSource
225
+ ) => Promise<IOperationResult>,
219
226
  onError: (error: Error) => void,
220
227
  onRevisions?: (revisions: ListenerRevisionWithError[]) => void,
221
228
  onAcknowledge?: (success: boolean) => void
@@ -246,7 +253,10 @@ export class PullResponderTransmitter implements IPullResponderTransmitter {
246
253
 
247
254
  let error: Error | undefined = undefined;
248
255
  try {
249
- const result = await onStrandUpdate(strand);
256
+ const result = await onStrandUpdate(strand, {
257
+ type: 'trigger',
258
+ trigger
259
+ });
250
260
  if (result.error) {
251
261
  throw result.error;
252
262
  }
@@ -291,7 +301,10 @@ export class PullResponderTransmitter implements IPullResponderTransmitter {
291
301
  static setupPull(
292
302
  driveId: string,
293
303
  trigger: PullResponderTrigger,
294
- onStrandUpdate: (strand: StrandUpdate) => Promise<IOperationResult>,
304
+ onStrandUpdate: (
305
+ strand: StrandUpdate,
306
+ source: StrandUpdateSource
307
+ ) => Promise<IOperationResult>,
295
308
  onError: (error: Error) => void,
296
309
  onRevisions?: (revisions: ListenerRevisionWithError[]) => void,
297
310
  onAcknowledge?: (success: boolean) => void
@@ -1,13 +1,13 @@
1
1
  import stringify from 'json-stringify-deterministic';
2
2
  import { gql, requestGraphql } from '../../../utils/graphql';
3
+ import { logger } from '../../../utils/logger';
3
4
  import {
4
5
  BaseDocumentDriveServer,
5
6
  Listener,
6
7
  ListenerRevision,
7
8
  StrandUpdate
8
9
  } from '../../types';
9
- import { ITransmitter } from './types';
10
- import { logger } from '../../../utils/logger';
10
+ import { ITransmitter, StrandUpdateSource } from './types';
11
11
 
12
12
  export class SwitchboardPushTransmitter implements ITransmitter {
13
13
  private drive: BaseDocumentDriveServer;
@@ -20,7 +20,24 @@ export class SwitchboardPushTransmitter implements ITransmitter {
20
20
  this.targetURL = listener.callInfo!.data!;
21
21
  }
22
22
 
23
- async transmit(strands: StrandUpdate[]): Promise<ListenerRevision[]> {
23
+ async transmit(
24
+ strands: StrandUpdate[],
25
+ source: StrandUpdateSource
26
+ ): Promise<ListenerRevision[]> {
27
+ if (
28
+ source.type === 'trigger' &&
29
+ source.trigger.data?.url === this.targetURL
30
+ ) {
31
+ return strands.map(strand => ({
32
+ driveId: strand.driveId,
33
+ documentId: strand.documentId,
34
+ scope: strand.scope,
35
+ branch: strand.branch,
36
+ status: 'SUCCESS',
37
+ revision: strand.operations.at(-1)?.index ?? -1
38
+ }));
39
+ }
40
+
24
41
  // Send Graphql mutation to switchboard
25
42
  try {
26
43
  const { pushUpdates } = await requestGraphql<{
@@ -4,11 +4,19 @@ import {
4
4
  } from 'document-model-libs/document-drive';
5
5
  import { ListenerRevision, StrandUpdate } from '../..';
6
6
 
7
+ export type StrandUpdateSource =
8
+ | {
9
+ type: 'local';
10
+ }
11
+ | { type: 'trigger'; trigger: Trigger };
12
+
7
13
  export interface ITransmitter {
8
- transmit(strands: StrandUpdate[]): Promise<ListenerRevision[]>;
14
+ transmit(
15
+ strands: StrandUpdate[],
16
+ source: StrandUpdateSource
17
+ ): Promise<ListenerRevision[]>;
9
18
  disconnect?(): Promise<void>;
10
19
  }
11
-
12
20
  export interface InternalTransmitterService extends ITransmitter {
13
21
  getName(): string;
14
22
  }
@@ -20,7 +20,7 @@ import type {
20
20
  } from 'document-model/document';
21
21
  import { Unsubscribe } from 'nanoevents';
22
22
  import { OperationError } from './error';
23
- import { ITransmitter } from './listener/transmitter/types';
23
+ import { ITransmitter, StrandUpdateSource } from './listener/transmitter/types';
24
24
 
25
25
  export type DriveInput = State<
26
26
  Omit<DocumentDriveState, '__typename' | 'id' | 'nodes'> & { id?: string },
@@ -59,7 +59,10 @@ export type SynchronizationUnit = {
59
59
  revision: number;
60
60
  };
61
61
 
62
- export type SynchronizationUnitQuery = Omit<SynchronizationUnit, "revision" | "lastUpdated">;
62
+ export type SynchronizationUnitQuery = Omit<
63
+ SynchronizationUnit,
64
+ 'revision' | 'lastUpdated'
65
+ >;
63
66
 
64
67
  export type Listener = {
65
68
  driveId: string;
@@ -149,6 +152,11 @@ export type GetDocumentOptions = ReducerOptions & {
149
152
  checkHashes?: boolean;
150
153
  };
151
154
 
155
+ export type AddOperationOptions = {
156
+ forceSync?: boolean;
157
+ source: StrandUpdateSource;
158
+ };
159
+
152
160
  export abstract class BaseDocumentDriveServer {
153
161
  /** Public methods **/
154
162
  abstract getDrives(): Promise<string[]>;
@@ -163,9 +171,7 @@ export abstract class BaseDocumentDriveServer {
163
171
  options?: GetDocumentOptions
164
172
  ): Promise<DocumentDriveDocument>;
165
173
 
166
- abstract getDriveBySlug(
167
- slug: string,
168
- ): Promise<DocumentDriveDocument>;
174
+ abstract getDriveBySlug(slug: string): Promise<DocumentDriveDocument>;
169
175
 
170
176
  abstract getDocuments(drive: string): Promise<string[]>;
171
177
  abstract getDocument(
@@ -178,100 +184,101 @@ export abstract class BaseDocumentDriveServer {
178
184
  drive: string,
179
185
  id: string,
180
186
  operation: Operation,
181
- forceSync?: boolean
187
+ options?: AddOperationOptions
182
188
  ): Promise<IOperationResult>;
183
189
 
184
190
  abstract addOperations(
185
191
  drive: string,
186
192
  id: string,
187
193
  operations: Operation[],
188
- forceSync?: boolean
194
+ options?: AddOperationOptions
189
195
  ): Promise<IOperationResult>;
190
196
 
191
197
  abstract queueOperation(
192
198
  drive: string,
193
199
  id: string,
194
200
  operation: Operation,
195
- forceSync?: boolean
201
+ options?: AddOperationOptions
196
202
  ): Promise<IOperationResult>;
197
203
 
198
204
  abstract queueOperations(
199
205
  drive: string,
200
206
  id: string,
201
207
  operations: Operation[],
202
- forceSync?: boolean
208
+ options?: AddOperationOptions
203
209
  ): Promise<IOperationResult>;
204
210
 
205
211
  abstract queueAction(
206
212
  drive: string,
207
213
  id: string,
208
214
  action: Action,
209
- forceSync?: boolean
215
+ options?: AddOperationOptions
210
216
  ): Promise<IOperationResult>;
211
217
 
212
218
  abstract queueActions(
213
219
  drive: string,
214
220
  id: string,
215
221
  actions: Action[],
216
- forceSync?: boolean
222
+ options?: AddOperationOptions
217
223
  ): Promise<IOperationResult>;
218
224
 
219
225
  abstract addDriveOperation(
220
226
  drive: string,
221
227
  operation: Operation<DocumentDriveAction | BaseAction>,
222
- forceSync?: boolean
228
+ options?: AddOperationOptions
223
229
  ): Promise<IOperationResult<DocumentDriveDocument>>;
224
230
  abstract addDriveOperations(
225
231
  drive: string,
226
232
  operations: Operation<DocumentDriveAction | BaseAction>[],
227
- forceSync?: boolean
233
+ options?: AddOperationOptions
228
234
  ): Promise<IOperationResult<DocumentDriveDocument>>;
229
235
 
230
236
  abstract queueDriveOperation(
231
237
  drive: string,
232
238
  operation: Operation<DocumentDriveAction | BaseAction>,
233
- forceSync?: boolean
239
+ options?: AddOperationOptions
234
240
  ): Promise<IOperationResult<DocumentDriveDocument>>;
241
+
235
242
  abstract queueDriveOperations(
236
243
  drive: string,
237
244
  operations: Operation<DocumentDriveAction | BaseAction>[],
238
- forceSync?: boolean
245
+ options?: AddOperationOptions
239
246
  ): Promise<IOperationResult<DocumentDriveDocument>>;
240
247
 
241
248
  abstract queueDriveAction(
242
249
  drive: string,
243
250
  action: DocumentDriveAction | BaseAction,
244
- forceSync?: boolean
251
+ options?: AddOperationOptions
245
252
  ): Promise<IOperationResult<DocumentDriveDocument>>;
246
253
 
247
254
  abstract queueDriveActions(
248
255
  drive: string,
249
256
  actions: Array<DocumentDriveAction | BaseAction>,
250
- forceSync?: boolean
257
+ options?: AddOperationOptions
251
258
  ): Promise<IOperationResult<DocumentDriveDocument>>;
252
259
 
253
260
  abstract addAction(
254
261
  drive: string,
255
262
  id: string,
256
263
  action: Action,
257
- forceSync?: boolean
264
+ options?: AddOperationOptions
258
265
  ): Promise<IOperationResult>;
259
266
  abstract addActions(
260
267
  drive: string,
261
268
  id: string,
262
269
  actions: Action[],
263
- forceSync?: boolean
270
+ options?: AddOperationOptions
264
271
  ): Promise<IOperationResult>;
265
272
 
266
273
  abstract addDriveAction(
267
274
  drive: string,
268
275
  action: DocumentDriveAction | BaseAction,
269
- forceSync?: boolean
276
+ options?: AddOperationOptions
270
277
  ): Promise<IOperationResult<DocumentDriveDocument>>;
271
278
  abstract addDriveActions(
272
279
  drive: string,
273
280
  actions: (DocumentDriveAction | BaseAction)[],
274
- forceSync?: boolean
281
+ options?: AddOperationOptions
275
282
  ): Promise<IOperationResult<DocumentDriveDocument>>;
276
283
 
277
284
  abstract getSyncStatus(drive: string): SyncStatus;
@@ -351,7 +358,7 @@ export abstract class BaseListenerManager {
351
358
  }
352
359
 
353
360
  abstract initDrive(drive: DocumentDriveDocument): Promise<void>;
354
- abstract removeDrive(driveId: DocumentDriveState["id"]): Promise<void>;
361
+ abstract removeDrive(driveId: DocumentDriveState['id']): Promise<void>;
355
362
 
356
363
  abstract addListener(listener: Listener): Promise<ITransmitter>;
357
364
  abstract removeListener(
@@ -376,6 +383,7 @@ export abstract class BaseListenerManager {
376
383
  abstract updateSynchronizationRevisions(
377
384
  driveId: string,
378
385
  syncUnits: SynchronizationUnit[],
386
+ source: StrandUpdateSource,
379
387
  willUpdate?: (listeners: Listener[]) => void,
380
388
  onError?: (
381
389
  error: Error,
@@ -193,6 +193,7 @@ export class PrismaStorage implements IDriveStorage {
193
193
  type: op.type,
194
194
  scope: op.scope,
195
195
  branch: 'main',
196
+ opId: op.id,
196
197
  skip: op.skip,
197
198
  context: op.context,
198
199
  resultingState: op.resultingState