dexie-cloud-addon 4.1.0-beta.44 → 4.1.0-beta.45

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.
@@ -8,7 +8,7 @@
8
8
  *
9
9
  * ==========================================================================
10
10
  *
11
- * Version 4.1.0-beta.44, Fri Feb 14 2025
11
+ * Version 4.1.0-beta.45, Mon Mar 31 2025
12
12
  *
13
13
  * https://dexie.org
14
14
  *
@@ -2799,6 +2799,7 @@ function encodeYMessage(msg) {
2799
2799
  break;
2800
2800
  case 'u-s':
2801
2801
  writeVarUint8Array(encoder, msg.u);
2802
+ writeVarString(encoder, msg.r || '');
2802
2803
  break;
2803
2804
  }
2804
2805
  }
@@ -3185,7 +3186,8 @@ function decodeYMessage(a) {
3185
3186
  table,
3186
3187
  prop,
3187
3188
  k,
3188
- u: readVarUint8Array(decoder)
3189
+ u: readVarUint8Array(decoder),
3190
+ r: (decoder.pos < decoder.arr.length && readVarString(decoder)) || undefined,
3189
3191
  };
3190
3192
  default:
3191
3193
  throw new TypeError(`Unknown message type: ${type}`);
@@ -4462,6 +4464,7 @@ function syncWithServer(changes, y, syncState, baseRevs, db, databaseUrl, schema
4462
4464
  baseRevs,
4463
4465
  changes: encodeIdsForServer(db.dx.core.schema, currentUser, changes),
4464
4466
  y,
4467
+ dxcv: db.cloud.version
4465
4468
  };
4466
4469
  console.debug('Sync request', syncRequest);
