dexie-cloud-addon 4.1.0-alpha.8 → 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 +622 -257
  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 +464 -255
  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 +620 -254
  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 +462 -253
  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.8, Tue Oct 15 2024
11
+ * Version 4.1.0-beta.25, Wed Dec 04 2024
12
12
  *
13
13
  * https://dexie.org
14
14
  *
@@ -16,8 +16,8 @@
16
16
  *
17
17
  */
18
18
 
19
- import Dexie, { PropModification, cmp, DexieYProvider, liveQuery } from 'dexie';
20
- import { Observable as Observable$1, BehaviorSubject, firstValueFrom, Subject, from as from$1, filter as filter$1, fromEvent, of, merge, mergeMap as mergeMap$1, Subscription as Subscription$1, throwError, combineLatest, map as map$1, share, timer as timer$1 } from 'rxjs';
19
+ import Dexie, { PropModification, cmp, DexieYProvider, RangeSet, liveQuery } from 'dexie';
20
+ import { Observable as Observable$1, BehaviorSubject, firstValueFrom, Subject, from as from$1, filter as filter$1, fromEvent, of, merge, switchMap as switchMap$1, tap as tap$1, mergeMap as mergeMap$1, Subscription as Subscription$1, throwError, combineLatest, map as map$1, share, timer as timer$1, startWith as startWith$1 } from 'rxjs';
21
21
 
22
22
  /******************************************************************************
23
23
  Copyright (c) Microsoft Corporation.
@@ -1765,8 +1765,8 @@ function registerSyncEvent(db, purpose) {
1765
1765
  });
1766
1766
  }
1767
1767
  function registerPeriodicSyncEvent(db) {
1768
- var _a;
1769
1768
  return __awaiter(this, void 0, void 0, function* () {
1769
+ var _a;
1770
1770
  try {
1771
1771
  // Register periodicSync event to SW:
1772
1772
  // @ts-ignore
@@ -1831,8 +1831,8 @@ const b64encode = typeof Buffer !== "undefined"
1831
1831
  return btoa(strs.join(""));
1832
1832
  };
1833
1833
 
1834
- function computeRealmSetHash({ realms, inviteRealms, }) {
1835
- return __awaiter(this, void 0, void 0, function* () {
1834
+ function computeRealmSetHash(_a) {
1835
+ return __awaiter(this, arguments, void 0, function* ({ realms, inviteRealms, }) {
1836
1836
  const data = JSON.stringify([
1837
1837
  ...realms.map((realmId) => ({ realmId, accepted: true })),
1838
1838
  ...inviteRealms.map((realmId) => ({ realmId, accepted: false })),
@@ -1868,8 +1868,8 @@ function flatten(a) {
1868
1868
  return concat.apply([], a);
1869
1869
  }
1870
1870
 
1871
- function listClientChanges(mutationTables, db, { since = {}, limit = Infinity } = {}) {
1872
- return __awaiter(this, void 0, void 0, function* () {
1871
+ function listClientChanges(mutationTables_1, db_1) {
1872
+ return __awaiter(this, arguments, void 0, function* (mutationTables, db, { since = {}, limit = Infinity } = {}) {
1873
1873
  const allMutsOnTables = yield Promise.all(mutationTables.map((mutationTable) => __awaiter(this, void 0, void 0, function* () {
1874
1874
  const tableName = getTableFromMutationTable(mutationTable.name);
1875
1875
  const lastRevision = since[tableName];
@@ -2754,13 +2754,20 @@ const writeAny = (encoder, data) => {
2754
2754
  function encodeYMessage(msg) {
2755
2755
  const encoder = new Encoder();
2756
2756
  writeVarString(encoder, msg.type);
2757
- writeVarString(encoder, msg.table);
2758
- writeVarString(encoder, msg.prop);
2757
+ if ('table' in msg)
2758
+ writeVarString(encoder, msg.table);
2759
+ if ('prop' in msg)
2760
+ writeVarString(encoder, msg.prop);
2759
2761
  switch (msg.type) {
2760
2762
  case 'u-ack':
2761
2763
  case 'u-reject':
2762
2764
  writeBigUint64(encoder, BigInt(msg.i));
2763
2765
  break;
2766
+ case 'outdated-server-rev':
2767
+ break;
2768
+ case 'y-complete-sync-done':
2769
+ writeVarString(encoder, msg.yServerRev);
2770
+ break;
2764
2771
  default:
2765
2772
  writeAny(encoder, msg.k);
2766
2773
  switch (msg.type) {
@@ -3104,6 +3111,12 @@ const readAny = decoder => readAnyLookupTable[127 - readUint8(decoder)](decoder)
3104
3111
  function decodeYMessage(a) {
3105
3112
  const decoder = new Decoder(a);
3106
3113
  const type = readVarString(decoder);
3114
+ if (type === 'outdated-server-rev') {
3115
+ return { type };
3116
+ }
3117
+ if (type === 'y-complete-sync-done') {
3118
+ return { type, yServerRev: readVarString(decoder) };
3119
+ }
3107
3120
  const table = readVarString(decoder);
3108
3121
  const prop = readVarString(decoder);
3109
3122
  switch (type) {
@@ -3516,8 +3529,8 @@ function confirmLogout(userInteraction, currentUserId, numUnsyncedChanges) {
3516
3529
  }
3517
3530
 
3518
3531
  function loadAccessToken(db) {
3519
- var _a, _b, _c;
3520
3532
  return __awaiter(this, void 0, void 0, function* () {
3533
+ var _a, _b, _c;
3521
3534
  const currentUser = yield db.getCurrentUser();
3522
3535
  const { accessToken, accessTokenExpiration, refreshToken, refreshTokenExpiration, claims, } = currentUser;
3523
3536
  if (!accessToken)
@@ -4347,8 +4360,8 @@ function cloneChange(change, rewriteValues) {
4347
4360
  // seconds (given that there is a Ratelimit-Reset header).
4348
4361
  let syncRatelimitDelays = new WeakMap();
4349
4362
  function checkSyncRateLimitDelay(db) {
4350
- var _a, _b;
4351
4363
  return __awaiter(this, void 0, void 0, function* () {
4364
+ var _a, _b;
4352
4365
  const delatMilliseconds = ((_b = (_a = syncRatelimitDelays.get(db)) === null || _a === void 0 ? void 0 : _a.getTime()) !== null && _b !== void 0 ? _b : 0) - Date.now();
4353
4366
  if (delatMilliseconds > 0) {
4354
4367
  console.debug(`Stalling sync request ${delatMilliseconds} ms to spare ratelimits`);
@@ -4409,6 +4422,7 @@ function syncWithServer(changes, y, syncState, baseRevs, db, databaseUrl, schema
4409
4422
  lastPull: syncState
4410
4423
  ? {
4411
4424
  serverRevision: syncState.serverRevision,
4425
+ yServerRevision: syncState.yServerRevision,
4412
4426
  realms: syncState.realms,
4413
4427
  inviteRealms: syncState.inviteRealms,
4414
4428
  }
@@ -4753,12 +4767,15 @@ function getUpdatesTable(db, table, ydocProp) {
4753
4767
 
4754
4768
  function applyYServerMessages(yMessages, db) {
4755
4769
  return __awaiter(this, void 0, void 0, function* () {
4756
- const result = {};
4770
+ var _a;
4771
+ const receivedUntils = {};
4772
+ let resyncNeeded = false;
4773
+ let yServerRevision;
4757
4774
  for (const m of yMessages) {
4758
4775
  switch (m.type) {
4759
4776
  case 'u-s': {
4760
4777
  const utbl = getUpdatesTable(db, m.table, m.prop);
4761
- result[utbl.name] = yield utbl.add({
4778
+ receivedUntils[utbl.name] = yield utbl.add({
4762
4779
  k: m.k,
4763
4780
  u: m.u,
4764
4781
  });
@@ -4783,7 +4800,24 @@ function applyYServerMessages(yMessages, db) {
4783
4800
  // See my question in https://discuss.yjs.dev/t/generate-an-inverse-update/2765
4784
4801
  console.debug(`Y update rejected. Deleting it.`);
4785
4802
  const utbl = getUpdatesTable(db, m.table, m.prop);
4786
- yield utbl.delete(m.i);
4803
+ // Delete the rejected update and all local updates since (avoid holes in the CRDT)
4804
+ // and destroy it's open document if there is one.
4805
+ const primaryKey = (_a = (yield utbl.get(m.i))) === null || _a === void 0 ? void 0 : _a.k;
4806
+ if (primaryKey != null) {
4807
+ yield db.transaction('rw', utbl, (tx) => {
4808
+ // @ts-ignore
4809
+ tx.idbtrans._rejecting_y_ypdate = true; // Inform ydoc triggers that we delete because of a rejection and not GC
4810
+ return utbl
4811
+ .where('i')
4812
+ .aboveOrEqual(m.i)
4813
+ .filter((u) => cmp(u.k, primaryKey) === 0 && ((u.f || 0) & 1) === 1)
4814
+ .delete();
4815
+ });
4816
+ // Destroy active doc
4817
+ const activeDoc = DexieYProvider.getDocCache(db.dx).find(m.table, primaryKey, m.prop);
4818
+ if (activeDoc)
4819
+ activeDoc.destroy(); // Destroy the document so that editors don't continue to work on it
4820
+ }
4787
4821
  break;
4788
4822
  }
4789
4823
  case 'in-sync': {
@@ -4793,15 +4827,26 @@ function applyYServerMessages(yMessages, db) {
4793
4827
  }
4794
4828
  break;
4795
4829
  }
4830
+ case 'y-complete-sync-done': {
4831
+ yServerRevision = m.yServerRev;
4832
+ break;
4833
+ }
4834
+ case 'outdated-server-rev':
4835
+ resyncNeeded = true;
4836
+ break;
4796
4837
  }
4797
4838
  }
4798
- return result;
4839
+ return {
4840
+ receivedUntils,
4841
+ resyncNeeded,
4842
+ yServerRevision
4843
+ };
4799
4844
  });
4800
4845
  }
4801
4846
 
4802
- function updateYSyncStates(lastUpdateIdsBeforeSync, receivedUntilsAfterSync, db, serverRevision) {
4803
- var _a, _b, _c, _d, _e;
4847
+ function updateYSyncStates(lastUpdateIdsBeforeSync, receivedUntilsAfterSync, db) {
4804
4848
  return __awaiter(this, void 0, void 0, function* () {
4849
+ var _a, _b, _c, _d, _e;
4805
4850
  // We want to update unsentFrom for each yTable to the value specified in first argument
4806
4851
  // because we got those values before we synced with server and here we are back from server
4807
4852
  // that has successfully received all those messages - no matter if the last update was a client or server update,
@@ -4848,14 +4893,12 @@ function updateYSyncStates(lastUpdateIdsBeforeSync, receivedUntilsAfterSync, db,
4848
4893
  yield db.table(yTable).add({
4849
4894
  i: DEXIE_CLOUD_SYNCER_ID,
4850
4895
  unsentFrom,
4851
- receivedUntil,
4852
- serverRev: serverRevision,
4896
+ receivedUntil
4853
4897
  });
4854
4898
  }
4855
4899
  else {
4856
4900
  state.unsentFrom = Math.max(unsentFrom, state.unsentFrom || 1);
4857
4901
  state.receivedUntil = Math.max(receivedUntil, state.receivedUntil || 0);
4858
- state.serverRev = serverRevision;
4859
4902
  yield db.table(yTable).put(state);
4860
4903
  }
4861
4904
  }));
@@ -4866,8 +4909,8 @@ function updateYSyncStates(lastUpdateIdsBeforeSync, receivedUntilsAfterSync, db,
4866
4909
  const BINSTREAM_TYPE_REALMID = 1;
4867
4910
  const BINSTREAM_TYPE_TABLE_AND_PROP = 2;
4868
4911
  const BINSTREAM_TYPE_DOCUMENT = 3;
4869
- function downloadYDocsFromServer(db, databaseUrl, { yDownloadedRealms, realms }) {
4870
- return __awaiter(this, void 0, void 0, function* () {
4912
+ function downloadYDocsFromServer(db_1, databaseUrl_1, _a) {
4913
+ return __awaiter(this, arguments, void 0, function* (db, databaseUrl, { yDownloadedRealms, realms }) {
4871
4914
  if (yDownloadedRealms &&
4872
4915
  realms &&
4873
4916
  realms.every((realmId) => yDownloadedRealms[realmId] === '*')) {
@@ -4982,8 +5025,7 @@ function downloadYDocsFromServer(db, databaseUrl, { yDownloadedRealms, realms })
4982
5025
 
4983
5026
  const CURRENT_SYNC_WORKER = 'currentSyncWorker';
4984
5027
  function sync(db, options, schema, syncOptions) {
4985
- return _sync
4986
- .apply(this, arguments)
5028
+ return _sync(db, options, schema, syncOptions)
4987
5029
  .then((result) => {
4988
5030
  if (!(syncOptions === null || syncOptions === void 0 ? void 0 : syncOptions.justCheckIfNeeded)) { // && syncOptions?.purpose !== 'push') {
4989
5031
  db.syncStateChangedEvent.next({
@@ -5025,11 +5067,11 @@ function sync(db, options, schema, syncOptions) {
5025
5067
  return Promise.reject(error);
5026
5068
  }));
5027
5069
  }
5028
- function _sync(db, options, schema, { isInitialSync, cancelToken, justCheckIfNeeded, purpose } = {
5029
- isInitialSync: false,
5030
- }) {
5031
- var _a;
5032
- return __awaiter(this, void 0, void 0, function* () {
5070
+ function _sync(db_1, options_1, schema_1) {
5071
+ return __awaiter(this, arguments, void 0, function* (db, options, schema, { isInitialSync, cancelToken, justCheckIfNeeded, purpose } = {
5072
+ isInitialSync: false,
5073
+ }) {
5074
+ var _a;
5033
5075
  if (!justCheckIfNeeded) {
5034
5076
  console.debug('SYNC STARTED', { isInitialSync, purpose });
5035
5077
  }
@@ -5046,7 +5088,7 @@ function _sync(db, options, schema, { isInitialSync, cancelToken, justCheckIfNee
5046
5088
  // Prepare for syncification by modifying locally unauthorized objects:
5047
5089
  //
5048
5090
  const persistedSyncState = yield db.getPersistedSyncState();
5049
- const readyForSyncification = !isInitialSync && currentUser.isLoggedIn;
5091
+ const readyForSyncification = currentUser.isLoggedIn;
5050
5092
  const tablesToSyncify = readyForSyncification
5051
5093
  ? getTablesToSyncify(db, persistedSyncState)
5052
5094
  : [];
@@ -5071,7 +5113,7 @@ function _sync(db, options, schema, { isInitialSync, cancelToken, justCheckIfNee
5071
5113
  const [clientChangeSet, syncState, baseRevs, { yMessages, lastUpdateIds }] = yield db.transaction('r', db.tables, () => __awaiter(this, void 0, void 0, function* () {
5072
5114
  const syncState = yield db.getPersistedSyncState();
5073
5115
  const baseRevs = yield db.$baseRevs.toArray();
5074
- let clientChanges = yield listClientChanges(mutationTables);
5116
+ let clientChanges = yield listClientChanges(mutationTables, db);
5075
5117
  const yResults = yield listYClientMessagesAndStateVector(db, tablesToSync);
5076
5118
  throwIfCancelled(cancelToken);
5077
5119
  if (doSyncify) {
@@ -5191,6 +5233,7 @@ function _sync(db, options, schema, { isInitialSync, cancelToken, justCheckIfNee
5191
5233
  newSyncState.realms = res.realms;
5192
5234
  newSyncState.inviteRealms = res.inviteRealms;
5193
5235
  newSyncState.serverRevision = res.serverRevision;
5236
+ newSyncState.yServerRevision = res.serverRevision;
5194
5237
  newSyncState.timestamp = new Date();
5195
5238
  delete newSyncState.error;
5196
5239
  const filteredChanges = filterServerChangesThroughAddedClientChanges(res.changes, addedClientChanges);
@@ -5202,11 +5245,17 @@ function _sync(db, options, schema, { isInitialSync, cancelToken, justCheckIfNee
5202
5245
  //
5203
5246
  // apply yMessages
5204
5247
  //
5205
- const receivedUntils = yield applyYServerMessages(res.yMessages, db);
5248
+ const { receivedUntils, resyncNeeded, yServerRevision } = yield applyYServerMessages(res.yMessages, db);
5249
+ if (yServerRevision) {
5250
+ newSyncState.yServerRevision = yServerRevision;
5251
+ }
5206
5252
  //
5207
5253
  // update Y SyncStates
5208
5254
  //
5209
- yield updateYSyncStates(lastUpdateIds, receivedUntils, db, res.serverRevision);
5255
+ yield updateYSyncStates(lastUpdateIds, receivedUntils, db);
5256
+ if (resyncNeeded) {
5257
+ newSyncState.yDownloadedRealms = {}; // Will trigger a full download of Y-documents below...
5258
+ }
5210
5259
  }
5211
5260
  //
5212
5261
  // Update regular syncState
@@ -5340,8 +5389,8 @@ function MessagesFromServerConsumer(db) {
5340
5389
  event.next(null);
5341
5390
  }
5342
5391
  function consumeQueue() {
5343
- var _a, _b, _c, _d, _e, _f;
5344
5392
  return __awaiter(this, void 0, void 0, function* () {
5393
+ var _a, _b, _c, _d, _e, _f;
5345
5394
  while (queue.length > 0) {
5346
5395
  const msg = queue.shift();
5347
5396
  try {
@@ -5499,7 +5548,7 @@ function MessagesFromServerConsumer(db) {
5499
5548
  };
5500
5549
  }
5501
5550
 
5502
- const wm$1 = new WeakMap();
5551
+ const wm$2 = new WeakMap();
5503
5552
  const DEXIE_CLOUD_SCHEMA = {
5504
5553
  members: '@id, [userId+realmId], [email+realmId], realmId',
5505
5554
  roles: '[realmId+name]',
@@ -5513,7 +5562,7 @@ let static_counter = 0;
5513
5562
  function DexieCloudDB(dx) {
5514
5563
  if ('vip' in dx)
5515
5564
  dx = dx['vip']; // Avoid race condition. Always map to a vipped dexie that don't block during db.on.ready().
5516
- let db = wm$1.get(dx.cloud);
5565
+ let db = wm$2.get(dx.cloud);
5517
5566
  if (!db) {
5518
5567
  const localSyncEvent = new Subject();
5519
5568
  let syncStateChangedEvent = new BroadcastedAndLocalEvent(`syncstatechanged-${dx.name}`);
@@ -5601,7 +5650,7 @@ function DexieCloudDB(dx) {
5601
5650
  Object.assign(db, helperMethods);
5602
5651
  db.messageConsumer = MessagesFromServerConsumer(db);
5603
5652
  db.messageProducer = new Subject();
5604
- wm$1.set(dx.cloud, db);
5653
+ wm$2.set(dx.cloud, db);
5605
5654
  }
5606
5655
  return db;
5607
5656
  }
@@ -5612,10 +5661,10 @@ function nameFromKeyPath(keyPath) {
5612
5661
  }
5613
5662
 
5614
5663
  // Emulate true-private property db. Why? So it's not stored in DB.
5615
- const wm = new WeakMap();
5664
+ const wm$1 = new WeakMap();
5616
5665
  class AuthPersistedContext {
5617
5666
  constructor(db, userLogin) {
5618
- wm.set(this, db);
5667
+ wm$1.set(this, db);
5619
5668
  Object.assign(this, userLogin);
5620
5669
  }
5621
5670
  static load(db, userId) {
@@ -5632,7 +5681,7 @@ class AuthPersistedContext {
5632
5681
  }
5633
5682
  save() {
5634
5683
  return __awaiter(this, void 0, void 0, function* () {
5635
- const db = wm.get(this);
5684
+ const db = wm$1.get(this);
5636
5685
  db.table("$logins").put(this);
5637
5686
  });
5638
5687
  }
@@ -5656,8 +5705,8 @@ function logout(db) {
5656
5705
  }
5657
5706
  });
5658
5707
  }
5659
- function _logout(db, { deleteUnsyncedData = false } = {}) {
5660
- return __awaiter(this, void 0, void 0, function* () {
5708
+ function _logout(db_1) {
5709
+ return __awaiter(this, arguments, void 0, function* (db, { deleteUnsyncedData = false } = {}) {
5661
5710
  // Clear the database without emptying configuration options.
5662
5711
  const [numUnsynced, loggedOut] = yield db.dx.transaction('rw', db.dx.tables, (tx) => __awaiter(this, void 0, void 0, function* () {
5663
5712
  // @ts-ignore
@@ -5695,11 +5744,11 @@ function _logout(db, { deleteUnsyncedData = false } = {}) {
5695
5744
 
5696
5745
  function otpFetchTokenCallback(db) {
5697
5746
  const { userInteraction } = db.cloud;
5698
- return function otpAuthenticate({ public_key, hints }) {
5699
- var _a;
5700
- return __awaiter(this, void 0, void 0, function* () {
5747
+ return function otpAuthenticate(_a) {
5748
+ return __awaiter(this, arguments, void 0, function* ({ public_key, hints }) {
5749
+ var _b;
5701
5750
  let tokenRequest;
5702
- const url = (_a = db.cloud.options) === null || _a === void 0 ? void 0 : _a.databaseUrl;
5751
+ const url = (_b = db.cloud.options) === null || _b === void 0 ? void 0 : _b.databaseUrl;
5703
5752
  if (!url)
5704
5753
  throw new Error(`No database URL given.`);
5705
5754
  if ((hints === null || hints === void 0 ? void 0 : hints.grant_type) === 'demo') {
@@ -5865,8 +5914,8 @@ function setCurrentUser(db, user) {
5865
5914
  }
5866
5915
 
5867
5916
  function login(db, hints) {
5868
- var _a;
5869
5917
  return __awaiter(this, void 0, void 0, function* () {
5918
+ var _a;
5870
5919
  const currentUser = yield db.getCurrentUser();
5871
5920
  const origUserId = currentUser.userId;
5872
5921
  if (currentUser.isLoggedIn && (!hints || (!hints.email && !hints.userId))) {
@@ -6303,8 +6352,7 @@ function createMutationTrackingMiddleware({ currentUserObservable, db, }) {
6303
6352
  outstandingTransactions.next(outstandingTransactions.value);
6304
6353
  };
6305
6354
  const txComplete = () => {
6306
- if (tx.mutationsAdded &&
6307
- !isEagerSyncDisabled(db)) {
6355
+ if (tx.mutationsAdded && !isEagerSyncDisabled(db)) {
6308
6356
  triggerSync(db, 'push');
6309
6357
  }
6310
6358
  removeTransaction();
@@ -6386,26 +6434,107 @@ function createMutationTrackingMiddleware({ currentUserObservable, db, }) {
6386
6434
  : mutateAndLog(req);
6387
6435
  } }));
6388
6436
  function mutateAndLog(req) {
6437
+ var _a, _b;
6389
6438
  const trans = req.trans;
6390
- trans.mutationsAdded = true;
6439
+ const unsyncedProps = (_b = (_a = db.cloud.options) === null || _a === void 0 ? void 0 : _a.unsyncedProperties) === null || _b === void 0 ? void 0 : _b[tableName];
6391
6440
  const { txid, currentUser: { userId }, } = trans;
6392
6441
  const { type } = req;
6393
6442
  const opNo = ++trans.opCount;
6443
+ function stripChangeSpec(changeSpec) {
6444
+ if (!unsyncedProps)
6445
+ return changeSpec;
6446
+ let rv = changeSpec;
6447
+ for (const keyPath of Object.keys(changeSpec)) {
6448
+ if (unsyncedProps.some((p) => keyPath === p || keyPath.startsWith(p + '.'))) {
6449
+ if (rv === changeSpec)
6450
+ rv = Object.assign({}, changeSpec); // clone on demand
6451
+ delete rv[keyPath];
6452
+ }
6453
+ }
6454
+ return rv;
6455
+ }
6394
6456
  return table.mutate(req).then((res) => {
6457
+ var _a;
6395
6458
  const { numFailures: hasFailures, failures } = res;
6396
6459
  let keys = type === 'delete' ? req.keys : res.results;
6397
6460
  let values = 'values' in req ? req.values : [];
6398
- let updates = 'updates' in req && req.updates;
6461
+ let changeSpec = 'changeSpec' in req ? req.changeSpec : undefined;
6462
+ let updates = 'updates' in req ? req.updates : undefined;
6399
6463
  if (hasFailures) {
6400
6464
  keys = keys.filter((_, idx) => !failures[idx]);
6401
6465
  values = values.filter((_, idx) => !failures[idx]);
6402
6466
  }
6467
+ if (unsyncedProps) {
6468
+ // Filter out unsynced properties
6469
+ values = values.map((value) => {
6470
+ const newValue = Object.assign({}, value);
6471
+ for (const prop of unsyncedProps) {
6472
+ delete newValue[prop];
6473
+ }
6474
+ return newValue;
6475
+ });
6476
+ if (changeSpec) {
6477
+ // modify operation with criteria and changeSpec.
6478
+ // We must strip out unsynced properties from changeSpec.
6479
+ // We deal with criteria later.
6480
+ changeSpec = stripChangeSpec(changeSpec);
6481
+ if (Object.keys(changeSpec).length === 0) {
6482
+ // Nothing to change on server
6483
+ return res;
6484
+ }
6485
+ }
6486
+ if (updates) {
6487
+ let strippedChangeSpecs = updates.changeSpecs.map(stripChangeSpec);
6488
+ let newUpdates = {
6489
+ keys: [],
6490
+ changeSpecs: [],
6491
+ };
6492
+ const validKeys = new RangeSet();
6493
+ let anyChangeSpecBecameEmpty = false;
6494
+ for (let i = 0, l = strippedChangeSpecs.length; i < l; ++i) {
6495
+ if (Object.keys(strippedChangeSpecs[i]).length > 0) {
6496
+ newUpdates.keys.push(updates.keys[i]);
6497
+ newUpdates.changeSpecs.push(strippedChangeSpecs[i]);
6498
+ validKeys.addKey(updates.keys[i]);
6499
+ }
6500
+ else {
6501
+ anyChangeSpecBecameEmpty = true;
6502
+ }
6503
+ }
6504
+ updates = newUpdates;
6505
+ if (anyChangeSpecBecameEmpty) {
6506
+ // Some keys were stripped. We must also strip them from keys and values
6507
+ let newKeys = [];
6508
+ let newValues = [];
6509
+ for (let i = 0, l = keys.length; i < l; ++i) {
6510
+ if (validKeys.hasKey(keys[i])) {
6511
+ newKeys.push(keys[i]);
6512
+ newValues.push(values[i]);
6513
+ }
6514
+ }
6515
+ keys = newKeys;
6516
+ values = newValues;
6517
+ }
6518
+ }
6519
+ }
6403
6520
  const ts = Date.now();
6404
6521
  // Canonicalize req.criteria.index to null if it's on the primary key.
6405
- const criteria = 'criteria' in req && req.criteria
6522
+ let criteria = 'criteria' in req && req.criteria
6406
6523
  ? Object.assign(Object.assign({}, req.criteria), { index: req.criteria.index === schema.primaryKey.keyPath // Use null to inform server that criteria is on primary key
6407
6524
  ? null // This will disable the server from trying to log consistent operations where it shouldnt.
6408
6525
  : req.criteria.index }) : undefined;
6526
+ if (unsyncedProps && (criteria === null || criteria === void 0 ? void 0 : criteria.index)) {
6527
+ const keyPaths = (_a = schema.indexes.find((idx) => idx.name === criteria.index)) === null || _a === void 0 ? void 0 : _a.keyPath;
6528
+ const involvedProps = keyPaths
6529
+ ? typeof keyPaths === 'string'
6530
+ ? [keyPaths]
6531
+ : keyPaths
6532
+ : [];
6533
+ if (involvedProps.some((p) => unsyncedProps === null || unsyncedProps === void 0 ? void 0 : unsyncedProps.includes(p))) {
6534
+ // Don't log criteria on unsynced properties as the server could not test them.
6535
+ criteria = undefined;
6536
+ }
6537
+ }
6409
6538
  const mut = req.type === 'delete'
6410
6539
  ? {
6411
6540
  type: 'delete',
@@ -6426,7 +6555,7 @@ function createMutationTrackingMiddleware({ currentUserObservable, db, }) {
6426
6555
  userId,
6427
6556
  values,
6428
6557
  }
6429
- : criteria && req.changeSpec
6558
+ : criteria && changeSpec
6430
6559
  ? {
6431
6560
  // Common changeSpec for all keys
6432
6561
  type: 'modify',
@@ -6434,37 +6563,51 @@ function createMutationTrackingMiddleware({ currentUserObservable, db, }) {
6434
6563
  opNo,
6435
6564
  keys,
6436
6565
  criteria,
6437
- changeSpec: req.changeSpec,
6566
+ changeSpec,
6438
6567
  txid,
6439
6568
  userId,
6440
6569
  }
6441
- : updates
6570
+ : changeSpec
6442
6571
  ? {
6443
- // One changeSpec per key
6572
+ // In case criteria involved an unsynced property, we go for keys instead.
6444
6573
  type: 'update',
6445
6574
  ts,
6446
6575
  opNo,
6447
- keys: updates.keys,
6448
- changeSpecs: updates.changeSpecs,
6449
- txid,
6450
- userId,
6451
- }
6452
- : {
6453
- type: 'upsert',
6454
- ts,
6455
- opNo,
6456
6576
  keys,
6457
- values,
6577
+ changeSpecs: keys.map(() => changeSpec),
6458
6578
  txid,
6459
6579
  userId,
6460
- };
6580
+ }
6581
+ : updates
6582
+ ? {
6583
+ // One changeSpec per key
6584
+ type: 'update',
6585
+ ts,
6586
+ opNo,
6587
+ keys: updates.keys,
6588
+ changeSpecs: updates.changeSpecs,
6589
+ txid,
6590
+ userId,
6591
+ }
6592
+ : {
6593
+ type: 'upsert',
6594
+ ts,
6595
+ opNo,
6596
+ keys,
6597
+ values,
6598
+ txid,
6599
+ userId,
6600
+ };
6461
6601
  if ('isAdditionalChunk' in req && req.isAdditionalChunk) {
6462
6602
  mut.isAdditionalChunk = true;
6463
6603
  }
6464
6604
  return keys.length > 0 || criteria
6465
6605
  ? mutsTable
6466
6606
  .mutate({ type: 'add', trans, values: [mut] }) // Log entry
6467
- .then(() => res) // Return original response
6607
+ .then(() => {
6608
+ trans.mutationsAdded = true; // Mark transaction as having added mutations to trigger eager sync
6609
+ return res; // Return original response
6610
+ })
6468
6611
  : res;
6469
6612
  });
6470
6613
  }
@@ -6548,10 +6691,18 @@ function overrideParseStoresSpec(origFunc, dexie) {
6548
6691
  };
6549
6692
  }
6550
6693
 
6694
+ function performGuardedJob(db, jobName, job) {
6695
+ if (typeof navigator === 'undefined' || !navigator.locks) {
6696
+ // No support for guarding jobs. IE11, node.js, etc.
6697
+ return job();
6698
+ }
6699
+ return navigator.locks.request(db.name + '|' + jobName, () => job());
6700
+ }
6701
+
6551
6702
  function performInitialSync(db, cloudOptions, cloudSchema) {
6552
6703
  return __awaiter(this, void 0, void 0, function* () {
6553
6704
  console.debug('Performing initial sync');
6554
- yield sync(db, cloudOptions, cloudSchema, { isInitialSync: true });
6705
+ yield performGuardedJob(db, CURRENT_SYNC_WORKER, () => sync(db, cloudOptions, cloudSchema, { isInitialSync: true }));
6555
6706
  console.debug('Done initial sync');
6556
6707
  });
6557
6708
  }
@@ -6638,29 +6789,35 @@ function createYClientUpdateObservable(db) {
6638
6789
  updatesTable: p.updatesTable,
6639
6790
  }))));
6640
6791
  return merge(...yTableRecords.map(({ table, ydocProp, updatesTable }) => {
6641
- let currentUnsentFrom = 1;
6642
- return liveQuery(() => __awaiter(this, void 0, void 0, function* () {
6643
- const yTbl = db.table(updatesTable);
6644
- const unsentFrom = yield yTbl
6645
- .where({ i: DEXIE_CLOUD_SYNCER_ID })
6646
- .first()
6647
- .then((syncer) => (syncer === null || syncer === void 0 ? void 0 : syncer.unsentFrom) || 1);
6648
- currentUnsentFrom = Math.max(currentUnsentFrom, unsentFrom);
6649
- const addedUpdates = yield listUpdatesSince(yTbl, currentUnsentFrom);
6650
- // Update currentUnsentFrom to only listen for updates that will be newer than the ones we emitted.
6651
- currentUnsentFrom = Math.max(currentUnsentFrom, ...addedUpdates.map((update) => update.i + 1));
6652
- return addedUpdates
6653
- .filter((update) => update.f && update.f & 1) // Only include local updates
6654
- .map((update) => {
6655
- return {
6656
- type: 'u-c',
6657
- table,
6658
- prop: ydocProp,
6659
- k: update.k,
6660
- u: update.u,
6661
- i: update.i,
6662
- };
6663
- });
6792
+ // Per updates table (table+prop combo), we first read syncer.unsentFrom,
6793
+ // and then start listening for updates since that number.
6794
+ const yTbl = db.table(updatesTable);
6795
+ return from$1(yTbl.get(DEXIE_CLOUD_SYNCER_ID)).pipe(switchMap$1((syncer) => {
6796
+ let currentUnsentFrom = (syncer === null || syncer === void 0 ? void 0 : syncer.unsentFrom) || 1;
6797
+ return from$1(liveQuery(() => __awaiter(this, void 0, void 0, function* () {
6798
+ const addedUpdates = yield listUpdatesSince(yTbl, currentUnsentFrom);
6799
+ return addedUpdates
6800
+ .filter((update) => update.f && update.f & 1) // Only include local updates
6801
+ .map((update) => {
6802
+ return {
6803
+ type: 'u-c',
6804
+ table,
6805
+ prop: ydocProp,
6806
+ k: update.k,
6807
+ u: update.u,
6808
+ i: update.i,
6809
+ };
6810
+ });
6811
+ }))).pipe(tap$1((addedUpdates) => {
6812
+ // Update currentUnsentFrom to only listen for updates that will be newer than the ones we emitted.
6813
+ // (Before, we did this within the liveQuery, but that caused a bug because
6814
+ // a cancelled emittion of a liveQuery would update the currentUnsentFrom without
6815
+ // emitting anything, leading to that we jumped over some updates. Here we update it
6816
+ // after the liveQuery has emitted its updates)
6817
+ if (addedUpdates.length > 0) {
6818
+ currentUnsentFrom = addedUpdates.at(-1).i + 1;
6819
+ }
6820
+ }));
6664
6821
  }));
6665
6822
  })).pipe(
6666
6823
  // Flatten the array of messages.
@@ -6680,6 +6837,23 @@ function getAwarenessLibrary(db) {
6680
6837
  const awarenessWeakMap = new WeakMap();
6681
6838
  const getDocAwareness = (doc) => awarenessWeakMap.get(doc);
6682
6839
 
6840
+ const wm = new WeakMap();
6841
+ /** A property (package-private) on Y.Doc that is used
6842
+ * to signal that the server wants us to send a 'doc-open' message
6843
+ * to the server for this document.
6844
+ *
6845
+ * @param doc
6846
+ * @returns
6847
+ */
6848
+ function getOpenDocSignal(doc) {
6849
+ let signal = wm.get(doc);
6850
+ if (!signal) {
6851
+ signal = new Subject();
6852
+ wm.set(doc, signal);
6853
+ }
6854
+ return signal;
6855
+ }
6856
+
6683
6857
  const SERVER_PING_TIMEOUT = 20000;
6684
6858
  const CLIENT_PING_INTERVAL = 30000;
6685
6859
  const FAIL_RETRY_WAIT_TIME = 60000;
@@ -6842,9 +7016,6 @@ class WSConnection extends Subscription$1 {
6842
7016
  if (msg.type === 'error') {
6843
7017
  throw new Error(`Error message from dexie-cloud: ${msg.error}`);
6844
7018
  }
6845
- else if (msg.type === 'rev') {
6846
- this.rev = msg.rev; // No meaning but seems reasonable.
6847
- }
6848
7019
  else if (msg.type === 'aware') {
6849
7020
  const docCache = DexieYProvider.getDocCache(this.db.dx);
6850
7021
  const doc = docCache.find(msg.table, msg.k, msg.prop);
@@ -6859,7 +7030,19 @@ class WSConnection extends Subscription$1 {
6859
7030
  else if (msg.type === 'u-ack' || msg.type === 'u-reject' || msg.type === 'u-s' || msg.type === 'in-sync') {
6860
7031
  applyYServerMessages([msg], this.db);
6861
7032
  }
7033
+ else if (msg.type === 'doc-open') {
7034
+ const docCache = DexieYProvider.getDocCache(this.db.dx);
7035
+ const doc = docCache.find(msg.table, msg.k, msg.prop);
7036
+ if (doc) {
7037
+ getOpenDocSignal(doc).next(); // Make yHandler reopen the document on server.
7038
+ }
7039
+ }
7040
+ else if (msg.type === 'outdated-server-rev' || msg.type === 'y-complete-sync-done') {
7041
+ // Won't happen but need this for typing.
7042
+ throw new Error('Outdated server revision or y-complete-sync-done not expected over WebSocket - only in sync using fetch()');
7043
+ }
6862
7044
  else if (msg.type !== 'pong') {
7045
+ // Forward the request to our subscriber, wich is in messageFromServerQueue.ts (via connectWebSocket's subscribe() at the end!)
6863
7046
  this.subscriber.next(msg);
6864
7047
  }
6865
7048
  }
@@ -6896,6 +7079,10 @@ class WSConnection extends Subscription$1 {
6896
7079
  }
6897
7080
  console.debug('dexie-cloud WebSocket send', msg.type, msg);
6898
7081
  if (msg.type === 'ready') {
7082
+ // Ok, we are certain to have stored everything up until revision msg.rev.
7083
+ // Update this.rev in case of reconnect - remember where we were and don't just start over!
7084
+ this.rev = msg.rev;
7085
+ // ... and then send along the request to the server so it would also be updated!
6899
7086
  (_a = this.ws) === null || _a === void 0 ? void 0 : _a.send(TSON.stringify(msg));
6900
7087
  }
6901
7088
  else {
@@ -6977,7 +7164,7 @@ function connectWebSocket(db) {
6977
7164
  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]));
6978
7165
  }
6979
7166
  return new BehaviorSubject([userLogin, syncState]);
6980
- }), 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]) => {
7167
+ }), 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]) => {
6981
7168
  var _a;
6982
7169
  if (!((_a = db.cloud.persistedSyncState) === null || _a === void 0 ? void 0 : _a.value)) {
6983
7170
  // Restart the flow if persistedSyncState is not yet available.
@@ -7038,22 +7225,14 @@ function connectWebSocket(db) {
7038
7225
  }
7039
7226
 
7040
7227
  function isSyncNeeded(db) {
7041
- var _a;
7042
7228
  return __awaiter(this, void 0, void 0, function* () {
7229
+ var _a;
7043
7230
  return ((_a = db.cloud.options) === null || _a === void 0 ? void 0 : _a.databaseUrl) && db.cloud.schema
7044
7231
  ? yield sync(db, db.cloud.options, db.cloud.schema, { justCheckIfNeeded: true })
7045
7232
  : false;
7046
7233
  });
7047
7234
  }
7048
7235
 
7049
- function performGuardedJob(db, jobName, job) {
7050
- if (typeof navigator === 'undefined' || !navigator.locks) {
7051
- // No support for guarding jobs. IE11, node.js, etc.
7052
- return job();
7053
- }
7054
- return navigator.locks.request(db.name + '|' + jobName, () => job());
7055
- }
7056
-
7057
7236
  const ongoingSyncs = new WeakMap();
7058
7237
  function syncIfPossible(db, cloudOptions, cloudSchema, options) {
7059
7238
  const ongoing = ongoingSyncs.get(db);
@@ -7107,20 +7286,7 @@ function syncIfPossible(db, cloudOptions, cloudSchema, options) {
7107
7286
  try {
7108
7287
  // Check if should delay sync due to ratelimit:
7109
7288
  yield checkSyncRateLimitDelay(db);
7110
- // Check if we need to lock the sync job. Not needed if we are the service worker.
7111
- if (db.cloud.isServiceWorkerDB) {
7112
- // We are the dedicated sync SW:
7113
- yield sync(db, cloudOptions, cloudSchema, options);
7114
- }
7115
- else if (!db.cloud.usingServiceWorker) {
7116
- // We use a flow that is better suited for the case when multiple workers want to
7117
- // do the same thing.
7118
- yield performGuardedJob(db, CURRENT_SYNC_WORKER, () => sync(db, cloudOptions, cloudSchema, options));
7119
- }
7120
- else {
7121
- assert(false);
7122
- throw new Error('Internal _syncIfPossible() - invalid precondition - should not have been called.');
7123
- }
7289
+ yield performGuardedJob(db, CURRENT_SYNC_WORKER, () => sync(db, cloudOptions, cloudSchema, options));
7124
7290
  ongoingSyncs.delete(db);
7125
7291
  console.debug('Done sync');
7126
7292
  }
@@ -7723,6 +7889,7 @@ class PermissionChecker {
7723
7889
  // If user can update any prop in any table in this realm, return true unless
7724
7890
  // it regards to ownership change:
7725
7891
  if (this.permissions.update === '*') {
7892
+ // @ts-ignore
7726
7893
  return props.every((prop) => prop !== 'owner');
7727
7894
  }
7728
7895
  const tablePermissions = (_b = this.permissions.update) === null || _b === void 0 ? void 0 : _b[this.tableName];
@@ -7796,125 +7963,151 @@ const getInvitesObservable = associate((db) => {
7796
7963
  function createYHandler(db) {
7797
7964
  return (provider) => {
7798
7965
  var _a;
7799
- const awap = getAwarenessLibrary(db);
7800
7966
  const doc = provider.doc;
7801
- const { parentTable, parentId, parentProp, updatesTable } = doc.meta;
7967
+ const { parentTable } = doc.meta || {};
7802
7968
  if (!((_a = db.cloud.schema) === null || _a === void 0 ? void 0 : _a[parentTable].markedForSync)) {
7803
7969
  return; // The table that holds the doc is not marked for sync - leave it to dexie. No syncing, no awareness.
7804
7970
  }
7805
- let awareness = new awap.Awareness(doc);
7806
- awarenessWeakMap.set(doc, awareness);
7807
- provider.awareness = awareness;
7808
- awareness.on('update', ({ added, updated, removed }, origin) => {
7809
- // Send the update
7810
- const changedClients = added.concat(updated).concat(removed);
7811
- const user = db.cloud.currentUser.value;
7812
- if (origin !== 'server' && user.isLoggedIn && !isEagerSyncDisabled(db)) {
7813
- const update = awap.encodeAwarenessUpdate(awareness, changedClients);
7971
+ let awareness;
7972
+ Object.defineProperty(provider, 'awareness', {
7973
+ get() {
7974
+ if (awareness)
7975
+ return awareness;
7976
+ awareness = createAwareness(db, doc, provider);
7977
+ awarenessWeakMap.set(doc, awareness);
7978
+ return awareness;
7979
+ },
7980
+ });
7981
+ };
7982
+ }
7983
+ function createAwareness(db, doc, provider) {
7984
+ const { parentTable, parentId, parentProp, updatesTable } = doc.meta;
7985
+ const awap = getAwarenessLibrary(db);
7986
+ const awareness = new awap.Awareness(doc);
7987
+ const reopenDocSignal = getOpenDocSignal(doc);
7988
+ awareness.on('update', ({ added, updated, removed }, origin) => {
7989
+ // Send the update
7990
+ const changedClients = added.concat(updated).concat(removed);
7991
+ const user = db.cloud.currentUser.value;
7992
+ if (origin !== 'server' && user.isLoggedIn && !isEagerSyncDisabled(db)) {
7993
+ const update = awap.encodeAwarenessUpdate(awareness, changedClients);
7994
+ db.messageProducer.next({
7995
+ type: 'aware',
7996
+ table: parentTable,
7997
+ prop: parentProp,
7998
+ k: doc.meta.parentId,
7999
+ u: update,
8000
+ });
8001
+ if (provider.destroyed) {
8002
+ // We're called from awareness.on('destroy') that did
8003
+ // removeAwarenessStates.
8004
+ // It's time to also send the doc-close message that dexie-cloud understands
8005
+ // and uses to stop subscribing for updates and awareness updates and brings
8006
+ // down the cached information in memory on the WS connection for this.
7814
8007
  db.messageProducer.next({
7815
- type: 'aware',
8008
+ type: 'doc-close',
7816
8009
  table: parentTable,
7817
8010
  prop: parentProp,
7818
8011
  k: doc.meta.parentId,
7819
- u: update,
7820
8012
  });
7821
- if (provider.destroyed) {
7822
- // We're called from awareness.on('destroy') that did
7823
- // removeAwarenessStates.
7824
- // It's time to also send the doc-close message that dexie-cloud understands
7825
- // and uses to stop subscribing for updates and awareness updates and brings
7826
- // down the cached information in memory on the WS connection for this.
7827
- db.messageProducer.next({
7828
- type: 'doc-close',
7829
- table: parentTable,
7830
- prop: parentProp,
7831
- k: doc.meta.parentId
7832
- });
7833
- }
7834
8013
  }
7835
- });
7836
- awareness.on('destroy', () => {
7837
- // Signal to server that this provider is destroyed (the update event will be triggered, which
7838
- // in turn will trigger db.messageProducer that will send the message to the server if WS is connected)
7839
- awap.removeAwarenessStates(awareness, [doc.clientID], 'provider destroyed');
7840
- });
7841
- // Now wait til document is loaded and then open the document on the server
7842
- provider.on('load', () => __awaiter(this, void 0, void 0, function* () {
8014
+ }
8015
+ });
8016
+ awareness.on('destroy', () => {
8017
+ // Signal to server that this provider is destroyed (the update event will be triggered, which
8018
+ // in turn will trigger db.messageProducer that will send the message to the server if WS is connected)
8019
+ awap.removeAwarenessStates(awareness, [doc.clientID], 'provider destroyed');
8020
+ });
8021
+ // Open the document on the server
8022
+ (() => __awaiter(this, void 0, void 0, function* () {
8023
+ if (provider.destroyed)
8024
+ return;
8025
+ let connected = false;
8026
+ let currentFlowId = 1;
8027
+ const subscription = combineLatest([
8028
+ db.cloud.webSocketStatus, // Wake up when webSocket status changes
8029
+ reopenDocSignal.pipe(startWith$1(null)), // Wake up when reopenDocSignal emits
8030
+ ]).subscribe(([wsStatus]) => {
7843
8031
  if (provider.destroyed)
7844
8032
  return;
7845
- let connected = false;
7846
- let currentFlowId = 1;
7847
- const subscription = db.cloud.webSocketStatus.subscribe((wsStatus) => {
7848
- if (provider.destroyed)
8033
+ // Keep "connected" state in a variable so we can check it after async operations
8034
+ connected = wsStatus === 'connected';
8035
+ // We are or got connected. Open the document on the server.
8036
+ const user = db.cloud.currentUser.value;
8037
+ if (wsStatus === 'connected' &&
8038
+ user.isLoggedIn &&
8039
+ !isEagerSyncDisabled(db)) {
8040
+ ++currentFlowId;
8041
+ openDocumentOnServer().catch((error) => {
8042
+ console.warn(`Error catched in createYHandler.ts: ${error}`);
8043
+ });
8044
+ }
8045
+ });
8046
+ // Wait until WebSocket is connected
8047
+ provider.addCleanupHandler(subscription);
8048
+ /** Sends an 'doc-open' message to server whenever websocket becomes
8049
+ * connected, or if it is already connected.
8050
+ * The flow is aborted in case websocket is disconnected while querying
8051
+ * information required to compute the state vector. Flow is also
8052
+ * aborted in case document or provider has been destroyed during
8053
+ * the async parts of the task.
8054
+ *
8055
+ * The state vector is only computed from the updates that have occured
8056
+ * after the last full sync - which could very often be zero - in which
8057
+ * case no state vector is sent (then the server already knows us by
8058
+ * revision)
8059
+ *
8060
+ * When server gets the doc-open message, it will authorize us for
8061
+ * whether we are allowed to read / write to this document, and then
8062
+ * keep the cached information in memory on the WS connection for this
8063
+ * particular document, as well as subscribe to updates and awareness updates
8064
+ * from other clients on the document.
8065
+ */
8066
+ function openDocumentOnServer() {
8067
+ return __awaiter(this, void 0, void 0, function* () {
8068
+ const myFlow = currentFlowId; // So we can abort when a new flow is started
8069
+ const yTbl = db.table(updatesTable);
8070
+ const syncStateTbl = db.$syncState;
8071
+ const [receivedUntil, yServerRev] = yield db.transaction('r', syncStateTbl, yTbl, () => __awaiter(this, void 0, void 0, function* () {
8072
+ const syncState = yield yTbl.get(DEXIE_CLOUD_SYNCER_ID);
8073
+ const persistedSyncState = yield syncStateTbl.get('syncState');
8074
+ return [
8075
+ (syncState === null || syncState === void 0 ? void 0 : syncState.receivedUntil) || 0,
8076
+ (persistedSyncState === null || persistedSyncState === void 0 ? void 0 : persistedSyncState.yServerRevision) ||
8077
+ (persistedSyncState === null || persistedSyncState === void 0 ? void 0 : persistedSyncState.serverRevision),
8078
+ ];
8079
+ }));
8080
+ // After every await, check if we still should be working on this task.
8081
+ if (provider.destroyed || currentFlowId !== myFlow || !connected)
7849
8082
  return;
7850
- // Keep "connected" state in a variable so we can check it after async operations
7851
- connected = wsStatus === 'connected';
7852
- // We are or got connected. Open the document on the server.
7853
- const user = db.cloud.currentUser.value;
7854
- if (wsStatus === "connected" && user.isLoggedIn && !isEagerSyncDisabled(db)) {
7855
- ++currentFlowId;
7856
- openDocumentOnServer().catch(error => {
7857
- console.warn(`Error catched in createYHandler.ts: ${error}`);
7858
- });
8083
+ const docOpenMsg = {
8084
+ type: 'doc-open',
8085
+ table: parentTable,
8086
+ prop: parentProp,
8087
+ k: parentId,
8088
+ serverRev: yServerRev,
8089
+ };
8090
+ const serverUpdatesSinceLastSync = yield yTbl
8091
+ .where('i')
8092
+ .between(receivedUntil, Infinity, false)
8093
+ .filter((update) => cmp(update.k, parentId) === 0 && // Only updates for this document
8094
+ ((update.f || 0) & 1) === 0 // Don't include local changes
8095
+ )
8096
+ .toArray();
8097
+ // After every await, check if we still should be working on this task.
8098
+ if (provider.destroyed || currentFlowId !== myFlow || !connected)
8099
+ return;
8100
+ if (serverUpdatesSinceLastSync.length > 0) {
8101
+ const Y = $Y(db); // Get the Yjs library from Dexie constructor options
8102
+ const mergedUpdate = Y.mergeUpdatesV2(serverUpdatesSinceLastSync.map((update) => update.u));
8103
+ const stateVector = Y.encodeStateVectorFromUpdateV2(mergedUpdate);
8104
+ docOpenMsg.sv = stateVector;
7859
8105
  }
8106
+ db.messageProducer.next(docOpenMsg);
7860
8107
  });
7861
- // Wait until WebSocket is connected
7862
- provider.addCleanupHandler(subscription);
7863
- /** Sends an 'doc-open' message to server whenever websocket becomes
7864
- * connected, or if it is already connected.
7865
- * The flow is aborted in case websocket is disconnected while querying
7866
- * information required to compute the state vector. Flow is also
7867
- * aborted in case document or provider has been destroyed during
7868
- * the async parts of the task.
7869
- *
7870
- * The state vector is only computed from the updates that have occured
7871
- * after the last full sync - which could very often be zero - in which
7872
- * case no state vector is sent (then the server already knows us by
7873
- * revision)
7874
- *
7875
- * When server gets the doc-open message, it will authorized us for
7876
- * whether we are allowed to read / write to this document, and then
7877
- * keep the cached information in memory on the WS connection for this
7878
- * particular document, as well as subscribe to updates and awareness updates
7879
- * from other clients on the document.
7880
- */
7881
- function openDocumentOnServer(wsStatus) {
7882
- return __awaiter(this, void 0, void 0, function* () {
7883
- const myFlow = currentFlowId; // So we can abort when a new flow is started
7884
- const yTbl = db.table(updatesTable);
7885
- const syncState = yield yTbl.get(DEXIE_CLOUD_SYNCER_ID);
7886
- // After every await, check if we still should be working on this task.
7887
- if (provider.destroyed || currentFlowId !== myFlow || !connected)
7888
- return;
7889
- const receivedUntil = (syncState === null || syncState === void 0 ? void 0 : syncState.receivedUntil) || 0;
7890
- const docOpenMsg = {
7891
- type: 'doc-open',
7892
- table: parentTable,
7893
- prop: parentProp,
7894
- k: parentId,
7895
- serverRev: syncState === null || syncState === void 0 ? void 0 : syncState.serverRev,
7896
- };
7897
- const serverUpdatesSinceLastSync = yield yTbl
7898
- .where('i')
7899
- .between(receivedUntil, Infinity, false)
7900
- .filter((update) => cmp(update.k, parentId) === 0 && // Only updates for this document
7901
- ((update.f || 0) & 1) === 0 // Don't include local changes
7902
- )
7903
- .toArray();
7904
- // After every await, check if we still should be working on this task.
7905
- if (provider.destroyed || currentFlowId !== myFlow || !connected)
7906
- return;
7907
- if (serverUpdatesSinceLastSync.length > 0) {
7908
- const Y = $Y(db); // Get the Yjs library from Dexie constructor options
7909
- const mergedUpdate = Y.mergeUpdatesV2(serverUpdatesSinceLastSync.map((update) => update.u));
7910
- const stateVector = Y.encodeStateVectorFromUpdateV2(mergedUpdate);
7911
- docOpenMsg.sv = stateVector;
7912
- }
7913
- db.messageProducer.next(docOpenMsg);
7914
- });
7915
- }
7916
- }));
7917
- };
8108
+ }
8109
+ }))();
8110
+ return awareness;
7918
8111
  }
7919
8112
 
7920
8113
  const DEFAULT_OPTIONS = {
@@ -7957,7 +8150,7 @@ function dexieCloud(dexie) {
7957
8150
  const syncComplete = new Subject();
7958
8151
  dexie.cloud = {
7959
8152
  // @ts-ignore
7960
- version: "4.1.0-alpha.8",
8153
+ version: "4.1.0-beta.25",
7961
8154
  options: Object.assign({}, DEFAULT_OPTIONS),
7962
8155
  schema: null,
7963
8156
  get currentUserId() {
@@ -7993,16 +8186,16 @@ function dexieCloud(dexie) {
7993
8186
  }
7994
8187
  updateSchemaFromOptions(dexie.cloud.schema, dexie.cloud.options);
7995
8188
  },
7996
- logout({ force } = {}) {
7997
- return __awaiter(this, void 0, void 0, function* () {
8189
+ logout() {
8190
+ return __awaiter(this, arguments, void 0, function* ({ force } = {}) {
7998
8191
  force
7999
8192
  ? yield _logout(DexieCloudDB(dexie), { deleteUnsyncedData: true })
8000
8193
  : yield logout(DexieCloudDB(dexie));
8001
8194
  });
8002
8195
  },
8003
- sync({ wait, purpose } = { wait: true, purpose: 'push' }) {
8004
- var _a;
8005
- return __awaiter(this, void 0, void 0, function* () {
8196
+ sync() {
8197
+ return __awaiter(this, arguments, void 0, function* ({ wait, purpose } = { wait: true, purpose: 'push' }) {
8198
+ var _a;
8006
8199
  if (wait === undefined)
8007
8200
  wait = true;
8008
8201
  const db = DexieCloudDB(dexie);
@@ -8060,8 +8253,8 @@ function dexieCloud(dexie) {
8060
8253
  dexie.use(createImplicitPropSetterMiddleware(DexieCloudDB(dexie)));
8061
8254
  dexie.use(createIdGenerationMiddleware(DexieCloudDB(dexie)));
8062
8255
  function onDbReady(dexie) {
8063
- var _a, _b, _c, _d, _e, _f, _g;
8064
8256
  return __awaiter(this, void 0, void 0, function* () {
8257
+ var _a, _b, _c, _d, _e, _f, _g;
8065
8258
  closed = false; // As Dexie calls us, we are not closed anymore. Maybe reopened? Remember db.ready event is registered with sticky flag!
8066
8259
  const db = DexieCloudDB(dexie);
8067
8260
  // Setup default GUI:
@@ -8084,7 +8277,7 @@ function dexieCloud(dexie) {
8084
8277
  ? yield navigator.serviceWorker.getRegistrations()
8085
8278
  : [];
8086
8279
  const [initiallySynced, lastSyncedRealms] = yield db.transaction('rw', db.$syncState, () => __awaiter(this, void 0, void 0, function* () {
8087
- var _h, _j;
8280
+ var _a, _b;
8088
8281
  const { options, schema } = db.cloud;
8089
8282
  const [persistedOptions, persistedSchema, persistedSyncState] = yield Promise.all([
8090
8283
  db.getOptions(),
@@ -8106,7 +8299,7 @@ function dexieCloud(dexie) {
8106
8299
  delete newPersistedOptions.awarenessProtocol;
8107
8300
  yield db.$syncState.put(newPersistedOptions, 'options');
8108
8301
  }
8109
- if (((_h = db.cloud.options) === null || _h === void 0 ? void 0 : _h.tryUseServiceWorker) &&
8302
+ if (((_a = db.cloud.options) === null || _a === void 0 ? void 0 : _a.tryUseServiceWorker) &&
8110
8303
  'serviceWorker' in navigator &&
8111
8304
  swRegistrations.length > 0 &&
8112
8305
  !DISABLE_SERVICEWORKER_STRATEGY) {
@@ -8120,7 +8313,7 @@ function dexieCloud(dexie) {
8120
8313
  // Not configured for using service worker or no service worker
8121
8314
  // registration exists. Don't rely on service worker to do any job.
8122
8315
  // Use LocalSyncWorker instead.
8123
- if (((_j = db.cloud.options) === null || _j === void 0 ? void 0 : _j.tryUseServiceWorker) &&
8316
+ if (((_b = db.cloud.options) === null || _b === void 0 ? void 0 : _b.tryUseServiceWorker) &&
8124
8317
  !db.cloud.isServiceWorkerDB) {
8125
8318
  console.debug('dexie-cloud-addon: Not using service worker.', swRegistrations.length === 0
8126
8319
  ? 'No SW registrations found.'
@@ -8163,10 +8356,6 @@ function dexieCloud(dexie) {
8163
8356
  db.setInitiallySynced(true);
8164
8357
  }
8165
8358
  verifySchema(db);
8166
- if (((_b = db.cloud.options) === null || _b === void 0 ? void 0 : _b.databaseUrl) && !initiallySynced) {
8167
- yield performInitialSync(db, db.cloud.options, db.cloud.schema);
8168
- db.setInitiallySynced(true);
8169
- }
8170
8359
  // Manage CurrentUser observable:
8171
8360
  throwIfClosed();
8172
8361
  if (!db.cloud.isServiceWorkerDB) {
@@ -8191,20 +8380,29 @@ function dexieCloud(dexie) {
8191
8380
  // HERE: If requireAuth, do athentication now.
8192
8381
  let changedUser = false;
8193
8382
  const user = yield db.getCurrentUser();
8194
- const requireAuth = (_c = db.cloud.options) === null || _c === void 0 ? void 0 : _c.requireAuth;
8383
+ const requireAuth = (_b = db.cloud.options) === null || _b === void 0 ? void 0 : _b.requireAuth;
8195
8384
  if (requireAuth) {
8196
- if (typeof requireAuth === 'object') {
8197
- // requireAuth contains login hints. Check if we already fulfil it:
8198
- if (!user.isLoggedIn ||
8199
- (requireAuth.userId && user.userId !== requireAuth.userId) ||
8200
- (requireAuth.email && user.email !== requireAuth.email)) {
8201
- // If not, login the configured user:
8202
- changedUser = yield login(db, requireAuth);
8203
- }
8385
+ if (db.cloud.isServiceWorkerDB) {
8386
+ // If this is a service worker DB, we can't do authentication here,
8387
+ // we just wait until the application has done it.
8388
+ console.debug('Dexie Cloud Service worker. Waiting for application to authenticate.');
8389
+ yield firstValueFrom(currentUserEmitter.pipe(filter((user) => !!user.isLoggedIn), take(1)));
8390
+ console.debug('Dexie Cloud Service worker. Application has authenticated.');
8204
8391
  }
8205
- else if (!user.isLoggedIn) {
8206
- // requireAuth is true and user is not logged in
8207
- changedUser = yield login(db);
8392
+ else {
8393
+ if (typeof requireAuth === 'object') {
8394
+ // requireAuth contains login hints. Check if we already fulfil it:
8395
+ if (!user.isLoggedIn ||
8396
+ (requireAuth.userId && user.userId !== requireAuth.userId) ||
8397
+ (requireAuth.email && user.email !== requireAuth.email)) {
8398
+ // If not, login the configured user:
8399
+ changedUser = yield login(db, requireAuth);
8400
+ }
8401
+ }
8402
+ else if (!user.isLoggedIn) {
8403
+ // requireAuth is true and user is not logged in
8404
+ changedUser = yield login(db);
8405
+ }
8208
8406
  }
8209
8407
  }
8210
8408
  if (user.isLoggedIn && (!lastSyncedRealms || !lastSyncedRealms.includes(user.userId))) {
@@ -8219,8 +8417,17 @@ function dexieCloud(dexie) {
8219
8417
  localSyncWorker.stop();
8220
8418
  localSyncWorker = null;
8221
8419
  throwIfClosed();
8420
+ const doInitialSync = ((_c = db.cloud.options) === null || _c === void 0 ? void 0 : _c.databaseUrl) && (!initiallySynced || changedUser);
8421
+ if (doInitialSync) {
8422
+ // Do the initial sync directly in the browser thread no matter if we are using service worker or not.
8423
+ yield performInitialSync(db, db.cloud.options, db.cloud.schema);
8424
+ db.setInitiallySynced(true);
8425
+ }
8426
+ throwIfClosed();
8222
8427
  if (db.cloud.usingServiceWorker && ((_d = db.cloud.options) === null || _d === void 0 ? void 0 : _d.databaseUrl)) {
8223
- registerSyncEvent(db, changedUser ? 'pull' : 'push').catch(() => { });
8428
+ if (!doInitialSync) {
8429
+ registerSyncEvent(db, 'push').catch(() => { });
8430
+ }
8224
8431
  registerPeriodicSyncEvent(db).catch(() => { });
8225
8432
  }
8226
8433
  else if (((_e = db.cloud.options) === null || _e === void 0 ? void 0 : _e.databaseUrl) &&
@@ -8229,7 +8436,9 @@ function dexieCloud(dexie) {
8229
8436
  // There's no SW. Start SyncWorker instead.
8230
8437
  localSyncWorker = LocalSyncWorker(db, db.cloud.options, db.cloud.schema);
8231
8438
  localSyncWorker.start();
8232
- triggerSync(db, changedUser ? 'pull' : 'push');
8439
+ if (!doInitialSync) {
8440
+ triggerSync(db, 'push');
8441
+ }
8233
8442
  }
8234
8443
  // Listen to online event and do sync.
8235
8444
  throwIfClosed();
@@ -8249,7 +8458,7 @@ function dexieCloud(dexie) {
8249
8458
  });
8250
8459
  }));
8251
8460
  }
8252
- // Connect WebSocket unless we
8461
+ // Connect WebSocket unless we are in a service worker or websocket is disabled.
8253
8462
  if (((_f = db.cloud.options) === null || _f === void 0 ? void 0 : _f.databaseUrl) &&
8254
8463
  !((_g = db.cloud.options) === null || _g === void 0 ? void 0 : _g.disableWebSocket) &&
8255
8464
  !IS_SERVICE_WORKER) {
@@ -8259,7 +8468,7 @@ function dexieCloud(dexie) {
8259
8468
  }
8260
8469
  }
8261
8470
  // @ts-ignore
8262
- dexieCloud.version = "4.1.0-alpha.8";
8471
+ dexieCloud.version = "4.1.0-beta.25";
8263
8472
  Dexie.Cloud = dexieCloud;
8264
8473
 
8265
8474
  // In case the SW lives for a while, let it reuse already opened connections:
@@ -8289,8 +8498,8 @@ function syncDB(dbName, purpose) {
8289
8498
  }
8290
8499
  return promise;
8291
8500
  function _syncDB(dbName, purpose) {
8292
- var _a;
8293
8501
  return __awaiter(this, void 0, void 0, function* () {
8502
+ var _a;
8294
8503
  let db = managedDBs.get(dbName);
8295
8504
  if (!db) {
8296
8505
  console.debug('Dexie Cloud SW: Creating new Dexie instance for', dbName);