dexie-cloud-addon 4.1.0-alpha.9 → 4.1.0-beta.25

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.
Files changed (37) hide show
  1. package/dist/modern/DexieCloudOptions.d.ts +3 -0
  2. package/dist/modern/TSON.d.ts +1 -1
  3. package/dist/modern/db/entities/PersistedSyncState.d.ts +1 -0
  4. package/dist/modern/define-ydoc-trigger.d.ts +2 -0
  5. package/dist/modern/dexie-cloud-addon.d.ts +1 -0
  6. package/dist/modern/dexie-cloud-addon.js +623 -260
  7. package/dist/modern/dexie-cloud-addon.js.map +1 -1
  8. package/dist/modern/dexie-cloud-addon.min.js +1 -1
  9. package/dist/modern/dexie-cloud-addon.min.js.map +1 -1
  10. package/dist/modern/getInvitesObservable.d.ts +11 -11
  11. package/dist/modern/service-worker.js +465 -258
  12. package/dist/modern/service-worker.js.map +1 -1
  13. package/dist/modern/service-worker.min.js +1 -1
  14. package/dist/modern/service-worker.min.js.map +1 -1
  15. package/dist/modern/yjs/YDexieCloudSyncState.d.ts +0 -1
  16. package/dist/modern/yjs/applyYMessages.d.ts +5 -1
  17. package/dist/modern/yjs/reopenDocSignal.d.ts +10 -0
  18. package/dist/modern/yjs/updateYSyncStates.d.ts +1 -1
  19. package/dist/umd/DexieCloudOptions.d.ts +3 -0
  20. package/dist/umd/TSON.d.ts +1 -1
  21. package/dist/umd/db/entities/PersistedSyncState.d.ts +1 -0
  22. package/dist/umd/define-ydoc-trigger.d.ts +2 -0
  23. package/dist/umd/dexie-cloud-addon.d.ts +1 -0
  24. package/dist/umd/dexie-cloud-addon.js +621 -257
  25. package/dist/umd/dexie-cloud-addon.js.map +1 -1
  26. package/dist/umd/dexie-cloud-addon.min.js +1 -1
  27. package/dist/umd/dexie-cloud-addon.min.js.map +1 -1
  28. package/dist/umd/getInvitesObservable.d.ts +11 -11
  29. package/dist/umd/service-worker.js +463 -256
  30. package/dist/umd/service-worker.js.map +1 -1
  31. package/dist/umd/service-worker.min.js +1 -1
  32. package/dist/umd/service-worker.min.js.map +1 -1
  33. package/dist/umd/yjs/YDexieCloudSyncState.d.ts +0 -1
  34. package/dist/umd/yjs/applyYMessages.d.ts +5 -1
  35. package/dist/umd/yjs/reopenDocSignal.d.ts +10 -0
  36. package/dist/umd/yjs/updateYSyncStates.d.ts +1 -1
  37. package/package.json +6 -5
@@ -8,7 +8,7 @@
8
8
  *
9
9
  * ==========================================================================
10
10
  *
11
- * Version 4.1.0-alpha.9, Tue Oct 15 2024
11
+ * Version 4.1.0-beta.25, Wed Dec 04 2024
12
12
  *
13
13
  * https://dexie.org
14
14
  *
@@ -1768,8 +1768,8 @@
1768
1768
  });
1769
1769
  }
1770
1770
  function registerPeriodicSyncEvent(db) {
1771
- var _a;
1772
1771
  return __awaiter(this, void 0, void 0, function* () {
1772
+ var _a;
1773
1773
  try {
1774
1774
  // Register periodicSync event to SW:
1775
1775
  // @ts-ignore
@@ -1834,8 +1834,8 @@
1834
1834
  return btoa(strs.join(""));
1835
1835
  };
1836
1836
 
1837
- function computeRealmSetHash({ realms, inviteRealms, }) {
1838
- return __awaiter(this, void 0, void 0, function* () {
1837
+ function computeRealmSetHash(_a) {
1838
+ return __awaiter(this, arguments, void 0, function* ({ realms, inviteRealms, }) {
1839
1839
  const data = JSON.stringify([
1840
1840
  ...realms.map((realmId) => ({ realmId, accepted: true })),
1841
1841
  ...inviteRealms.map((realmId) => ({ realmId, accepted: false })),
@@ -1871,8 +1871,8 @@
1871
1871
  return concat.apply([], a);
1872
1872
  }
1873
1873
 
1874
- function listClientChanges(mutationTables, db, { since = {}, limit = Infinity } = {}) {
1875
- return __awaiter(this, void 0, void 0, function* () {
1874
+ function listClientChanges(mutationTables_1, db_1) {
1875
+ return __awaiter(this, arguments, void 0, function* (mutationTables, db, { since = {}, limit = Infinity } = {}) {
1876
1876
  const allMutsOnTables = yield Promise.all(mutationTables.map((mutationTable) => __awaiter(this, void 0, void 0, function* () {
1877
1877
  const tableName = getTableFromMutationTable(mutationTable.name);
1878
1878
  const lastRevision = since[tableName];
@@ -2757,13 +2757,20 @@
2757
2757
  function encodeYMessage(msg) {
2758
2758
  const encoder = new Encoder();
2759
2759
  writeVarString(encoder, msg.type);
2760
- writeVarString(encoder, msg.table);
2761
- writeVarString(encoder, msg.prop);
2760
+ if ('table' in msg)
2761
+ writeVarString(encoder, msg.table);
2762
+ if ('prop' in msg)
2763
+ writeVarString(encoder, msg.prop);
2762
2764
  switch (msg.type) {
2763
2765
  case 'u-ack':
2764
2766
  case 'u-reject':
2765
2767
  writeBigUint64(encoder, BigInt(msg.i));
2766
2768
  break;
2769
+ case 'outdated-server-rev':
2770
+ break;
2771
+ case 'y-complete-sync-done':
2772
+ writeVarString(encoder, msg.yServerRev);
2773
+ break;
2767
2774
  default:
2768
2775
  writeAny(encoder, msg.k);
2769
2776
  switch (msg.type) {
@@ -3107,6 +3114,12 @@
3107
3114
  function decodeYMessage(a) {
3108
3115
  const decoder = new Decoder(a);
3109
3116
  const type = readVarString(decoder);
3117
+ if (type === 'outdated-server-rev') {
3118
+ return { type };
3119
+ }
3120
+ if (type === 'y-complete-sync-done') {
3121
+ return { type, yServerRev: readVarString(decoder) };
3122
+ }
3110
3123
  const table = readVarString(decoder);
3111
3124
  const prop = readVarString(decoder);
3112
3125
  switch (type) {
@@ -3519,8 +3532,8 @@
3519
3532
  }
3520
3533
 
3521
3534
  function loadAccessToken(db) {
3522
- var _a, _b, _c;
3523
3535
  return __awaiter(this, void 0, void 0, function* () {
3536
+ var _a, _b, _c;
3524
3537
  const currentUser = yield db.getCurrentUser();
3525
3538
  const { accessToken, accessTokenExpiration, refreshToken, refreshTokenExpiration, claims, } = currentUser;
3526
3539
  if (!accessToken)
@@ -4350,8 +4363,8 @@
4350
4363
  // seconds (given that there is a Ratelimit-Reset header).
4351
4364
  let syncRatelimitDelays = new WeakMap();
4352
4365
  function checkSyncRateLimitDelay(db) {
4353
- var _a, _b;
4354
4366
  return __awaiter(this, void 0, void 0, function* () {
4367
+ var _a, _b;
4355
4368
  const delatMilliseconds = ((_b = (_a = syncRatelimitDelays.get(db)) === null || _a === void 0 ? void 0 : _a.getTime()) !== null && _b !== void 0 ? _b : 0) - Date.now();
4356
4369
  if (delatMilliseconds > 0) {
4357
4370
  console.debug(`Stalling sync request ${delatMilliseconds} ms to spare ratelimits`);
@@ -4412,6 +4425,7 @@
4412
4425
  lastPull: syncState
4413
4426
  ? {
4414
4427
  serverRevision: syncState.serverRevision,
4428
+ yServerRevision: syncState.yServerRevision,
4415
4429
  realms: syncState.realms,
4416
4430
  inviteRealms: syncState.inviteRealms,
4417
4431
  }
@@ -4756,12 +4770,15 @@
4756
4770
 
4757
4771
  function applyYServerMessages(yMessages, db) {
4758
4772
  return __awaiter(this, void 0, void 0, function* () {
4759
- const result = {};
4773
+ var _a;
4774
+ const receivedUntils = {};
4775
+ let resyncNeeded = false;
4776
+ let yServerRevision;
4760
4777
  for (const m of yMessages) {
4761
4778
  switch (m.type) {
4762
4779
  case 'u-s': {
4763
4780
  const utbl = getUpdatesTable(db, m.table, m.prop);
4764
- result[utbl.name] = yield utbl.add({
4781
+ receivedUntils[utbl.name] = yield utbl.add({
4765
4782
  k: m.k,
4766
4783
  u: m.u,
4767
4784
  });
@@ -4786,7 +4803,24 @@
4786
4803
  // See my question in https://discuss.yjs.dev/t/generate-an-inverse-update/2765
4787
4804
  console.debug(`Y update rejected. Deleting it.`);
4788
4805
  const utbl = getUpdatesTable(db, m.table, m.prop);
4789
- yield utbl.delete(m.i);
4806
+ // Delete the rejected update and all local updates since (avoid holes in the CRDT)
4807
+ // and destroy it's open document if there is one.
4808
+ const primaryKey = (_a = (yield utbl.get(m.i))) === null || _a === void 0 ? void 0 : _a.k;
4809
+ if (primaryKey != null) {
4810
+ yield db.transaction('rw', utbl, (tx) => {
4811
+ // @ts-ignore
4812
+ tx.idbtrans._rejecting_y_ypdate = true; // Inform ydoc triggers that we delete because of a rejection and not GC
4813
+ return utbl
4814
+ .where('i')
4815
+ .aboveOrEqual(m.i)
4816
+ .filter((u) => Dexie.cmp(u.k, primaryKey) === 0 && ((u.f || 0) & 1) === 1)
4817
+ .delete();
4818
+ });
4819
+ // Destroy active doc
4820
+ const activeDoc = Dexie.DexieYProvider.getDocCache(db.dx).find(m.table, primaryKey, m.prop);
4821
+ if (activeDoc)
4822
+ activeDoc.destroy(); // Destroy the document so that editors don't continue to work on it
4823
+ }
4790
4824
  break;
4791
4825
  }
4792
4826
  case 'in-sync': {
@@ -4796,15 +4830,26 @@
4796
4830
  }
4797
4831
  break;
4798
4832
  }
4833
+ case 'y-complete-sync-done': {
4834
+ yServerRevision = m.yServerRev;
4835
+ break;
4836
+ }
4837
+ case 'outdated-server-rev':
4838
+ resyncNeeded = true;
4839
+ break;
4799
4840
  }
4800
4841
  }
4801
- return result;
4842
+ return {
4843
+ receivedUntils,
4844
+ resyncNeeded,
4845
+ yServerRevision
4846
+ };
4802
4847
  });
4803
4848
  }
4804
4849
 
4805
- function updateYSyncStates(lastUpdateIdsBeforeSync, receivedUntilsAfterSync, db, serverRevision) {
4806
- var _a, _b, _c, _d, _e;
4850
+ function updateYSyncStates(lastUpdateIdsBeforeSync, receivedUntilsAfterSync, db) {
4807
4851
  return __awaiter(this, void 0, void 0, function* () {
4852
+ var _a, _b, _c, _d, _e;
4808
4853
  // We want to update unsentFrom for each yTable to the value specified in first argument
4809
4854
  // because we got those values before we synced with server and here we are back from server
4810
4855
  // that has successfully received all those messages - no matter if the last update was a client or server update,
@@ -4851,14 +4896,12 @@
4851
4896
  yield db.table(yTable).add({
4852
4897
  i: DEXIE_CLOUD_SYNCER_ID,
4853
4898
  unsentFrom,
4854
- receivedUntil,
4855
- serverRev: serverRevision,
4899
+ receivedUntil
4856
4900
  });
4857
4901
  }
4858
4902
  else {
4859
4903
  state.unsentFrom = Math.max(unsentFrom, state.unsentFrom || 1);
4860
4904
  state.receivedUntil = Math.max(receivedUntil, state.receivedUntil || 0);
4861
- state.serverRev = serverRevision;
4862
4905
  yield db.table(yTable).put(state);
4863
4906
  }
4864
4907
  }));
@@ -4869,8 +4912,8 @@
4869
4912
  const BINSTREAM_TYPE_REALMID = 1;
4870
4913
  const BINSTREAM_TYPE_TABLE_AND_PROP = 2;
4871
4914
  const BINSTREAM_TYPE_DOCUMENT = 3;
4872
- function downloadYDocsFromServer(db, databaseUrl, { yDownloadedRealms, realms }) {
4873
- return __awaiter(this, void 0, void 0, function* () {
4915
+ function downloadYDocsFromServer(db_1, databaseUrl_1, _a) {
4916
+ return __awaiter(this, arguments, void 0, function* (db, databaseUrl, { yDownloadedRealms, realms }) {
4874
4917
  if (yDownloadedRealms &&
4875
4918
  realms &&
4876
4919
  realms.every((realmId) => yDownloadedRealms[realmId] === '*')) {
@@ -4985,8 +5028,7 @@
4985
5028
 
4986
5029
  const CURRENT_SYNC_WORKER = 'currentSyncWorker';
4987
5030
  function sync(db, options, schema, syncOptions) {
4988
- return _sync
4989
- .apply(this, arguments)
5031
+ return _sync(db, options, schema, syncOptions)
4990
5032
  .then((result) => {
4991
5033
  if (!(syncOptions === null || syncOptions === void 0 ? void 0 : syncOptions.justCheckIfNeeded)) { // && syncOptions?.purpose !== 'push') {
4992
5034
  db.syncStateChangedEvent.next({
@@ -5028,11 +5070,11 @@
5028
5070
  return Promise.reject(error);
5029
5071
  }));
5030
5072
  }
5031
- function _sync(db, options, schema, { isInitialSync, cancelToken, justCheckIfNeeded, purpose } = {
5032
- isInitialSync: false,
5033
- }) {
5034
- var _a;
5035
- return __awaiter(this, void 0, void 0, function* () {
5073
+ function _sync(db_1, options_1, schema_1) {
5074
+ return __awaiter(this, arguments, void 0, function* (db, options, schema, { isInitialSync, cancelToken, justCheckIfNeeded, purpose } = {
5075
+ isInitialSync: false,
5076
+ }) {
5077
+ var _a;
5036
5078
  if (!justCheckIfNeeded) {
5037
5079
  console.debug('SYNC STARTED', { isInitialSync, purpose });
5038
5080
  }
@@ -5049,7 +5091,7 @@
5049
5091
  // Prepare for syncification by modifying locally unauthorized objects:
5050
5092
  //
5051
5093
  const persistedSyncState = yield db.getPersistedSyncState();
5052
- const readyForSyncification = !isInitialSync && currentUser.isLoggedIn;
5094
+ const readyForSyncification = currentUser.isLoggedIn;
5053
5095
  const tablesToSyncify = readyForSyncification
5054
5096
  ? getTablesToSyncify(db, persistedSyncState)
5055
5097
  : [];
@@ -5074,7 +5116,7 @@
5074
5116
  const [clientChangeSet, syncState, baseRevs, { yMessages, lastUpdateIds }] = yield db.transaction('r', db.tables, () => __awaiter(this, void 0, void 0, function* () {
5075
5117
  const syncState = yield db.getPersistedSyncState();
5076
5118
  const baseRevs = yield db.$baseRevs.toArray();
5077
- let clientChanges = yield listClientChanges(mutationTables);
5119
+ let clientChanges = yield listClientChanges(mutationTables, db);
5078
5120
  const yResults = yield listYClientMessagesAndStateVector(db, tablesToSync);
5079
5121
  throwIfCancelled(cancelToken);
5080
5122
  if (doSyncify) {
@@ -5194,6 +5236,7 @@
5194
5236
  newSyncState.realms = res.realms;
5195
5237
  newSyncState.inviteRealms = res.inviteRealms;
5196
5238
  newSyncState.serverRevision = res.serverRevision;
5239
+ newSyncState.yServerRevision = res.serverRevision;
5197
5240
  newSyncState.timestamp = new Date();
5198
5241
  delete newSyncState.error;
5199
5242
  const filteredChanges = filterServerChangesThroughAddedClientChanges(res.changes, addedClientChanges);
@@ -5205,11 +5248,17 @@
5205
5248
  //
5206
5249
  // apply yMessages
5207
5250
  //
5208
- const receivedUntils = yield applyYServerMessages(res.yMessages, db);
5251
+ const { receivedUntils, resyncNeeded, yServerRevision } = yield applyYServerMessages(res.yMessages, db);
5252
+ if (yServerRevision) {
5253
+ newSyncState.yServerRevision = yServerRevision;
5254
+ }
5209
5255
  //
5210
5256
  // update Y SyncStates
5211
5257
  //
5212
- yield updateYSyncStates(lastUpdateIds, receivedUntils, db, res.serverRevision);
5258
+ yield updateYSyncStates(lastUpdateIds, receivedUntils, db);
5259
+ if (resyncNeeded) {
5260
+ newSyncState.yDownloadedRealms = {}; // Will trigger a full download of Y-documents below...
5261
+ }
5213
5262
  }
5214
5263
  //
5215
5264
  // Update regular syncState
@@ -5343,8 +5392,8 @@
5343
5392
  event.next(null);
5344
5393
  }
5345
5394
  function consumeQueue() {
5346
- var _a, _b, _c, _d, _e, _f;
5347
5395
  return __awaiter(this, void 0, void 0, function* () {
5396
+ var _a, _b, _c, _d, _e, _f;
5348
5397
  while (queue.length > 0) {
5349
5398
  const msg = queue.shift();
5350
5399
  try {
@@ -5502,7 +5551,7 @@
5502
5551
  };
5503
5552
  }
5504
5553
 
5505
- const wm$1 = new WeakMap();
5554
+ const wm$2 = new WeakMap();
5506
5555
  const DEXIE_CLOUD_SCHEMA = {
5507
5556
  members: '@id, [userId+realmId], [email+realmId], realmId',
5508
5557
  roles: '[realmId+name]',
@@ -5516,7 +5565,7 @@
5516
5565
  function DexieCloudDB(dx) {
5517
5566
  if ('vip' in dx)
5518
5567
  dx = dx['vip']; // Avoid race condition. Always map to a vipped dexie that don't block during db.on.ready().
5519
- let db = wm$1.get(dx.cloud);
5568
+ let db = wm$2.get(dx.cloud);
5520
5569
  if (!db) {
5521
5570
  const localSyncEvent = new rxjs.Subject();
5522
5571
  let syncStateChangedEvent = new BroadcastedAndLocalEvent(`syncstatechanged-${dx.name}`);
@@ -5604,7 +5653,7 @@
5604
5653
  Object.assign(db, helperMethods);
5605
5654
  db.messageConsumer = MessagesFromServerConsumer(db);
5606
5655
  db.messageProducer = new rxjs.Subject();
5607
- wm$1.set(dx.cloud, db);
5656
+ wm$2.set(dx.cloud, db);
5608
5657
  }
5609
5658
  return db;
5610
5659
  }
@@ -5615,10 +5664,10 @@
5615
5664
  }
5616
5665
 
5617
5666
  // Emulate true-private property db. Why? So it's not stored in DB.
5618
- const wm = new WeakMap();
5667
+ const wm$1 = new WeakMap();
5619
5668
  class AuthPersistedContext {
5620
5669
  constructor(db, userLogin) {
5621
- wm.set(this, db);
5670
+ wm$1.set(this, db);
5622
5671
  Object.assign(this, userLogin);
5623
5672
  }
5624
5673
  static load(db, userId) {
@@ -5635,7 +5684,7 @@
5635
5684
  }
5636
5685
  save() {
5637
5686
  return __awaiter(this, void 0, void 0, function* () {
5638
- const db = wm.get(this);
5687
+ const db = wm$1.get(this);
5639
5688
  db.table("$logins").put(this);
5640
5689
  });
5641
5690
  }
@@ -5659,8 +5708,8 @@
5659
5708
  }
5660
5709
  });
5661
5710
  }
5662
- function _logout(db, { deleteUnsyncedData = false } = {}) {
5663
- return __awaiter(this, void 0, void 0, function* () {
5711
+ function _logout(db_1) {
5712
+ return __awaiter(this, arguments, void 0, function* (db, { deleteUnsyncedData = false } = {}) {
5664
5713
  // Clear the database without emptying configuration options.
5665
5714
  const [numUnsynced, loggedOut] = yield db.dx.transaction('rw', db.dx.tables, (tx) => __awaiter(this, void 0, void 0, function* () {
5666
5715
  // @ts-ignore
@@ -5698,11 +5747,11 @@
5698
5747
 
5699
5748
  function otpFetchTokenCallback(db) {
5700
5749
  const { userInteraction } = db.cloud;
5701
- return function otpAuthenticate({ public_key, hints }) {
5702
- var _a;
5703
- return __awaiter(this, void 0, void 0, function* () {
5750
+ return function otpAuthenticate(_a) {
5751
+ return __awaiter(this, arguments, void 0, function* ({ public_key, hints }) {
5752
+ var _b;
5704
5753
  let tokenRequest;
5705
- const url = (_a = db.cloud.options) === null || _a === void 0 ? void 0 : _a.databaseUrl;
5754
+ const url = (_b = db.cloud.options) === null || _b === void 0 ? void 0 : _b.databaseUrl;
5706
5755
  if (!url)
5707
5756
  throw new Error(`No database URL given.`);
5708
5757
  if ((hints === null || hints === void 0 ? void 0 : hints.grant_type) === 'demo') {
@@ -5868,8 +5917,8 @@
5868
5917
  }
5869
5918
 
5870
5919
  function login(db, hints) {
5871
- var _a;
5872
5920
  return __awaiter(this, void 0, void 0, function* () {
5921
+ var _a;
5873
5922
  const currentUser = yield db.getCurrentUser();
5874
5923
  const origUserId = currentUser.userId;
5875
5924
  if (currentUser.isLoggedIn && (!hints || (!hints.email && !hints.userId))) {
@@ -6306,8 +6355,7 @@
6306
6355
  outstandingTransactions.next(outstandingTransactions.value);
6307
6356
  };
6308
6357
  const txComplete = () => {
6309
- if (tx.mutationsAdded &&
6310
- !isEagerSyncDisabled(db)) {
6358
+ if (tx.mutationsAdded && !isEagerSyncDisabled(db)) {
6311
6359
  triggerSync(db, 'push');
6312
6360
  }
6313
6361
  removeTransaction();
@@ -6389,26 +6437,107 @@
6389
6437
  : mutateAndLog(req);
6390
6438
  } }));
6391
6439
  function mutateAndLog(req) {
6440
+ var _a, _b;
6392
6441
  const trans = req.trans;
6393
- trans.mutationsAdded = true;
6442
+ const unsyncedProps = (_b = (_a = db.cloud.options) === null || _a === void 0 ? void 0 : _a.unsyncedProperties) === null || _b === void 0 ? void 0 : _b[tableName];
6394
6443
  const { txid, currentUser: { userId }, } = trans;
6395
6444
  const { type } = req;
6396
6445
  const opNo = ++trans.opCount;
6446
+ function stripChangeSpec(changeSpec) {
6447
+ if (!unsyncedProps)
6448
+ return changeSpec;
6449
+ let rv = changeSpec;
6450
+ for (const keyPath of Object.keys(changeSpec)) {
6451
+ if (unsyncedProps.some((p) => keyPath === p || keyPath.startsWith(p + '.'))) {
6452
+ if (rv === changeSpec)
6453
+ rv = Object.assign({}, changeSpec); // clone on demand
6454
+ delete rv[keyPath];
6455
+ }
6456
+ }
6457
+ return rv;
6458
+ }
6397
6459
  return table.mutate(req).then((res) => {
6460
+ var _a;
6398
6461
  const { numFailures: hasFailures, failures } = res;
6399
6462
  let keys = type === 'delete' ? req.keys : res.results;
6400
6463
  let values = 'values' in req ? req.values : [];
6401
- let updates = 'updates' in req && req.updates;
6464
+ let changeSpec = 'changeSpec' in req ? req.changeSpec : undefined;
6465
+ let updates = 'updates' in req ? req.updates : undefined;
6402
6466
  if (hasFailures) {
6403
6467
  keys = keys.filter((_, idx) => !failures[idx]);
6404
6468
  values = values.filter((_, idx) => !failures[idx]);
6405
6469
  }
6470
+ if (unsyncedProps) {
6471
+ // Filter out unsynced properties
6472
+ values = values.map((value) => {
6473
+ const newValue = Object.assign({}, value);
6474
+ for (const prop of unsyncedProps) {
6475
+ delete newValue[prop];
6476
+ }
6477
+ return newValue;
6478
+ });
6479
+ if (changeSpec) {
6480
+ // modify operation with criteria and changeSpec.
6481
+ // We must strip out unsynced properties from changeSpec.
6482
+ // We deal with criteria later.
6483
+ changeSpec = stripChangeSpec(changeSpec);
6484
+ if (Object.keys(changeSpec).length === 0) {
6485
+ // Nothing to change on server
6486
+ return res;
6487
+ }
6488
+ }
6489
+ if (updates) {
6490
+ let strippedChangeSpecs = updates.changeSpecs.map(stripChangeSpec);
6491
+ let newUpdates = {
6492
+ keys: [],
6493
+ changeSpecs: [],
6494
+ };
6495
+ const validKeys = new Dexie.RangeSet();
6496
+ let anyChangeSpecBecameEmpty = false;
6497
+ for (let i = 0, l = strippedChangeSpecs.length; i < l; ++i) {
6498
+ if (Object.keys(strippedChangeSpecs[i]).length > 0) {
6499
+ newUpdates.keys.push(updates.keys[i]);
6500
+ newUpdates.changeSpecs.push(strippedChangeSpecs[i]);
6501
+ validKeys.addKey(updates.keys[i]);
6502
+ }
6503
+ else {
6504
+ anyChangeSpecBecameEmpty = true;
6505
+ }
6506
+ }
6507
+ updates = newUpdates;
6508
+ if (anyChangeSpecBecameEmpty) {
6509
+ // Some keys were stripped. We must also strip them from keys and values
6510
+ let newKeys = [];
6511
+ let newValues = [];
6512
+ for (let i = 0, l = keys.length; i < l; ++i) {
6513
+ if (validKeys.hasKey(keys[i])) {
6514
+ newKeys.push(keys[i]);
6515
+ newValues.push(values[i]);
6516
+ }
6517
+ }
6518
+ keys = newKeys;
6519
+ values = newValues;
6520
+ }
6521
+ }
6522
+ }
6406
6523
  const ts = Date.now();
6407
6524
  // Canonicalize req.criteria.index to null if it's on the primary key.
6408
- const criteria = 'criteria' in req && req.criteria
6525
+ let criteria = 'criteria' in req && req.criteria
6409
6526
  ? Object.assign(Object.assign({}, req.criteria), { index: req.criteria.index === schema.primaryKey.keyPath // Use null to inform server that criteria is on primary key
6410
6527
  ? null // This will disable the server from trying to log consistent operations where it shouldnt.
6411
6528
  : req.criteria.index }) : undefined;
6529
+ if (unsyncedProps && (criteria === null || criteria === void 0 ? void 0 : criteria.index)) {
6530
+ const keyPaths = (_a = schema.indexes.find((idx) => idx.name === criteria.index)) === null || _a === void 0 ? void 0 : _a.keyPath;
6531
+ const involvedProps = keyPaths
6532
+ ? typeof keyPaths === 'string'
6533
+ ? [keyPaths]
6534
+ : keyPaths
6535
+ : [];
6536
+ if (involvedProps.some((p) => unsyncedProps === null || unsyncedProps === void 0 ? void 0 : unsyncedProps.includes(p))) {
6537
+ // Don't log criteria on unsynced properties as the server could not test them.
6538
+ criteria = undefined;
6539
+ }
6540
+ }
6412
6541
  const mut = req.type === 'delete'
6413
6542
  ? {
6414
6543
  type: 'delete',
@@ -6429,7 +6558,7 @@
6429
6558
  userId,
6430
6559
  values,
6431
6560
  }
6432
- : criteria && req.changeSpec
6561
+ : criteria && changeSpec
6433
6562
  ? {
6434
6563
  // Common changeSpec for all keys
6435
6564
  type: 'modify',
@@ -6437,37 +6566,51 @@
6437
6566
  opNo,
6438
6567
  keys,
6439
6568
  criteria,
6440
- changeSpec: req.changeSpec,
6569
+ changeSpec,
6441
6570
  txid,
6442
6571
  userId,
6443
6572
  }
6444
- : updates
6573
+ : changeSpec
6445
6574
  ? {
6446
- // One changeSpec per key
6575
+ // In case criteria involved an unsynced property, we go for keys instead.
6447
6576
  type: 'update',
6448
6577
  ts,
6449
6578
  opNo,
6450
- keys: updates.keys,
6451
- changeSpecs: updates.changeSpecs,
6452
- txid,
6453
- userId,
6454
- }
6455
- : {
6456
- type: 'upsert',
6457
- ts,
6458
- opNo,
6459
6579
  keys,
6460
- values,
6580
+ changeSpecs: keys.map(() => changeSpec),
6461
6581
  txid,
6462
6582
  userId,
6463
- };
6583
+ }
6584
+ : updates
6585
+ ? {
6586
+ // One changeSpec per key
6587
+ type: 'update',
6588
+ ts,
6589
+ opNo,
6590
+ keys: updates.keys,
6591
+ changeSpecs: updates.changeSpecs,
6592
+ txid,
6593
+ userId,
6594
+ }
6595
+ : {
6596
+ type: 'upsert',
6597
+ ts,
6598
+ opNo,
6599
+ keys,
6600
+ values,
6601
+ txid,
6602
+ userId,
6603
+ };
6464
6604
  if ('isAdditionalChunk' in req && req.isAdditionalChunk) {
6465
6605
  mut.isAdditionalChunk = true;
6466
6606
  }
6467
6607
  return keys.length > 0 || criteria
6468
6608
  ? mutsTable
6469
6609
  .mutate({ type: 'add', trans, values: [mut] }) // Log entry
6470
- .then(() => res) // Return original response
6610
+ .then(() => {
6611
+ trans.mutationsAdded = true; // Mark transaction as having added mutations to trigger eager sync
6612
+ return res; // Return original response
6613
+ })
6471
6614
  : res;
6472
6615
  });
6473
6616
  }
@@ -6551,10 +6694,18 @@
6551
6694
  };
6552
6695
  }
6553
6696
 
6697
+ function performGuardedJob(db, jobName, job) {
6698
+ if (typeof navigator === 'undefined' || !navigator.locks) {
6699
+ // No support for guarding jobs. IE11, node.js, etc.
6700
+ return job();
6701
+ }
6702
+ return navigator.locks.request(db.name + '|' + jobName, () => job());
6703
+ }
6704
+
6554
6705
  function performInitialSync(db, cloudOptions, cloudSchema) {
6555
6706
  return __awaiter(this, void 0, void 0, function* () {
6556
6707
  console.debug('Performing initial sync');
6557
- yield sync(db, cloudOptions, cloudSchema, { isInitialSync: true });
6708
+ yield performGuardedJob(db, CURRENT_SYNC_WORKER, () => sync(db, cloudOptions, cloudSchema, { isInitialSync: true }));
6558
6709
  console.debug('Done initial sync');
6559
6710
  });
6560
6711
  }
@@ -6641,38 +6792,42 @@
6641
6792
  updatesTable: p.updatesTable,
6642
6793
  }))));
6643
6794
  return rxjs.merge(...yTableRecords.map(({ table, ydocProp, updatesTable }) => {
6644
- let currentUnsentFrom = 1;
6645
- return Dexie.liveQuery(() => __awaiter(this, void 0, void 0, function* () {
6646
- const yTbl = db.table(updatesTable);
6647
- const unsentFrom = yield yTbl
6648
- .where({ i: DEXIE_CLOUD_SYNCER_ID })
6649
- .first()
6650
- .then((syncer) => (syncer === null || syncer === void 0 ? void 0 : syncer.unsentFrom) || 1);
6651
- currentUnsentFrom = Math.max(currentUnsentFrom, unsentFrom);
6652
- const addedUpdates = yield listUpdatesSince(yTbl, currentUnsentFrom);
6653
- // Update currentUnsentFrom to only listen for updates that will be newer than the ones we emitted.
6654
- currentUnsentFrom = Math.max(currentUnsentFrom, ...addedUpdates.map((update) => update.i + 1));
6655
- return addedUpdates
6656
- .filter((update) => update.f && update.f & 1) // Only include local updates
6657
- .map((update) => {
6658
- return {
6659
- type: 'u-c',
6660
- table,
6661
- prop: ydocProp,
6662
- k: update.k,
6663
- u: update.u,
6664
- i: update.i,
6665
- };
6666
- });
6795
+ // Per updates table (table+prop combo), we first read syncer.unsentFrom,
6796
+ // and then start listening for updates since that number.
6797
+ const yTbl = db.table(updatesTable);
6798
+ return rxjs.from(yTbl.get(DEXIE_CLOUD_SYNCER_ID)).pipe(rxjs.switchMap((syncer) => {
6799
+ let currentUnsentFrom = (syncer === null || syncer === void 0 ? void 0 : syncer.unsentFrom) || 1;
6800
+ return rxjs.from(Dexie.liveQuery(() => __awaiter(this, void 0, void 0, function* () {
6801
+ const addedUpdates = yield listUpdatesSince(yTbl, currentUnsentFrom);
6802
+ return addedUpdates
6803
+ .filter((update) => update.f && update.f & 1) // Only include local updates
6804
+ .map((update) => {
6805
+ return {
6806
+ type: 'u-c',
6807
+ table,
6808
+ prop: ydocProp,
6809
+ k: update.k,
6810
+ u: update.u,
6811
+ i: update.i,
6812
+ };
6813
+ });
6814
+ }))).pipe(rxjs.tap((addedUpdates) => {
6815
+ // Update currentUnsentFrom to only listen for updates that will be newer than the ones we emitted.
6816
+ // (Before, we did this within the liveQuery, but that caused a bug because
6817
+ // a cancelled emittion of a liveQuery would update the currentUnsentFrom without
6818
+ // emitting anything, leading to that we jumped over some updates. Here we update it
6819
+ // after the liveQuery has emitted its updates)
6820
+ if (addedUpdates.length > 0) {
6821
+ currentUnsentFrom = addedUpdates.at(-1).i + 1;
6822
+ }
6823
+ }));
6667
6824
  }));
6668
6825
  })).pipe(
6669
6826
  // Flatten the array of messages.
6670
6827
  // If messageProducer emits empty array, nothing is emitted
6671
6828
  // but if messageProducer emits array of messages, they are
6672
6829
  // emitted one by one.
6673
- rxjs.mergeMap((messages) => messages), rxjs.tap(message => {
6674
- console.debug('dexie-cloud emitting y-c', message);
6675
- }));
6830
+ rxjs.mergeMap((messages) => messages));
6676
6831
  }
6677
6832
 
6678
6833
  function getAwarenessLibrary(db) {
@@ -6685,6 +6840,23 @@
6685
6840
  const awarenessWeakMap = new WeakMap();
6686
6841
  const getDocAwareness = (doc) => awarenessWeakMap.get(doc);
6687
6842
 
6843
+ const wm = new WeakMap();
6844
+ /** A property (package-private) on Y.Doc that is used
6845
+ * to signal that the server wants us to send a 'doc-open' message
6846
+ * to the server for this document.
6847
+ *
6848
+ * @param doc
6849
+ * @returns
6850
+ */
6851
+ function getOpenDocSignal(doc) {
6852
+ let signal = wm.get(doc);
6853
+ if (!signal) {
6854
+ signal = new rxjs.Subject();
6855
+ wm.set(doc, signal);
6856
+ }
6857
+ return signal;
6858
+ }
6859
+
6688
6860
  const SERVER_PING_TIMEOUT = 20000;
6689
6861
  const CLIENT_PING_INTERVAL = 30000;
6690
6862
  const FAIL_RETRY_WAIT_TIME = 60000;
@@ -6847,9 +7019,6 @@
6847
7019
  if (msg.type === 'error') {
6848
7020
  throw new Error(`Error message from dexie-cloud: ${msg.error}`);
6849
7021
  }
6850
- else if (msg.type === 'rev') {
6851
- this.rev = msg.rev; // No meaning but seems reasonable.
6852
- }
6853
7022
  else if (msg.type === 'aware') {
6854
7023
  const docCache = Dexie.DexieYProvider.getDocCache(this.db.dx);
6855
7024
  const doc = docCache.find(msg.table, msg.k, msg.prop);
@@ -6864,7 +7033,19 @@
6864
7033
  else if (msg.type === 'u-ack' || msg.type === 'u-reject' || msg.type === 'u-s' || msg.type === 'in-sync') {
6865
7034
  applyYServerMessages([msg], this.db);
6866
7035
  }
7036
+ else if (msg.type === 'doc-open') {
7037
+ const docCache = Dexie.DexieYProvider.getDocCache(this.db.dx);
7038
+ const doc = docCache.find(msg.table, msg.k, msg.prop);
7039
+ if (doc) {
7040
+ getOpenDocSignal(doc).next(); // Make yHandler reopen the document on server.
7041
+ }
7042
+ }
7043
+ else if (msg.type === 'outdated-server-rev' || msg.type === 'y-complete-sync-done') {
7044
+ // Won't happen but need this for typing.
7045
+ throw new Error('Outdated server revision or y-complete-sync-done not expected over WebSocket - only in sync using fetch()');
7046
+ }
6867
7047
  else if (msg.type !== 'pong') {
7048
+ // Forward the request to our subscriber, wich is in messageFromServerQueue.ts (via connectWebSocket's subscribe() at the end!)
6868
7049
  this.subscriber.next(msg);
6869
7050
  }
6870
7051
  }
@@ -6901,6 +7082,10 @@
6901
7082
  }
6902
7083
  console.debug('dexie-cloud WebSocket send', msg.type, msg);
6903
7084
  if (msg.type === 'ready') {
7085
+ // Ok, we are certain to have stored everything up until revision msg.rev.
7086
+ // Update this.rev in case of reconnect - remember where we were and don't just start over!
7087
+ this.rev = msg.rev;
7088
+ // ... and then send along the request to the server so it would also be updated!
6904
7089
  (_a = this.ws) === null || _a === void 0 ? void 0 : _a.send(TSON.stringify(msg));
6905
7090
  }
6906
7091
  else {
@@ -6982,7 +7167,7 @@
6982
7167
  return db.cloud.persistedSyncState.pipe(filter((syncState) => (syncState === null || syncState === void 0 ? void 0 : syncState.realms.includes(userLogin.userId)) || false), take(1), map((syncState) => [userLogin, syncState]));
6983
7168
  }
6984
7169
  return new rxjs.BehaviorSubject([userLogin, syncState]);
6985
- }), switchMap(([userLogin, syncState]) => __awaiter(this, void 0, void 0, function* () { return [userLogin, yield computeRealmSetHash(syncState)]; })), distinctUntilChanged(([prevUser, prevHash], [currUser, currHash]) => prevUser === currUser && prevHash === currHash), switchMap(([userLogin, realmSetHash]) => {
7170
+ }), switchMap((_a) => __awaiter(this, [_a], void 0, function* ([userLogin, syncState]) { return [userLogin, yield computeRealmSetHash(syncState)]; })), distinctUntilChanged(([prevUser, prevHash], [currUser, currHash]) => prevUser === currUser && prevHash === currHash), switchMap(([userLogin, realmSetHash]) => {
6986
7171
  var _a;
6987
7172
  if (!((_a = db.cloud.persistedSyncState) === null || _a === void 0 ? void 0 : _a.value)) {
6988
7173
  // Restart the flow if persistedSyncState is not yet available.
@@ -7043,22 +7228,14 @@
7043
7228
  }
7044
7229
 
7045
7230
  function isSyncNeeded(db) {
7046
- var _a;
7047
7231
  return __awaiter(this, void 0, void 0, function* () {
7232
+ var _a;
7048
7233
  return ((_a = db.cloud.options) === null || _a === void 0 ? void 0 : _a.databaseUrl) && db.cloud.schema
7049
7234
  ? yield sync(db, db.cloud.options, db.cloud.schema, { justCheckIfNeeded: true })
7050
7235
  : false;
7051
7236
  });
7052
7237
  }
7053
7238
 
7054
- function performGuardedJob(db, jobName, job) {
7055
- if (typeof navigator === 'undefined' || !navigator.locks) {
7056
- // No support for guarding jobs. IE11, node.js, etc.
7057
- return job();
7058
- }
7059
- return navigator.locks.request(db.name + '|' + jobName, () => job());
7060
- }
7061
-
7062
7239
  const ongoingSyncs = new WeakMap();
7063
7240
  function syncIfPossible(db, cloudOptions, cloudSchema, options) {
7064
7241
  const ongoing = ongoingSyncs.get(db);
@@ -7112,20 +7289,7 @@
7112
7289
  try {
7113
7290
  // Check if should delay sync due to ratelimit:
7114
7291
  yield checkSyncRateLimitDelay(db);
7115
- // Check if we need to lock the sync job. Not needed if we are the service worker.
7116
- if (db.cloud.isServiceWorkerDB) {
7117
- // We are the dedicated sync SW:
7118
- yield sync(db, cloudOptions, cloudSchema, options);
7119
- }
7120
- else if (!db.cloud.usingServiceWorker) {
7121
- // We use a flow that is better suited for the case when multiple workers want to
7122
- // do the same thing.
7123
- yield performGuardedJob(db, CURRENT_SYNC_WORKER, () => sync(db, cloudOptions, cloudSchema, options));
7124
- }
7125
- else {
7126
- assert(false);
7127
- throw new Error('Internal _syncIfPossible() - invalid precondition - should not have been called.');
7128
- }
7292
+ yield performGuardedJob(db, CURRENT_SYNC_WORKER, () => sync(db, cloudOptions, cloudSchema, options));
7129
7293
  ongoingSyncs.delete(db);
7130
7294
  console.debug('Done sync');
7131
7295
  }
@@ -7728,6 +7892,7 @@
7728
7892
  // If user can update any prop in any table in this realm, return true unless
7729
7893
  // it regards to ownership change:
7730
7894
  if (this.permissions.update === '*') {
7895
+ // @ts-ignore
7731
7896
  return props.every((prop) => prop !== 'owner');
7732
7897
  }
7733
7898
  const tablePermissions = (_b = this.permissions.update) === null || _b === void 0 ? void 0 : _b[this.tableName];
@@ -7801,125 +7966,151 @@
7801
7966
  function createYHandler(db) {
7802
7967
  return (provider) => {
7803
7968
  var _a;
7804
- const awap = getAwarenessLibrary(db);
7805
7969
  const doc = provider.doc;
7806
- const { parentTable, parentId, parentProp, updatesTable } = doc.meta;
7970
+ const { parentTable } = doc.meta || {};
7807
7971
  if (!((_a = db.cloud.schema) === null || _a === void 0 ? void 0 : _a[parentTable].markedForSync)) {
7808
7972
  return; // The table that holds the doc is not marked for sync - leave it to dexie. No syncing, no awareness.
7809
7973
  }
7810
- let awareness = new awap.Awareness(doc);
7811
- awarenessWeakMap.set(doc, awareness);
7812
- provider.awareness = awareness;
7813
- awareness.on('update', ({ added, updated, removed }, origin) => {
7814
- // Send the update
7815
- const changedClients = added.concat(updated).concat(removed);
7816
- const user = db.cloud.currentUser.value;
7817
- if (origin !== 'server' && user.isLoggedIn && !isEagerSyncDisabled(db)) {
7818
- const update = awap.encodeAwarenessUpdate(awareness, changedClients);
7974
+ let awareness;
7975
+ Object.defineProperty(provider, 'awareness', {
7976
+ get() {
7977
+ if (awareness)
7978
+ return awareness;
7979
+ awareness = createAwareness(db, doc, provider);
7980
+ awarenessWeakMap.set(doc, awareness);
7981
+ return awareness;
7982
+ },
7983
+ });
7984
+ };
7985
+ }
7986
+ function createAwareness(db, doc, provider) {
7987
+ const { parentTable, parentId, parentProp, updatesTable } = doc.meta;
7988
+ const awap = getAwarenessLibrary(db);
7989
+ const awareness = new awap.Awareness(doc);
7990
+ const reopenDocSignal = getOpenDocSignal(doc);
7991
+ awareness.on('update', ({ added, updated, removed }, origin) => {
7992
+ // Send the update
7993
+ const changedClients = added.concat(updated).concat(removed);
7994
+ const user = db.cloud.currentUser.value;
7995
+ if (origin !== 'server' && user.isLoggedIn && !isEagerSyncDisabled(db)) {
7996
+ const update = awap.encodeAwarenessUpdate(awareness, changedClients);
7997
+ db.messageProducer.next({
7998
+ type: 'aware',
7999
+ table: parentTable,
8000
+ prop: parentProp,
8001
+ k: doc.meta.parentId,
8002
+ u: update,
8003
+ });
8004
+ if (provider.destroyed) {
8005
+ // We're called from awareness.on('destroy') that did
8006
+ // removeAwarenessStates.
8007
+ // It's time to also send the doc-close message that dexie-cloud understands
8008
+ // and uses to stop subscribing for updates and awareness updates and brings
8009
+ // down the cached information in memory on the WS connection for this.
7819
8010
  db.messageProducer.next({
7820
- type: 'aware',
8011
+ type: 'doc-close',
7821
8012
  table: parentTable,
7822
8013
  prop: parentProp,
7823
8014
  k: doc.meta.parentId,
7824
- u: update,
7825
8015
  });
7826
- if (provider.destroyed) {
7827
- // We're called from awareness.on('destroy') that did
7828
- // removeAwarenessStates.
7829
- // It's time to also send the doc-close message that dexie-cloud understands
7830
- // and uses to stop subscribing for updates and awareness updates and brings
7831
- // down the cached information in memory on the WS connection for this.
7832
- db.messageProducer.next({
7833
- type: 'doc-close',
7834
- table: parentTable,
7835
- prop: parentProp,
7836
- k: doc.meta.parentId
7837
- });
7838
- }
7839
8016
  }
7840
- });
7841
- awareness.on('destroy', () => {
7842
- // Signal to server that this provider is destroyed (the update event will be triggered, which
7843
- // in turn will trigger db.messageProducer that will send the message to the server if WS is connected)
7844
- awap.removeAwarenessStates(awareness, [doc.clientID], 'provider destroyed');
7845
- });
7846
- // Now wait til document is loaded and then open the document on the server
7847
- provider.on('load', () => __awaiter(this, void 0, void 0, function* () {
8017
+ }
8018
+ });
8019
+ awareness.on('destroy', () => {
8020
+ // Signal to server that this provider is destroyed (the update event will be triggered, which
8021
+ // in turn will trigger db.messageProducer that will send the message to the server if WS is connected)
8022
+ awap.removeAwarenessStates(awareness, [doc.clientID], 'provider destroyed');
8023
+ });
8024
+ // Open the document on the server
8025
+ (() => __awaiter(this, void 0, void 0, function* () {
8026
+ if (provider.destroyed)
8027
+ return;
8028
+ let connected = false;
8029
+ let currentFlowId = 1;
8030
+ const subscription = rxjs.combineLatest([
8031
+ db.cloud.webSocketStatus, // Wake up when webSocket status changes
8032
+ reopenDocSignal.pipe(rxjs.startWith(null)), // Wake up when reopenDocSignal emits
8033
+ ]).subscribe(([wsStatus]) => {
7848
8034
  if (provider.destroyed)
7849
8035
  return;
7850
- let connected = false;
7851
- let currentFlowId = 1;
7852
- const subscription = db.cloud.webSocketStatus.subscribe((wsStatus) => {
7853
- if (provider.destroyed)
8036
+ // Keep "connected" state in a variable so we can check it after async operations
8037
+ connected = wsStatus === 'connected';
8038
+ // We are or got connected. Open the document on the server.
8039
+ const user = db.cloud.currentUser.value;
8040
+ if (wsStatus === 'connected' &&
8041
+ user.isLoggedIn &&
8042
+ !isEagerSyncDisabled(db)) {
8043
+ ++currentFlowId;
8044
+ openDocumentOnServer().catch((error) => {
8045
+ console.warn(`Error catched in createYHandler.ts: ${error}`);
8046
+ });
8047
+ }
8048
+ });
8049
+ // Wait until WebSocket is connected
8050
+ provider.addCleanupHandler(subscription);
8051
+ /** Sends an 'doc-open' message to server whenever websocket becomes
8052
+ * connected, or if it is already connected.
8053
+ * The flow is aborted in case websocket is disconnected while querying
8054
+ * information required to compute the state vector. Flow is also
8055
+ * aborted in case document or provider has been destroyed during
8056
+ * the async parts of the task.
8057
+ *
8058
+ * The state vector is only computed from the updates that have occured
8059
+ * after the last full sync - which could very often be zero - in which
8060
+ * case no state vector is sent (then the server already knows us by
8061
+ * revision)
8062
+ *
8063
+ * When server gets the doc-open message, it will authorize us for
8064
+ * whether we are allowed to read / write to this document, and then
8065
+ * keep the cached information in memory on the WS connection for this
8066
+ * particular document, as well as subscribe to updates and awareness updates
8067
+ * from other clients on the document.
8068
+ */
8069
+ function openDocumentOnServer() {
8070
+ return __awaiter(this, void 0, void 0, function* () {
8071
+ const myFlow = currentFlowId; // So we can abort when a new flow is started
8072
+ const yTbl = db.table(updatesTable);
8073
+ const syncStateTbl = db.$syncState;
8074
+ const [receivedUntil, yServerRev] = yield db.transaction('r', syncStateTbl, yTbl, () => __awaiter(this, void 0, void 0, function* () {
8075
+ const syncState = yield yTbl.get(DEXIE_CLOUD_SYNCER_ID);
8076
+ const persistedSyncState = yield syncStateTbl.get('syncState');
8077
+ return [
8078
+ (syncState === null || syncState === void 0 ? void 0 : syncState.receivedUntil) || 0,
8079
+ (persistedSyncState === null || persistedSyncState === void 0 ? void 0 : persistedSyncState.yServerRevision) ||
8080
+ (persistedSyncState === null || persistedSyncState === void 0 ? void 0 : persistedSyncState.serverRevision),
8081
+ ];
8082
+ }));
8083
+ // After every await, check if we still should be working on this task.
8084
+ if (provider.destroyed || currentFlowId !== myFlow || !connected)
7854
8085
  return;
7855
- // Keep "connected" state in a variable so we can check it after async operations
7856
- connected = wsStatus === 'connected';
7857
- // We are or got connected. Open the document on the server.
7858
- const user = db.cloud.currentUser.value;
7859
- if (wsStatus === "connected" && user.isLoggedIn && !isEagerSyncDisabled(db)) {
7860
- ++currentFlowId;
7861
- openDocumentOnServer().catch(error => {
7862
- console.warn(`Error catched in createYHandler.ts: ${error}`);
7863
- });
8086
+ const docOpenMsg = {
8087
+ type: 'doc-open',
8088
+ table: parentTable,
8089
+ prop: parentProp,
8090
+ k: parentId,
8091
+ serverRev: yServerRev,
8092
+ };
8093
+ const serverUpdatesSinceLastSync = yield yTbl
8094
+ .where('i')
8095
+ .between(receivedUntil, Infinity, false)
8096
+ .filter((update) => Dexie.cmp(update.k, parentId) === 0 && // Only updates for this document
8097
+ ((update.f || 0) & 1) === 0 // Don't include local changes
8098
+ )
8099
+ .toArray();
8100
+ // After every await, check if we still should be working on this task.
8101
+ if (provider.destroyed || currentFlowId !== myFlow || !connected)
8102
+ return;
8103
+ if (serverUpdatesSinceLastSync.length > 0) {
8104
+ const Y = $Y(db); // Get the Yjs library from Dexie constructor options
8105
+ const mergedUpdate = Y.mergeUpdatesV2(serverUpdatesSinceLastSync.map((update) => update.u));
8106
+ const stateVector = Y.encodeStateVectorFromUpdateV2(mergedUpdate);
8107
+ docOpenMsg.sv = stateVector;
7864
8108
  }
8109
+ db.messageProducer.next(docOpenMsg);
7865
8110
  });
7866
- // Wait until WebSocket is connected
7867
- provider.addCleanupHandler(subscription);
7868
- /** Sends an 'doc-open' message to server whenever websocket becomes
7869
- * connected, or if it is already connected.
7870
- * The flow is aborted in case websocket is disconnected while querying
7871
- * information required to compute the state vector. Flow is also
7872
- * aborted in case document or provider has been destroyed during
7873
- * the async parts of the task.
7874
- *
7875
- * The state vector is only computed from the updates that have occured
7876
- * after the last full sync - which could very often be zero - in which
7877
- * case no state vector is sent (then the server already knows us by
7878
- * revision)
7879
- *
7880
- * When server gets the doc-open message, it will authorized us for
7881
- * whether we are allowed to read / write to this document, and then
7882
- * keep the cached information in memory on the WS connection for this
7883
- * particular document, as well as subscribe to updates and awareness updates
7884
- * from other clients on the document.
7885
- */
7886
- function openDocumentOnServer(wsStatus) {
7887
- return __awaiter(this, void 0, void 0, function* () {
7888
- const myFlow = currentFlowId; // So we can abort when a new flow is started
7889
- const yTbl = db.table(updatesTable);
7890
- const syncState = yield yTbl.get(DEXIE_CLOUD_SYNCER_ID);
7891
- // After every await, check if we still should be working on this task.
7892
- if (provider.destroyed || currentFlowId !== myFlow || !connected)
7893
- return;
7894
- const receivedUntil = (syncState === null || syncState === void 0 ? void 0 : syncState.receivedUntil) || 0;
7895
- const docOpenMsg = {
7896
- type: 'doc-open',
7897
- table: parentTable,
7898
- prop: parentProp,
7899
- k: parentId,
7900
- serverRev: syncState === null || syncState === void 0 ? void 0 : syncState.serverRev,
7901
- };
7902
- const serverUpdatesSinceLastSync = yield yTbl
7903
- .where('i')
7904
- .between(receivedUntil, Infinity, false)
7905
- .filter((update) => Dexie.cmp(update.k, parentId) === 0 && // Only updates for this document
7906
- ((update.f || 0) & 1) === 0 // Don't include local changes
7907
- )
7908
- .toArray();
7909
- // After every await, check if we still should be working on this task.
7910
- if (provider.destroyed || currentFlowId !== myFlow || !connected)
7911
- return;
7912
- if (serverUpdatesSinceLastSync.length > 0) {
7913
- const Y = $Y(db); // Get the Yjs library from Dexie constructor options
7914
- const mergedUpdate = Y.mergeUpdatesV2(serverUpdatesSinceLastSync.map((update) => update.u));
7915
- const stateVector = Y.encodeStateVectorFromUpdateV2(mergedUpdate);
7916
- docOpenMsg.sv = stateVector;
7917
- }
7918
- db.messageProducer.next(docOpenMsg);
7919
- });
7920
- }
7921
- }));
7922
- };
8111
+ }
8112
+ }))();
8113
+ return awareness;
7923
8114
  }
7924
8115
 
7925
8116
  const DEFAULT_OPTIONS = {
@@ -7962,7 +8153,7 @@
7962
8153
  const syncComplete = new rxjs.Subject();
7963
8154
  dexie.cloud = {
7964
8155
  // @ts-ignore
7965
- version: "4.1.0-alpha.9",
8156
+ version: "4.1.0-beta.25",
7966
8157
  options: Object.assign({}, DEFAULT_OPTIONS),
7967
8158
  schema: null,
7968
8159
  get currentUserId() {
@@ -7998,16 +8189,16 @@
7998
8189
  }
7999
8190
  updateSchemaFromOptions(dexie.cloud.schema, dexie.cloud.options);
8000
8191
  },
8001
- logout({ force } = {}) {
8002
- return __awaiter(this, void 0, void 0, function* () {
8192
+ logout() {
8193
+ return __awaiter(this, arguments, void 0, function* ({ force } = {}) {
8003
8194
  force
8004
8195
  ? yield _logout(DexieCloudDB(dexie), { deleteUnsyncedData: true })
8005
8196
  : yield logout(DexieCloudDB(dexie));
8006
8197
  });
8007
8198
  },
8008
- sync({ wait, purpose } = { wait: true, purpose: 'push' }) {
8009
- var _a;
8010
- return __awaiter(this, void 0, void 0, function* () {
8199
+ sync() {
8200
+ return __awaiter(this, arguments, void 0, function* ({ wait, purpose } = { wait: true, purpose: 'push' }) {
8201
+ var _a;
8011
8202
  if (wait === undefined)
8012
8203
  wait = true;
8013
8204
  const db = DexieCloudDB(dexie);
@@ -8065,8 +8256,8 @@
8065
8256
  dexie.use(createImplicitPropSetterMiddleware(DexieCloudDB(dexie)));
8066
8257
  dexie.use(createIdGenerationMiddleware(DexieCloudDB(dexie)));
8067
8258
  function onDbReady(dexie) {
8068
- var _a, _b, _c, _d, _e, _f, _g;
8069
8259
  return __awaiter(this, void 0, void 0, function* () {
8260
+ var _a, _b, _c, _d, _e, _f, _g;
8070
8261
  closed = false; // As Dexie calls us, we are not closed anymore. Maybe reopened? Remember db.ready event is registered with sticky flag!
8071
8262
  const db = DexieCloudDB(dexie);
8072
8263
  // Setup default GUI:
@@ -8089,7 +8280,7 @@
8089
8280
  ? yield navigator.serviceWorker.getRegistrations()
8090
8281
  : [];
8091
8282
  const [initiallySynced, lastSyncedRealms] = yield db.transaction('rw', db.$syncState, () => __awaiter(this, void 0, void 0, function* () {
8092
- var _h, _j;
8283
+ var _a, _b;
8093
8284
  const { options, schema } = db.cloud;
8094
8285
  const [persistedOptions, persistedSchema, persistedSyncState] = yield Promise.all([
8095
8286
  db.getOptions(),
@@ -8111,7 +8302,7 @@
8111
8302
  delete newPersistedOptions.awarenessProtocol;
8112
8303
  yield db.$syncState.put(newPersistedOptions, 'options');
8113
8304
  }
8114
- if (((_h = db.cloud.options) === null || _h === void 0 ? void 0 : _h.tryUseServiceWorker) &&
8305
+ if (((_a = db.cloud.options) === null || _a === void 0 ? void 0 : _a.tryUseServiceWorker) &&
8115
8306
  'serviceWorker' in navigator &&
8116
8307
  swRegistrations.length > 0 &&
8117
8308
  !DISABLE_SERVICEWORKER_STRATEGY) {
@@ -8125,7 +8316,7 @@
8125
8316
  // Not configured for using service worker or no service worker
8126
8317
  // registration exists. Don't rely on service worker to do any job.
8127
8318
  // Use LocalSyncWorker instead.
8128
- if (((_j = db.cloud.options) === null || _j === void 0 ? void 0 : _j.tryUseServiceWorker) &&
8319
+ if (((_b = db.cloud.options) === null || _b === void 0 ? void 0 : _b.tryUseServiceWorker) &&
8129
8320
  !db.cloud.isServiceWorkerDB) {
8130
8321
  console.debug('dexie-cloud-addon: Not using service worker.', swRegistrations.length === 0
8131
8322
  ? 'No SW registrations found.'
@@ -8168,10 +8359,6 @@
8168
8359
  db.setInitiallySynced(true);
8169
8360
  }
8170
8361
  verifySchema(db);
8171
- if (((_b = db.cloud.options) === null || _b === void 0 ? void 0 : _b.databaseUrl) && !initiallySynced) {
8172
- yield performInitialSync(db, db.cloud.options, db.cloud.schema);
8173
- db.setInitiallySynced(true);
8174
- }
8175
8362
  // Manage CurrentUser observable:
8176
8363
  throwIfClosed();
8177
8364
  if (!db.cloud.isServiceWorkerDB) {
@@ -8196,20 +8383,29 @@
8196
8383
  // HERE: If requireAuth, do athentication now.
8197
8384
  let changedUser = false;
8198
8385
  const user = yield db.getCurrentUser();
8199
- const requireAuth = (_c = db.cloud.options) === null || _c === void 0 ? void 0 : _c.requireAuth;
8386
+ const requireAuth = (_b = db.cloud.options) === null || _b === void 0 ? void 0 : _b.requireAuth;
8200
8387
  if (requireAuth) {
8201
- if (typeof requireAuth === 'object') {
8202
- // requireAuth contains login hints. Check if we already fulfil it:
8203
- if (!user.isLoggedIn ||
8204
- (requireAuth.userId && user.userId !== requireAuth.userId) ||
8205
- (requireAuth.email && user.email !== requireAuth.email)) {
8206
- // If not, login the configured user:
8207
- changedUser = yield login(db, requireAuth);
8208
- }
8388
+ if (db.cloud.isServiceWorkerDB) {
8389
+ // If this is a service worker DB, we can't do authentication here,
8390
+ // we just wait until the application has done it.
8391
+ console.debug('Dexie Cloud Service worker. Waiting for application to authenticate.');
8392
+ yield rxjs.firstValueFrom(currentUserEmitter.pipe(filter((user) => !!user.isLoggedIn), take(1)));
8393
+ console.debug('Dexie Cloud Service worker. Application has authenticated.');
8209
8394
  }
8210
- else if (!user.isLoggedIn) {
8211
- // requireAuth is true and user is not logged in
8212
- changedUser = yield login(db);
8395
+ else {
8396
+ if (typeof requireAuth === 'object') {
8397
+ // requireAuth contains login hints. Check if we already fulfil it:
8398
+ if (!user.isLoggedIn ||
8399
+ (requireAuth.userId && user.userId !== requireAuth.userId) ||
8400
+ (requireAuth.email && user.email !== requireAuth.email)) {
8401
+ // If not, login the configured user:
8402
+ changedUser = yield login(db, requireAuth);
8403
+ }
8404
+ }
8405
+ else if (!user.isLoggedIn) {
8406
+ // requireAuth is true and user is not logged in
8407
+ changedUser = yield login(db);
8408
+ }
8213
8409
  }
8214
8410
  }
8215
8411
  if (user.isLoggedIn && (!lastSyncedRealms || !lastSyncedRealms.includes(user.userId))) {
@@ -8224,8 +8420,17 @@
8224
8420
  localSyncWorker.stop();
8225
8421
  localSyncWorker = null;
8226
8422
  throwIfClosed();
8423
+ const doInitialSync = ((_c = db.cloud.options) === null || _c === void 0 ? void 0 : _c.databaseUrl) && (!initiallySynced || changedUser);
8424
+ if (doInitialSync) {
8425
+ // Do the initial sync directly in the browser thread no matter if we are using service worker or not.
8426
+ yield performInitialSync(db, db.cloud.options, db.cloud.schema);
8427
+ db.setInitiallySynced(true);
8428
+ }
8429
+ throwIfClosed();
8227
8430
  if (db.cloud.usingServiceWorker && ((_d = db.cloud.options) === null || _d === void 0 ? void 0 : _d.databaseUrl)) {
8228
- registerSyncEvent(db, changedUser ? 'pull' : 'push').catch(() => { });
8431
+ if (!doInitialSync) {
8432
+ registerSyncEvent(db, 'push').catch(() => { });
8433
+ }
8229
8434
  registerPeriodicSyncEvent(db).catch(() => { });
8230
8435
  }
8231
8436
  else if (((_e = db.cloud.options) === null || _e === void 0 ? void 0 : _e.databaseUrl) &&
@@ -8234,7 +8439,9 @@
8234
8439
  // There's no SW. Start SyncWorker instead.
8235
8440
  localSyncWorker = LocalSyncWorker(db, db.cloud.options, db.cloud.schema);
8236
8441
  localSyncWorker.start();
8237
- triggerSync(db, changedUser ? 'pull' : 'push');
8442
+ if (!doInitialSync) {
8443
+ triggerSync(db, 'push');
8444
+ }
8238
8445
  }
8239
8446
  // Listen to online event and do sync.
8240
8447
  throwIfClosed();
@@ -8254,7 +8461,7 @@
8254
8461
  });
8255
8462
  }));
8256
8463
  }
8257
- // Connect WebSocket unless we
8464
+ // Connect WebSocket unless we are in a service worker or websocket is disabled.
8258
8465
  if (((_f = db.cloud.options) === null || _f === void 0 ? void 0 : _f.databaseUrl) &&
8259
8466
  !((_g = db.cloud.options) === null || _g === void 0 ? void 0 : _g.disableWebSocket) &&
8260
8467
  !IS_SERVICE_WORKER) {
@@ -8264,7 +8471,7 @@
8264
8471
  }
8265
8472
  }
8266
8473
  // @ts-ignore
8267
- dexieCloud.version = "4.1.0-alpha.9";
8474
+ dexieCloud.version = "4.1.0-beta.25";
8268
8475
  Dexie.Cloud = dexieCloud;
8269
8476
 
8270
8477
  // In case the SW lives for a while, let it reuse already opened connections:
@@ -8294,8 +8501,8 @@
8294
8501
  }
8295
8502
  return promise;
8296
8503
  function _syncDB(dbName, purpose) {
8297
- var _a;
8298
8504
  return __awaiter(this, void 0, void 0, function* () {
8505
+ var _a;
8299
8506
  let db = managedDBs.get(dbName);
8300
8507
  if (!db) {
8301
8508
  console.debug('Dexie Cloud SW: Creating new Dexie instance for', dbName);