4467
4470
  db.syncStateChangedEvent.next({
@@ -4604,9 +4607,11 @@ function applyServerChanges(changes, db) {
4604
4607
  return __awaiter(this, void 0, void 0, function* () {
4605
4608
  console.debug('Applying server changes', changes, Dexie.currentTransaction);
4606
4609
  for (const { table: tableName, muts } of changes) {
4610
+ if (!db.dx._allTables[tableName]) {
4611
+ console.debug(`Server sent changes for table ${tableName} that we don't have. Ignoring.`);
4612
+ continue;
4613
+ }
4607
4614
  const table = db.table(tableName);
4608
- if (!table)
4609
- continue; // If server sends changes on a table we don't have, ignore it.
4610
4615
  const { primaryKey } = table.core.schema;
4611
4616
  const keyDecoder = (key) => {
4612
4617
  switch (key[0]) {
@@ -4792,9 +4797,15 @@ function listYClientMessagesAndStateVector(db, tablesToSync) {
4792
4797
 
4793
4798
  function getUpdatesTable(db, table, ydocProp) {
4794
4799
  var _a, _b, _c;
4800
+ if (!db.dx._allTables[table])
4801
+ return undefined;
4795
4802
  const utbl = (_c = (_b = (_a = db.table(table)) === null || _a === void 0 ? void 0 : _a.schema.yProps) === null || _b === void 0 ? void 0 : _b.find(p => p.prop === ydocProp)) === null || _c === void 0 ? void 0 : _c.updatesTable;
4796
- if (!utbl)
4797
- throw new Error(`No updatesTable found for ${table}.${ydocProp}`);
4803
+ if (!utbl) {
4804
+ console.debug(`No updatesTable found for ${table}.${ydocProp}`);
4805
+ return undefined;
4806
+ }
4807
+ if (!db.dx._allTables[utbl])
4808
+ return undefined;
4798
4809
  return db.table(utbl);
4799
4810
  }
4800
4811
 
@@ -4805,74 +4816,91 @@ function applyYServerMessages(yMessages, db) {
4805
4816
  let resyncNeeded = false;
4806
4817
  let yServerRevision;
4807
4818
  for (const m of yMessages) {
4808
- switch (m.type) {
4809
- case 'u-s': {
4810
- const utbl = getUpdatesTable(db, m.table, m.prop);
4811
- receivedUntils[utbl.name] = yield utbl.add({
4812
- k: m.k,
4813
- u: m.u,
4814
- });
4815
- break;
4816
- }
4817
- case 'u-ack': {
4818
- const utbl = getUpdatesTable(db, m.table, m.prop);
4819
- yield db.transaction('rw', utbl, (tx) => __awaiter(this, void 0, void 0, function* () {
4820
- let syncer = (yield tx
4821
- .table(utbl.name)
4822
- .get(DEXIE_CLOUD_SYNCER_ID));
4823
- yield tx.table(utbl.name).put(Object.assign(Object.assign({}, (syncer || { i: DEXIE_CLOUD_SYNCER_ID })), { unsentFrom: Math.max((syncer === null || syncer === void 0 ? void 0 : syncer.unsentFrom) || 1, m.i + 1) }));
4824
- }));
4825
- break;
4826
- }
4827
- case 'u-reject': {
4828
- // Acces control or constraint rejected the update.
4829
- // We delete it. It's not going to be sent again.
4830
- // What's missing is a way to notify consumers, such as Tiptap editor, that the update was rejected.
4831
- // This is only an issue when the document is open. We could find the open document and
4832
- // in a perfect world, we should send a reverse update to the open document to undo the change.
4833
- // See my question in https://discuss.yjs.dev/t/generate-an-inverse-update/2765
4834
- console.debug(`Y update rejected. Deleting it.`);
4835
- const utbl = getUpdatesTable(db, m.table, m.prop);
4836
- // Delete the rejected update and all local updates since (avoid holes in the CRDT)
4837
- // and destroy it's open document if there is one.
4838
- const primaryKey = (_a = (yield utbl.get(m.i))) === null || _a === void 0 ? void 0 : _a.k;
4839
- if (primaryKey != null) {
4840
- yield db.transaction('rw', utbl, (tx) => {
4841
- // @ts-ignore
4842
- tx.idbtrans._rejecting_y_ypdate = true; // Inform ydoc triggers that we delete because of a rejection and not GC
4843
- return utbl
4844
- .where('i')
4845
- .aboveOrEqual(m.i)
4846
- .filter((u) => cmp(u.k, primaryKey) === 0 && ((u.f || 0) & 1) === 1)
4847
- .delete();
4848
- });
4849
- // Destroy active doc
4850
- const activeDoc = DexieYProvider.getDocCache(db.dx).find(m.table, primaryKey, m.prop);
4851
- if (activeDoc)
4852
- activeDoc.destroy(); // Destroy the document so that editors don't continue to work on it
4819
+ try {
4820
+ switch (m.type) {
4821
+ case 'u-s': {
4822
+ const utbl = getUpdatesTable(db, m.table, m.prop);
4823
+ if (utbl) {
4824
+ const updateRow = {
4825
+ k: m.k,
4826
+ u: m.u,
4827
+ };
4828
+ if (m.r) {
4829
+ // @ts-ignore
4830
+ updateRow.r = m.r;
4831
+ yServerRevision = m.r;
4832
+ }
4833
+ receivedUntils[utbl.name] = yield utbl.add(updateRow);
4834
+ }
4835
+ break;
4853
4836
  }
4854
- break;
4855
- }
4856
- case 'in-sync': {
4857
- const doc = DexieYProvider.getDocCache(db.dx).find(m.table, m.k, m.prop);
4858
- if (doc && !doc.isSynced) {
4859
- doc.emit('sync', [true]);
4837
+ case 'u-ack': {
4838
+ const utbl = getUpdatesTable(db, m.table, m.prop);
4839
+ if (utbl) {
4840
+ yield db.transaction('rw', utbl, (tx) => __awaiter(this, void 0, void 0, function* () {
4841
+ let syncer = (yield tx
4842
+ .table(utbl.name)
4843
+ .get(DEXIE_CLOUD_SYNCER_ID));
4844
+ yield tx.table(utbl.name).put(Object.assign(Object.assign({}, (syncer || { i: DEXIE_CLOUD_SYNCER_ID })), { unsentFrom: Math.max((syncer === null || syncer === void 0 ? void 0 : syncer.unsentFrom) || 1, m.i + 1) }));
4845
+ }));
4846
+ }
4847
+ break;
4860
4848
  }
4861
- break;
4862
- }
4863
- case 'y-complete-sync-done': {
4864
- yServerRevision = m.yServerRev;
4865
- break;
4849
+ case 'u-reject': {
4850
+ // Acces control or constraint rejected the update.
4851
+ // We delete it. It's not going to be sent again.
4852
+ // What's missing is a way to notify consumers, such as Tiptap editor, that the update was rejected.
4853
+ // This is only an issue when the document is open. We could find the open document and
4854
+ // in a perfect world, we should send a reverse update to the open document to undo the change.
4855
+ // See my question in https://discuss.yjs.dev/t/generate-an-inverse-update/2765
4856
+ console.debug(`Y update rejected. Deleting it.`);
4857
+ const utbl = getUpdatesTable(db, m.table, m.prop);
4858
+ if (!utbl)
4859
+ break;
4860
+ // Delete the rejected update and all local updates since (avoid holes in the CRDT)
4861
+ // and destroy it's open document if there is one.
4862
+ const primaryKey = (_a = (yield utbl.get(m.i))) === null || _a === void 0 ? void 0 : _a.k;
4863
+ if (primaryKey != null) {
4864
+ yield db.transaction('rw', utbl, (tx) => {
4865
+ // @ts-ignore
4866
+ tx.idbtrans._rejecting_y_ypdate = true; // Inform ydoc triggers that we delete because of a rejection and not GC
4867
+ return utbl
4868
+ .where('i')
4869
+ .aboveOrEqual(m.i)
4870
+ .filter((u) => cmp(u.k, primaryKey) === 0 && ((u.f || 0) & 1) === 1)
4871
+ .delete();
4872
+ });
4873
+ // Destroy active doc
4874
+ const activeDoc = DexieYProvider.getDocCache(db.dx).find(m.table, primaryKey, m.prop);
4875
+ if (activeDoc)
4876
+ activeDoc.destroy(); // Destroy the document so that editors don't continue to work on it
4877
+ }
4878
+ break;
4879
+ }
4880
+ case 'in-sync': {
4881
+ const doc = DexieYProvider.getDocCache(db.dx).find(m.table, m.k, m.prop);
4882
+ if (doc && !doc.isSynced) {
4883
+ doc.emit('sync', [true]);
4884
+ }
4885
+ break;
4886
+ }
4887
+ case 'y-complete-sync-done': {
4888
+ yServerRevision = m.yServerRev;
4889
+ break;
4890
+ }
4891
+ case 'outdated-server-rev':
4892
+ resyncNeeded = true;
4893
+ break;
4866
4894
  }
4867
- case 'outdated-server-rev':
4868
- resyncNeeded = true;
4869
- break;
4895
+ }
4896
+ catch (e) {
4897
+ console.error(`Failed to apply YMessage`, m, e);
4870
4898
  }
4871
4899
  }
4872
4900
  return {
4873
4901
  receivedUntils,
4874
4902
  resyncNeeded,
4875
- yServerRevision
4903
+ yServerRevision,
4876
4904
  };
4877
4905
  });
4878
4906
  }
@@ -4983,7 +5011,9 @@ function downloadYDocsFromServer(db_1, databaseUrl_1, _a) {
4983
5011
  throw new Error(`Protocol error from ${databaseUrl}/y/download`);
4984
5012
  }
4985
5013
  const yTable = getUpdatesTable(db, currentTable, currentProp);
4986
- yield yTable.bulkAdd(docsToInsert);
5014
+ if (yTable) {
5015
+ yield yTable.bulkAdd(docsToInsert);
5016
+ }
4987
5017
  docsToInsert = [];
4988
5018
  }
4989
5019
  if (currentRealmId &&
@@ -5319,12 +5349,12 @@ function _sync(db_1, options_1, schema_1) {
5319
5349
  return false; // Not needed anymore
5320
5350
  });
5321
5351
  }
5322
- function deleteObjectsFromRemovedRealms(db, res, prevState) {
5352
+ function deleteObjectsFromRemovedRealms(db, res, syncState) {
5323
5353
  return __awaiter(this, void 0, void 0, function* () {
5324
5354
  const deletedRealms = new Set();
5325
5355
  const rejectedRealms = new Set();
5326
- const previousRealmSet = prevState ? prevState.realms : [];
5327
- const previousInviteRealmSet = prevState ? prevState.inviteRealms : [];
5356
+ const previousRealmSet = syncState ? syncState.realms : [];
5357
+ const previousInviteRealmSet = syncState ? syncState.inviteRealms : [];
5328
5358
  const updatedRealmSet = new Set(res.realms);
5329
5359
  const updatedTotalRealmSet = new Set(res.realms.concat(res.inviteRealms));
5330
5360
  for (const realmId of previousRealmSet) {
@@ -5366,17 +5396,10 @@ function deleteObjectsFromRemovedRealms(db, res, prevState) {
5366
5396
  }
5367
5397
  }
5368
5398
  }
5369
- if (rejectedRealms.size > 0) {
5370
- // Remove rejected/deleted realms from yDownloadedRealms because of the following use case:
5371
- // 1. User becomes added to the realm
5372
- // 2. User syncs and all documents of the realm is downloaded (downloadYDocsFromServer.ts)
5373
- // 3. User leaves the realm and all docs are deleted locally (built-in-trigger of deleting their rows in this file)
5374
- // 4. User is yet again added to the realm. At this point, we must make sure the docs are not considered already downloaded.
5375
- const updateSpec = {};
5399
+ if (rejectedRealms.size > 0 && (syncState === null || syncState === void 0 ? void 0 : syncState.yDownloadedRealms)) {
5376
5400
  for (const realmId of rejectedRealms) {
5377
- updateSpec[`yDownloadedRealms.${realmId}`] = undefined; // Setting to undefined will delete the property
5401
+ delete syncState.yDownloadedRealms[realmId];
5378
5402
  }
5379
- yield db.$syncState.update('syncState', updateSpec);
5380
5403
  }
5381
5404
  });
5382
5405
  }
@@ -6899,13 +6922,13 @@ const SERVER_PING_TIMEOUT = 20000;
6899
6922
  const CLIENT_PING_INTERVAL = 30000;
6900
6923
  const FAIL_RETRY_WAIT_TIME = 60000;
6901
6924
  class WSObservable extends Observable$1 {
6902
- constructor(db, rev, realmSetHash, clientIdentity, messageProducer, webSocketStatus, user) {
6903
- super((subscriber) => new WSConnection(db, rev, realmSetHash, clientIdentity, user, subscriber, messageProducer, webSocketStatus));
6925
+ constructor(db, rev, yrev, realmSetHash, clientIdentity, messageProducer, webSocketStatus, user) {
6926
+ super((subscriber) => new WSConnection(db, rev, yrev, realmSetHash, clientIdentity, user, subscriber, messageProducer, webSocketStatus));
6904
6927
  }
6905
6928
  }
6906
6929
  let counter = 0;
6907
6930
  class WSConnection extends Subscription$1 {
6908
- constructor(db, rev, realmSetHash, clientIdentity, user, subscriber, messageProducer, webSocketStatus) {
6931
+ constructor(db, rev, yrev, realmSetHash, clientIdentity, user, subscriber, messageProducer, webSocketStatus) {
6909
6932
  super(() => this.teardown());
6910
6933
  this.id = ++counter;
6911
6934
  this.subscriptions = new Set();
@@ -6914,6 +6937,7 @@ class WSConnection extends Subscription$1 {
6914
6937
  this.db = db;
6915
6938
  this.databaseUrl = db.cloud.options.databaseUrl;
6916
6939
  this.rev = rev;
6940
+ this.yrev = yrev;
6917
6941
  this.realmSetHash = realmSetHash;
6918
6942
  this.clientIdentity = clientIdentity;
6919
6943
  this.user = user;
@@ -6983,6 +7007,11 @@ class WSConnection extends Subscription$1 {
6983
7007
  }
6984
7008
  this.webSocketStatus.next('connecting');
6985
7009
  this.pinger = setInterval(() => __awaiter(this, void 0, void 0, function* () {
7010
+ // setInterval here causes unnecessary pings when server is proved active anyway.
7011
+ // TODO: Use setTimout() here instead. When triggered, check if we really need to ping.
7012
+ // In case we've had server activity, we don't need to ping. Then schedule then next ping
7013
+ // to the time when we should ping next time (based on lastServerActivity + CLIENT_PING_INTERVAL).
7014
+ // Else, ping now and schedule next ping to CLIENT_PING_INTERVAL from now.
6986
7015
  if (this.closed) {
6987
7016
  console.debug('pinger check', this.id, 'CLOSED.');
6988
7017
  this.teardown();
@@ -7029,9 +7058,13 @@ class WSConnection extends Subscription$1 {
7029
7058
  if (this.subscriber.closed)
7030
7059
  return;
7031
7060
  searchParams.set('v', '2');
7032
- searchParams.set('rev', this.rev);
7061
+ if (this.rev)
7062
+ searchParams.set('rev', this.rev);
7063
+ if (this.yrev)
7064
+ searchParams.set('yrev', this.yrev);
7033
7065
  searchParams.set('realmsHash', this.realmSetHash);
7034
7066
  searchParams.set('clientId', this.clientIdentity);
7067
+ searchParams.set('dxcv', this.db.cloud.version);
7035
7068
  if (this.user.accessToken) {
7036
7069
  searchParams.set('token', this.user.accessToken);
7037
7070
  }
@@ -7068,8 +7101,8 @@ class WSConnection extends Subscription$1 {
7068
7101
  }
7069
7102
  }
7070
7103
  }
7071
- else if (msg.type === 'u-ack' || msg.type === 'u-reject' || msg.type === 'u-s' || msg.type === 'in-sync') {
7072
- applyYServerMessages([msg], this.db);
7104
+ else if (msg.type === 'pong') {
7105
+ // Do nothing
7073
7106
  }
7074
7107
  else if (msg.type === 'doc-open') {
7075
7108
  const docCache = DexieYProvider.getDocCache(this.db.dx);
@@ -7078,11 +7111,26 @@ class WSConnection extends Subscription$1 {
7078
7111
  getOpenDocSignal(doc).next(); // Make yHandler reopen the document on server.
7079
7112
  }
7080
7113
  }
7081
- else if (msg.type === 'outdated-server-rev' || msg.type === 'y-complete-sync-done') {
7082
- // Won't happen but need this for typing.
7083
- throw new Error('Outdated server revision or y-complete-sync-done not expected over WebSocket - only in sync using fetch()');
7114
+ else if (msg.type === 'u-ack' || msg.type === 'u-reject' || msg.type === 'u-s' || msg.type === 'in-sync' || msg.type === 'outdated-server-rev' || msg.type === 'y-complete-sync-done') {
7115
+ applyYServerMessages([msg], this.db).then((_a) => __awaiter(this, [_a], void 0, function* ({ resyncNeeded, yServerRevision, receivedUntils }) {
7116
+ if (yServerRevision) {
7117
+ yield this.db.$syncState.update('syncState', { yServerRevision: yServerRevision });
7118
+ }
7119
+ if (msg.type === 'u-s' && receivedUntils) {
7120
+ const utbl = getUpdatesTable(this.db, msg.table, msg.prop);
7121
+ if (utbl) {
7122
+ const receivedUntil = receivedUntils[utbl.name];
7123
+ if (receivedUntil) {
7124
+ yield utbl.update(DEXIE_CLOUD_SYNCER_ID, { receivedUntil });
7125
+ }
7126
+ }
7127
+ }
7128
+ if (resyncNeeded) {
7129
+ yield this.db.cloud.sync({ purpose: 'pull', wait: true });
7130
+ }
7131
+ }));
7084
7132
  }
7085
- else if (msg.type !== 'pong') {
7133
+ else {
7086
7134
  // Forward the request to our subscriber, wich is in messageFromServerQueue.ts (via connectWebSocket's subscribe() at the end!)
7087
7135
  this.subscriber.next(msg);
7088
7136
  }
@@ -7215,7 +7263,7 @@ function connectWebSocket(db) {
7215
7263
  // If no new entries, server won't bother the client. If new entries, server sends only those
7216
7264
  // and the baseRev of the last from same client-ID.
7217
7265
  if (userLogin) {
7218
- return new WSObservable(db, db.cloud.persistedSyncState.value.serverRevision, realmSetHash, db.cloud.persistedSyncState.value.clientIdentity, messageProducer, db.cloud.webSocketStatus, userLogin);
7266
+ return new WSObservable(db, db.cloud.persistedSyncState.value.serverRevision, db.cloud.persistedSyncState.value.yServerRevision, realmSetHash, db.cloud.persistedSyncState.value.clientIdentity, messageProducer, db.cloud.webSocketStatus, userLogin);
7219
7267
  }
7220
7268
  else {
7221
7269
  return from$1([]);
@@ -7345,61 +7393,95 @@ function syncIfPossible(db, cloudOptions, cloudSchema, options) {
7345
7393
  }
7346
7394
 
7347
7395
  const SECONDS = 1000;
7348
- const MINUTES = 60 * SECONDS;
7349
7396
 
7350
7397
  function LocalSyncWorker(db, cloudOptions, cloudSchema) {
7351
7398
  let localSyncEventSubscription = null;
7352
- //let syncHandler: ((event: Event) => void) | null = null;
7353
- //let periodicSyncHandler: ((event: Event) => void) | null = null;
7354
7399
  let cancelToken = { cancelled: false };
7355
- let retryHandle = null;
7356
- let retryPurpose = null; // "pull" is superset of "push"
7357
- function syncAndRetry(purpose, retryNum = 1) {
7400
+ let nextRetryTime = 0;
7401
+ let syncStartTime = 0;
7402
+ function syncAndRetry(retryNum = 1) {
7358
7403
  // Use setTimeout() to get onto a clean stack and
7359
7404
  // break free from possible active transaction:
7360
7405
  setTimeout(() => {
7361
- if (retryHandle)
7362
- clearTimeout(retryHandle);
7363
- const combPurpose = retryPurpose === 'pull' ? 'pull' : purpose;
7364
- retryHandle = null;
7365
- retryPurpose = null;
7406
+ const purpose = pullSignalled ? 'pull' : 'push';
7407
+ syncStartTime = Date.now();
7366
7408
  syncIfPossible(db, cloudOptions, cloudSchema, {
7367
7409
  cancelToken,
7368
7410
  retryImmediatelyOnFetchError: true, // workaround for "net::ERR_NETWORK_CHANGED" in chrome.
7369
- purpose: combPurpose,
7370
- }).catch((e) => {
7371
- console.error('error in syncIfPossible()', e);
7411
+ purpose,
7412
+ }).then(() => {
7413
+ if (cancelToken.cancelled) {
7414
+ stop();
7415
+ }
7416
+ else {
7417
+ if (pullSignalled || pushSignalled) {
7418
+ // If we have signalled for more sync, do it now.
7419
+ pullSignalled = false;
7420
+ pushSignalled = false;
7421
+ return syncAndRetry();
7422
+ }
7423
+ }
7424
+ ongoingSync = false;
7425
+ nextRetryTime = 0;
7426
+ syncStartTime = 0;
7427
+ }).catch((error) => {
7428
+ console.error('error in syncIfPossible()', error);
7372
7429
  if (cancelToken.cancelled) {
7373
7430
  stop();
7431
+ ongoingSync = false;
7432
+ nextRetryTime = 0;
7433
+ syncStartTime = 0;
7374
7434
  }
7375
- else if (retryNum < 3) {
7376
- // Mimic service worker sync event: retry 3 times
7377
- // * first retry after 5 minutes
7378
- // * second retry 15 minutes later
7379
- const combinedPurpose = retryPurpose && retryPurpose === 'pull' ? 'pull' : purpose;
7380
- const handle = setTimeout(() => syncAndRetry(combinedPurpose, retryNum + 1), [0, 5, 15][retryNum] * MINUTES);
7381
- // Cancel the previous retryHandle if it exists to avoid scheduling loads of retries.
7382
- if (retryHandle)
7383
- clearTimeout(retryHandle);
7384
- retryHandle = handle;
7385
- retryPurpose = combinedPurpose;
7435
+ else if (retryNum < 5) {
7436
+ // Mimic service worker sync event but a bit more eager: retry 4 times
7437
+ // * first retry after 20 seconds
7438
+ // * second retry 40 seconds later
7439
+ // * third retry 5 minutes later
7440
+ // * last retry 15 minutes later
7441
+ const retryIn = [0, 20, 40, 300, 900][retryNum] * SECONDS;
7442
+ nextRetryTime = Date.now() + retryIn;
7443
+ syncStartTime = 0;
7444
+ setTimeout(() => syncAndRetry(retryNum + 1), retryIn);
7445
+ }
7446
+ else {
7447
+ ongoingSync = false;
7448
+ nextRetryTime = 0;
7449
+ syncStartTime = 0;
7386
7450
  }
7387
7451
  });
7388
7452
  }, 0);
7389
7453
  }
7454
+ let pullSignalled = false;
7455
+ let pushSignalled = false;
7456
+ let ongoingSync = false;
7457
+ const consumer = (purpose) => {
7458
+ if (cancelToken.cancelled)
7459
+ return;
7460
+ if (purpose === 'pull') {
7461
+ pullSignalled = true;
7462
+ }
7463
+ if (purpose === 'push') {
7464
+ pushSignalled = true;
7465
+ }
7466
+ if (ongoingSync) {
7467
+ if (nextRetryTime) {
7468
+ console.debug(`Sync is paused until ${new Date(nextRetryTime).toISOString()} due to error in last sync attempt`);
7469
+ }
7470
+ else if (syncStartTime > 0 && Date.now() - syncStartTime > 20 * SECONDS) {
7471
+ console.debug(`An existing sync operation is taking more than 20 seconds. Will resync when done.`);
7472
+ }
7473
+ return;
7474
+ }
7475
+ ongoingSync = true;
7476
+ syncAndRetry();
7477
+ };
7390
7478
  const start = () => {
7391
7479
  // Sync eagerly whenever a change has happened (+ initially when there's no syncState yet)
7392
7480
  // This initial subscribe will also trigger an sync also now.
7393
7481
  console.debug('Starting LocalSyncWorker', db.localSyncEvent['id']);
7394
7482
  localSyncEventSubscription = db.localSyncEvent.subscribe(({ purpose }) => {
7395
- try {
7396
- syncAndRetry(purpose || 'pull');
7397
- }
7398
- catch (err) {
7399
- console.error('What-the....', err);
7400
- }
7483
+ consumer(purpose || 'pull');
7401
7484
  });
7402
- //setTimeout(()=>db.localSyncEvent.next({}), 5000);
7403
7485
  };
7404
7486
  const stop = () => {
7405
7487
  console.debug('Stopping LocalSyncWorker');
@@ -8191,7 +8273,7 @@ function dexieCloud(dexie) {
8191
8273
  const syncComplete = new Subject();
8192
8274
  dexie.cloud = {
8193
8275
  // @ts-ignore
8194
- version: "4.1.0-beta.44",
8276
+ version: "4.1.0-beta.45",
8195
8277
  options: Object.assign({}, DEFAULT_OPTIONS),
8196
8278
  schema: null,
8197
8279
  get currentUserId() {
@@ -8509,7 +8591,7 @@ function dexieCloud(dexie) {
8509
8591
  }
8510
8592
  }
8511
8593
  // @ts-ignore
8512
- dexieCloud.version = "4.1.0-beta.44";
8594
+ dexieCloud.version = "4.1.0-beta.45";
8513
8595
  Dexie.Cloud = dexieCloud;
8514
8596
 
8515
8597
  // In case the SW lives for a while, let it reuse already opened connections: