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

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 (38) 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
  38. package/postinstall-banner.js +23 -0
@@ -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.26, 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, tap as tap$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,38 +6789,42 @@ 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.
6667
6824
  // If messageProducer emits empty array, nothing is emitted
6668
6825
  // but if messageProducer emits array of messages, they are
6669
6826
  // emitted one by one.
6670
- mergeMap$1((messages) => messages), tap$1(message => {
6671
- console.debug('dexie-cloud emitting y-c', message);
6672
- }));
6827
+ mergeMap$1((messages) => messages));
6673
6828
  }
6674
6829
 
6675
6830
  function getAwarenessLibrary(db) {
@@ -6682,6 +6837,23 @@ function getAwarenessLibrary(db) {
6682
6837
  const awarenessWeakMap = new WeakMap();
6683
6838
  const getDocAwareness = (doc) => awarenessWeakMap.get(doc);
6684
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
+
6685
6857
  const SERVER_PING_TIMEOUT = 20000;
6686
6858
  const CLIENT_PING_INTERVAL = 30000;
6687
6859
  const FAIL_RETRY_WAIT_TIME = 60000;
@@ -6844,9 +7016,6 @@ class WSConnection extends Subscription$1 {
6844
7016
  if (msg.type === 'error') {
6845
7017
  throw new Error(`Error message from dexie-cloud: ${msg.error}`);
6846
7018
  }
6847
- else if (msg.type === 'rev') {
6848
- this.rev = msg.rev; // No meaning but seems reasonable.
6849
- }
6850
7019
  else if (msg.type === 'aware') {
6851
7020
  const docCache = DexieYProvider.getDocCache(this.db.dx);
6852
7021
  const doc = docCache.find(msg.table, msg.k, msg.prop);
@@ -6861,7 +7030,19 @@ class WSConnection extends Subscription$1 {
6861
7030
  else if (msg.type === 'u-ack' || msg.type === 'u-reject' || msg.type === 'u-s' || msg.type === 'in-sync') {
6862
7031
  applyYServerMessages([msg], this.db);
6863
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
+ }
6864
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!)
6865
7046
  this.subscriber.next(msg);
6866
7047
  }
6867
7048
  }
@@ -6898,6 +7079,10 @@ class WSConnection extends Subscription$1 {
6898
7079
  }
6899
7080
  console.debug('dexie-cloud WebSocket send', msg.type, msg);
6900
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!
6901
7086
  (_a = this.ws) === null || _a === void 0 ? void 0 : _a.send(TSON.stringify(msg));
6902
7087
  }
6903
7088
  else {
@@ -6979,7 +7164,7 @@ function connectWebSocket(db) {
6979
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]));
6980
7165
  }
6981
7166
  return new BehaviorSubject([userLogin, syncState]);
6982
- }), 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]) => {
6983
7168
  var _a;
6984
7169
  if (!((_a = db.cloud.persistedSyncState) === null || _a === void 0 ? void 0 : _a.value)) {
6985
7170
  // Restart the flow if persistedSyncState is not yet available.
@@ -7040,22 +7225,14 @@ function connectWebSocket(db) {
7040
7225
  }
7041
7226
 
7042
7227
  function isSyncNeeded(db) {
7043
- var _a;
7044
7228
  return __awaiter(this, void 0, void 0, function* () {
7229
+ var _a;
7045
7230
  return ((_a = db.cloud.options) === null || _a === void 0 ? void 0 : _a.databaseUrl) && db.cloud.schema
7046
7231
  ? yield sync(db, db.cloud.options, db.cloud.schema, { justCheckIfNeeded: true })
7047
7232
  : false;
7048
7233
  });
7049
7234
  }
7050
7235
 
7051
- function performGuardedJob(db, jobName, job) {
7052
- if (typeof navigator === 'undefined' || !navigator.locks) {
7053
- // No support for guarding jobs. IE11, node.js, etc.
7054
- return job();
7055
- }
7056
- return navigator.locks.request(db.name + '|' + jobName, () => job());
7057
- }
7058
-
7059
7236
  const ongoingSyncs = new WeakMap();
7060
7237
  function syncIfPossible(db, cloudOptions, cloudSchema, options) {
7061
7238
  const ongoing = ongoingSyncs.get(db);
@@ -7109,20 +7286,7 @@ function syncIfPossible(db, cloudOptions, cloudSchema, options) {
7109
7286
  try {
7110
7287
  // Check if should delay sync due to ratelimit:
7111
7288
  yield checkSyncRateLimitDelay(db);
7112
- // Check if we need to lock the sync job. Not needed if we are the service worker.
7113
- if (db.cloud.isServiceWorkerDB) {
7114
- // We are the dedicated sync SW:
7115
- yield sync(db, cloudOptions, cloudSchema, options);
7116
- }
7117
- else if (!db.cloud.usingServiceWorker) {
7118
- // We use a flow that is better suited for the case when multiple workers want to
7119
- // do the same thing.
7120
- yield performGuardedJob(db, CURRENT_SYNC_WORKER, () => sync(db, cloudOptions, cloudSchema, options));
7121
- }
7122
- else {
7123
- assert(false);
7124
- throw new Error('Internal _syncIfPossible() - invalid precondition - should not have been called.');
7125
- }
7289
+ yield performGuardedJob(db, CURRENT_SYNC_WORKER, () => sync(db, cloudOptions, cloudSchema, options));
7126
7290
  ongoingSyncs.delete(db);
7127
7291
  console.debug('Done sync');
7128
7292
  }
@@ -7725,6 +7889,7 @@ class PermissionChecker {
7725
7889
  // If user can update any prop in any table in this realm, return true unless
7726
7890
  // it regards to ownership change:
7727
7891
  if (this.permissions.update === '*') {
7892
+ // @ts-ignore
7728
7893
  return props.every((prop) => prop !== 'owner');
7729
7894
  }
7730
7895
  const tablePermissions = (_b = this.permissions.update) === null || _b === void 0 ? void 0 : _b[this.tableName];
@@ -7798,125 +7963,151 @@ const getInvitesObservable = associate((db) => {
7798
7963
  function createYHandler(db) {
7799
7964
  return (provider) => {
7800
7965
  var _a;
7801
- const awap = getAwarenessLibrary(db);
7802
7966
  const doc = provider.doc;
7803
- const { parentTable, parentId, parentProp, updatesTable } = doc.meta;
7967
+ const { parentTable } = doc.meta || {};
7804
7968
  if (!((_a = db.cloud.schema) === null || _a === void 0 ? void 0 : _a[parentTable].markedForSync)) {
7805
7969
  return; // The table that holds the doc is not marked for sync - leave it to dexie. No syncing, no awareness.
7806
7970
  }
7807
- let awareness = new awap.Awareness(doc);
7808
- awarenessWeakMap.set(doc, awareness);
7809
- provider.awareness = awareness;
7810
- awareness.on('update', ({ added, updated, removed }, origin) => {
7811
- // Send the update
7812
- const changedClients = added.concat(updated).concat(removed);
7813
- const user = db.cloud.currentUser.value;
7814
- if (origin !== 'server' && user.isLoggedIn && !isEagerSyncDisabled(db)) {
7815
- 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.
7816
8007
  db.messageProducer.next({
7817
- type: 'aware',
8008
+ type: 'doc-close',
7818
8009
  table: parentTable,
7819
8010
  prop: parentProp,
7820
8011
  k: doc.meta.parentId,
7821
- u: update,
7822
8012
  });
7823
- if (provider.destroyed) {
7824
- // We're called from awareness.on('destroy') that did
7825
- // removeAwarenessStates.
7826
- // It's time to also send the doc-close message that dexie-cloud understands
7827
- // and uses to stop subscribing for updates and awareness updates and brings
7828
- // down the cached information in memory on the WS connection for this.
7829
- db.messageProducer.next({
7830
- type: 'doc-close',
7831
- table: parentTable,
7832
- prop: parentProp,
7833
- k: doc.meta.parentId
7834
- });
7835
- }
7836
8013
  }
7837
- });
7838
- awareness.on('destroy', () => {
7839
- // Signal to server that this provider is destroyed (the update event will be triggered, which
7840
- // in turn will trigger db.messageProducer that will send the message to the server if WS is connected)
7841
- awap.removeAwarenessStates(awareness, [doc.clientID], 'provider destroyed');
7842
- });
7843
- // Now wait til document is loaded and then open the document on the server
7844
- 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]) => {
7845
8031
  if (provider.destroyed)
7846
8032
  return;
7847
- let connected = false;
7848
- let currentFlowId = 1;
7849
- const subscription = db.cloud.webSocketStatus.subscribe((wsStatus) => {
7850
- 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)
7851
8082
  return;
7852
- // Keep "connected" state in a variable so we can check it after async operations
7853
- connected = wsStatus === 'connected';
7854
- // We are or got connected. Open the document on the server.
7855
- const user = db.cloud.currentUser.value;
7856
- if (wsStatus === "connected" && user.isLoggedIn && !isEagerSyncDisabled(db)) {
7857
- ++currentFlowId;
7858
- openDocumentOnServer().catch(error => {
7859
- console.warn(`Error catched in createYHandler.ts: ${error}`);
7860
- });
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;
7861
8105
  }
8106
+ db.messageProducer.next(docOpenMsg);
7862
8107
  });
7863
- // Wait until WebSocket is connected
7864
- provider.addCleanupHandler(subscription);
7865
- /** Sends an 'doc-open' message to server whenever websocket becomes
7866
- * connected, or if it is already connected.
7867
- * The flow is aborted in case websocket is disconnected while querying
7868
- * information required to compute the state vector. Flow is also
7869
- * aborted in case document or provider has been destroyed during
7870
- * the async parts of the task.
7871
- *
7872
- * The state vector is only computed from the updates that have occured
7873
- * after the last full sync - which could very often be zero - in which
7874
- * case no state vector is sent (then the server already knows us by
7875
- * revision)
7876
- *
7877
- * When server gets the doc-open message, it will authorized us for
7878
- * whether we are allowed to read / write to this document, and then
7879
- * keep the cached information in memory on the WS connection for this
7880
- * particular document, as well as subscribe to updates and awareness updates
7881
- * from other clients on the document.
7882
- */
7883
- function openDocumentOnServer(wsStatus) {
7884
- return __awaiter(this, void 0, void 0, function* () {
7885
- const myFlow = currentFlowId; // So we can abort when a new flow is started
7886
- const yTbl = db.table(updatesTable);
7887
- const syncState = yield yTbl.get(DEXIE_CLOUD_SYNCER_ID);
7888
- // After every await, check if we still should be working on this task.
7889
- if (provider.destroyed || currentFlowId !== myFlow || !connected)
7890
- return;
7891
- const receivedUntil = (syncState === null || syncState === void 0 ? void 0 : syncState.receivedUntil) || 0;
7892
- const docOpenMsg = {
7893
- type: 'doc-open',
7894
- table: parentTable,
7895
- prop: parentProp,
7896
- k: parentId,
7897
- serverRev: syncState === null || syncState === void 0 ? void 0 : syncState.serverRev,
7898
- };
7899
- const serverUpdatesSinceLastSync = yield yTbl
7900
- .where('i')
7901
- .between(receivedUntil, Infinity, false)
7902
- .filter((update) => cmp(update.k, parentId) === 0 && // Only updates for this document
7903
- ((update.f || 0) & 1) === 0 // Don't include local changes
7904
- )
7905
- .toArray();
7906
- // After every await, check if we still should be working on this task.
7907
- if (provider.destroyed || currentFlowId !== myFlow || !connected)
7908
- return;
7909
- if (serverUpdatesSinceLastSync.length > 0) {
7910
- const Y = $Y(db); // Get the Yjs library from Dexie constructor options
7911
- const mergedUpdate = Y.mergeUpdatesV2(serverUpdatesSinceLastSync.map((update) => update.u));
7912
- const stateVector = Y.encodeStateVectorFromUpdateV2(mergedUpdate);
7913
- docOpenMsg.sv = stateVector;
7914
- }
7915
- db.messageProducer.next(docOpenMsg);
7916
- });
7917
- }
7918
- }));
7919
- };
8108
+ }
8109
+ }))();
8110
+ return awareness;
7920
8111
  }
7921
8112
 
7922
8113
  const DEFAULT_OPTIONS = {
@@ -7959,7 +8150,7 @@ function dexieCloud(dexie) {
7959
8150
  const syncComplete = new Subject();
7960
8151
  dexie.cloud = {
7961
8152
  // @ts-ignore
7962
- version: "4.1.0-alpha.9",
8153
+ version: "4.1.0-beta.26",
7963
8154
  options: Object.assign({}, DEFAULT_OPTIONS),
7964
8155
  schema: null,
7965
8156
  get currentUserId() {
@@ -7995,16 +8186,16 @@ function dexieCloud(dexie) {
7995
8186
  }
7996
8187
  updateSchemaFromOptions(dexie.cloud.schema, dexie.cloud.options);
7997
8188
  },
7998
- logout({ force } = {}) {
7999
- return __awaiter(this, void 0, void 0, function* () {
8189
+ logout() {
8190
+ return __awaiter(this, arguments, void 0, function* ({ force } = {}) {
8000
8191
  force
8001
8192
  ? yield _logout(DexieCloudDB(dexie), { deleteUnsyncedData: true })
8002
8193
  : yield logout(DexieCloudDB(dexie));
8003
8194
  });
8004
8195
  },
8005
- sync({ wait, purpose } = { wait: true, purpose: 'push' }) {
8006
- var _a;
8007
- 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;
8008
8199
  if (wait === undefined)
8009
8200
  wait = true;
8010
8201
  const db = DexieCloudDB(dexie);
@@ -8062,8 +8253,8 @@ function dexieCloud(dexie) {
8062
8253
  dexie.use(createImplicitPropSetterMiddleware(DexieCloudDB(dexie)));
8063
8254
  dexie.use(createIdGenerationMiddleware(DexieCloudDB(dexie)));
8064
8255
  function onDbReady(dexie) {
8065
- var _a, _b, _c, _d, _e, _f, _g;
8066
8256
  return __awaiter(this, void 0, void 0, function* () {
8257
+ var _a, _b, _c, _d, _e, _f, _g;
8067
8258
  closed = false; // As Dexie calls us, we are not closed anymore. Maybe reopened? Remember db.ready event is registered with sticky flag!
8068
8259
  const db = DexieCloudDB(dexie);
8069
8260
  // Setup default GUI:
@@ -8086,7 +8277,7 @@ function dexieCloud(dexie) {
8086
8277
  ? yield navigator.serviceWorker.getRegistrations()
8087
8278
  : [];
8088
8279
  const [initiallySynced, lastSyncedRealms] = yield db.transaction('rw', db.$syncState, () => __awaiter(this, void 0, void 0, function* () {
8089
- var _h, _j;
8280
+ var _a, _b;
8090
8281
  const { options, schema } = db.cloud;
8091
8282
  const [persistedOptions, persistedSchema, persistedSyncState] = yield Promise.all([
8092
8283
  db.getOptions(),
@@ -8108,7 +8299,7 @@ function dexieCloud(dexie) {
8108
8299
  delete newPersistedOptions.awarenessProtocol;
8109
8300
  yield db.$syncState.put(newPersistedOptions, 'options');
8110
8301
  }
8111
- 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) &&
8112
8303
  'serviceWorker' in navigator &&
8113
8304
  swRegistrations.length > 0 &&
8114
8305
  !DISABLE_SERVICEWORKER_STRATEGY) {
@@ -8122,7 +8313,7 @@ function dexieCloud(dexie) {
8122
8313
  // Not configured for using service worker or no service worker
8123
8314
  // registration exists. Don't rely on service worker to do any job.
8124
8315
  // Use LocalSyncWorker instead.
8125
- 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) &&
8126
8317
  !db.cloud.isServiceWorkerDB) {
8127
8318
  console.debug('dexie-cloud-addon: Not using service worker.', swRegistrations.length === 0
8128
8319
  ? 'No SW registrations found.'
@@ -8165,10 +8356,6 @@ function dexieCloud(dexie) {
8165
8356
  db.setInitiallySynced(true);
8166
8357
  }
8167
8358
  verifySchema(db);
8168
- if (((_b = db.cloud.options) === null || _b === void 0 ? void 0 : _b.databaseUrl) && !initiallySynced) {
8169
- yield performInitialSync(db, db.cloud.options, db.cloud.schema);
8170
- db.setInitiallySynced(true);
8171
- }
8172
8359
  // Manage CurrentUser observable:
8173
8360
  throwIfClosed();
8174
8361
  if (!db.cloud.isServiceWorkerDB) {
@@ -8193,20 +8380,29 @@ function dexieCloud(dexie) {
8193
8380
  // HERE: If requireAuth, do athentication now.
8194
8381
  let changedUser = false;
8195
8382
  const user = yield db.getCurrentUser();
8196
- 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;
8197
8384
  if (requireAuth) {
8198
- if (typeof requireAuth === 'object') {
8199
- // requireAuth contains login hints. Check if we already fulfil it:
8200
- if (!user.isLoggedIn ||
8201
- (requireAuth.userId && user.userId !== requireAuth.userId) ||
8202
- (requireAuth.email && user.email !== requireAuth.email)) {
8203
- // If not, login the configured user:
8204
- changedUser = yield login(db, requireAuth);
8205
- }
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.');
8206
8391
  }
8207
- else if (!user.isLoggedIn) {
8208
- // requireAuth is true and user is not logged in
8209
- 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
+ }
8210
8406
  }
8211
8407
  }
8212
8408
  if (user.isLoggedIn && (!lastSyncedRealms || !lastSyncedRealms.includes(user.userId))) {
@@ -8221,8 +8417,17 @@ function dexieCloud(dexie) {
8221
8417
  localSyncWorker.stop();
8222
8418
  localSyncWorker = null;
8223
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();
8224
8427
  if (db.cloud.usingServiceWorker && ((_d = db.cloud.options) === null || _d === void 0 ? void 0 : _d.databaseUrl)) {
8225
- registerSyncEvent(db, changedUser ? 'pull' : 'push').catch(() => { });
8428
+ if (!doInitialSync) {
8429
+ registerSyncEvent(db, 'push').catch(() => { });
8430
+ }
8226
8431
  registerPeriodicSyncEvent(db).catch(() => { });
8227
8432
  }
8228
8433
  else if (((_e = db.cloud.options) === null || _e === void 0 ? void 0 : _e.databaseUrl) &&
@@ -8231,7 +8436,9 @@ function dexieCloud(dexie) {
8231
8436
  // There's no SW. Start SyncWorker instead.
8232
8437
  localSyncWorker = LocalSyncWorker(db, db.cloud.options, db.cloud.schema);
8233
8438
  localSyncWorker.start();
8234
- triggerSync(db, changedUser ? 'pull' : 'push');
8439
+ if (!doInitialSync) {
8440
+ triggerSync(db, 'push');
8441
+ }
8235
8442
  }
8236
8443
  // Listen to online event and do sync.
8237
8444
  throwIfClosed();
@@ -8251,7 +8458,7 @@ function dexieCloud(dexie) {
8251
8458
  });
8252
8459
  }));
8253
8460
  }
8254
- // Connect WebSocket unless we
8461
+ // Connect WebSocket unless we are in a service worker or websocket is disabled.
8255
8462
  if (((_f = db.cloud.options) === null || _f === void 0 ? void 0 : _f.databaseUrl) &&
8256
8463
  !((_g = db.cloud.options) === null || _g === void 0 ? void 0 : _g.disableWebSocket) &&
8257
8464
  !IS_SERVICE_WORKER) {
@@ -8261,7 +8468,7 @@ function dexieCloud(dexie) {
8261
8468
  }
8262
8469
  }
8263
8470
  // @ts-ignore
8264
- dexieCloud.version = "4.1.0-alpha.9";
8471
+ dexieCloud.version = "4.1.0-beta.26";
8265
8472
  Dexie.Cloud = dexieCloud;
8266
8473
 
8267
8474
  // In case the SW lives for a while, let it reuse already opened connections:
@@ -8291,8 +8498,8 @@ function syncDB(dbName, purpose) {
8291
8498
  }
8292
8499
  return promise;
8293
8500
  function _syncDB(dbName, purpose) {
8294
- var _a;
8295
8501
  return __awaiter(this, void 0, void 0, function* () {
8502
+ var _a;
8296
8503
  let db = managedDBs.get(dbName);
8297
8504
  if (!db) {
8298
8505
  console.debug('Dexie Cloud SW: Creating new Dexie instance for', dbName);