dexie-cloud-addon 4.1.0-alpha.2 → 4.1.0-alpha.21

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/WSObservable.d.ts +4 -4
  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 +584 -237
  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 +426 -235
  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/applyYMessages.d.ts +4 -1
  16. package/dist/modern/yjs/listYClientMessagesAndStateVector.d.ts +3 -1
  17. package/dist/umd/DexieCloudOptions.d.ts +3 -0
  18. package/dist/umd/TSON.d.ts +1 -1
  19. package/dist/umd/WSObservable.d.ts +4 -4
  20. package/dist/umd/define-ydoc-trigger.d.ts +2 -0
  21. package/dist/umd/dexie-cloud-addon.d.ts +1 -0
  22. package/dist/umd/dexie-cloud-addon.js +582 -234
  23. package/dist/umd/dexie-cloud-addon.js.map +1 -1
  24. package/dist/umd/dexie-cloud-addon.min.js +1 -1
  25. package/dist/umd/dexie-cloud-addon.min.js.map +1 -1
  26. package/dist/umd/getInvitesObservable.d.ts +11 -11
  27. package/dist/umd/service-worker.js +424 -233
  28. package/dist/umd/service-worker.js.map +1 -1
  29. package/dist/umd/service-worker.min.js +1 -1
  30. package/dist/umd/service-worker.min.js.map +1 -1
  31. package/dist/umd/yjs/applyYMessages.d.ts +4 -1
  32. package/dist/umd/yjs/listYClientMessagesAndStateVector.d.ts +3 -1
  33. package/package.json +4 -4
  34. package/dist/modern/yjs/listYClientMessages.d.ts +0 -3
  35. package/dist/umd/yjs/listYClientMessages.d.ts +0 -3
  36. /package/dist/modern/yjs/{y.d.ts → Y.d.ts} +0 -0
  37. /package/dist/umd/yjs/{y.d.ts → Y.d.ts} +0 -0
@@ -8,7 +8,7 @@
8
8
  *
9
9
  * ==========================================================================
10
10
  *
11
- * Version 4.1.0-alpha.2, Mon Oct 07 2024
11
+ * Version 4.1.0-alpha.21, Mon Nov 18 2024
12
12
  *
13
13
  * https://dexie.org
14
14
  *
@@ -1768,8 +1768,8 @@
1768
1768
  });
1769
1769
  }
1770
1770
  function registerPeriodicSyncEvent(db) {
1771
- var _a;
1772
1771
  return __awaiter(this, void 0, void 0, function* () {
1772
+ var _a;
1773
1773
  try {
1774
1774
  // Register periodicSync event to SW:
1775
1775
  // @ts-ignore
@@ -1834,8 +1834,8 @@
1834
1834
  return btoa(strs.join(""));
1835
1835
  };
1836
1836
 
1837
- function computeRealmSetHash({ realms, inviteRealms, }) {
1838
- return __awaiter(this, void 0, void 0, function* () {
1837
+ function computeRealmSetHash(_a) {
1838
+ return __awaiter(this, arguments, void 0, function* ({ realms, inviteRealms, }) {
1839
1839
  const data = JSON.stringify([
1840
1840
  ...realms.map((realmId) => ({ realmId, accepted: true })),
1841
1841
  ...inviteRealms.map((realmId) => ({ realmId, accepted: false })),
@@ -1871,8 +1871,8 @@
1871
1871
  return concat.apply([], a);
1872
1872
  }
1873
1873
 
1874
- function listClientChanges(mutationTables, db, { since = {}, limit = Infinity } = {}) {
1875
- return __awaiter(this, void 0, void 0, function* () {
1874
+ function listClientChanges(mutationTables_1, db_1) {
1875
+ return __awaiter(this, arguments, void 0, function* (mutationTables, db, { since = {}, limit = Infinity } = {}) {
1876
1876
  const allMutsOnTables = yield Promise.all(mutationTables.map((mutationTable) => __awaiter(this, void 0, void 0, function* () {
1877
1877
  const tableName = getTableFromMutationTable(mutationTable.name);
1878
1878
  const lastRevision = since[tableName];
@@ -2757,13 +2757,17 @@
2757
2757
  function encodeYMessage(msg) {
2758
2758
  const encoder = new Encoder();
2759
2759
  writeVarString(encoder, msg.type);
2760
- writeVarString(encoder, msg.table);
2761
- writeVarString(encoder, msg.prop);
2760
+ if ('table' in msg)
2761
+ writeVarString(encoder, msg.table);
2762
+ if ('prop' in msg)
2763
+ writeVarString(encoder, msg.prop);
2762
2764
  switch (msg.type) {
2763
2765
  case 'u-ack':
2764
2766
  case 'u-reject':
2765
2767
  writeBigUint64(encoder, BigInt(msg.i));
2766
2768
  break;
2769
+ case 'outdated-server-rev':
2770
+ break;
2767
2771
  default:
2768
2772
  writeAny(encoder, msg.k);
2769
2773
  switch (msg.type) {
@@ -3107,6 +3111,9 @@
3107
3111
  function decodeYMessage(a) {
3108
3112
  const decoder = new Decoder(a);
3109
3113
  const type = readVarString(decoder);
3114
+ if (type === 'outdated-server-rev') {
3115
+ return { type };
3116
+ }
3110
3117
  const table = readVarString(decoder);
3111
3118
  const prop = readVarString(decoder);
3112
3119
  switch (type) {
@@ -3519,8 +3526,8 @@
3519
3526
  }
3520
3527
 
3521
3528
  function loadAccessToken(db) {
3522
- var _a, _b, _c;
3523
3529
  return __awaiter(this, void 0, void 0, function* () {
3530
+ var _a, _b, _c;
3524
3531
  const currentUser = yield db.getCurrentUser();
3525
3532
  const { accessToken, accessTokenExpiration, refreshToken, refreshTokenExpiration, claims, } = currentUser;
3526
3533
  if (!accessToken)
@@ -4350,8 +4357,8 @@
4350
4357
  // seconds (given that there is a Ratelimit-Reset header).
4351
4358
  let syncRatelimitDelays = new WeakMap();
4352
4359
  function checkSyncRateLimitDelay(db) {
4353
- var _a, _b;
4354
4360
  return __awaiter(this, void 0, void 0, function* () {
4361
+ var _a, _b;
4355
4362
  const delatMilliseconds = ((_b = (_a = syncRatelimitDelays.get(db)) === null || _a === void 0 ? void 0 : _a.getTime()) !== null && _b !== void 0 ? _b : 0) - Date.now();
4356
4363
  if (delatMilliseconds > 0) {
4357
4364
  console.debug(`Stalling sync request ${delatMilliseconds} ms to spare ratelimits`);
@@ -4664,13 +4671,12 @@
4664
4671
  * @param db
4665
4672
  * @returns
4666
4673
  */
4667
- function listYClientMessagesAndStateVector(db) {
4668
- var _a;
4674
+ function listYClientMessagesAndStateVector(db, tablesToSync) {
4669
4675
  return __awaiter(this, void 0, void 0, function* () {
4670
4676
  const result = [];
4671
4677
  const lastUpdateIds = {};
4672
- for (const table of db.tables) {
4673
- if (table.schema.yProps && ((_a = db.cloud.schema) === null || _a === void 0 ? void 0 : _a[table.name].markedForSync)) {
4678
+ for (const table of tablesToSync) {
4679
+ if (table.schema.yProps) {
4674
4680
  for (const yProp of table.schema.yProps) {
4675
4681
  const Y = $Y(db); // This is how we retrieve the user-provided Y library
4676
4682
  const yTable = db.table(yProp.updatesTable); // the updates-table for this combo of table+propName
@@ -4757,12 +4763,14 @@
4757
4763
 
4758
4764
  function applyYServerMessages(yMessages, db) {
4759
4765
  return __awaiter(this, void 0, void 0, function* () {
4760
- const result = {};
4766
+ var _a;
4767
+ const receivedUntils = {};
4768
+ let resyncNeeded = false;
4761
4769
  for (const m of yMessages) {
4762
4770
  switch (m.type) {
4763
4771
  case 'u-s': {
4764
4772
  const utbl = getUpdatesTable(db, m.table, m.prop);
4765
- result[utbl.name] = yield utbl.add({
4773
+ receivedUntils[utbl.name] = yield utbl.add({
4766
4774
  k: m.k,
4767
4775
  u: m.u,
4768
4776
  });
@@ -4787,7 +4795,24 @@
4787
4795
  // See my question in https://discuss.yjs.dev/t/generate-an-inverse-update/2765
4788
4796
  console.debug(`Y update rejected. Deleting it.`);
4789
4797
  const utbl = getUpdatesTable(db, m.table, m.prop);
4790
- yield utbl.delete(m.i);
4798
+ // Delete the rejected update and all local updates since (avoid holes in the CRDT)
4799
+ // and destroy it's open document if there is one.
4800
+ const primaryKey = (_a = (yield utbl.get(m.i))) === null || _a === void 0 ? void 0 : _a.k;
4801
+ if (primaryKey != null) {
4802
+ yield db.transaction('rw', utbl, (tx) => {
4803
+ // @ts-ignore
4804
+ tx.idbtrans._rejecting_y_ypdate = true; // Inform ydoc triggers that we delete because of a rejection and not GC
4805
+ return utbl
4806
+ .where('i')
4807
+ .aboveOrEqual(m.i)
4808
+ .filter((u) => Dexie.cmp(u.k, primaryKey) === 0 && ((u.f || 0) & 1) === 1)
4809
+ .delete();
4810
+ });
4811
+ // Destroy active doc
4812
+ const activeDoc = Dexie.DexieYProvider.getDocCache(db.dx).find(m.table, primaryKey, m.prop);
4813
+ if (activeDoc)
4814
+ activeDoc.destroy(); // Destroy the document so that editors don't continue to work on it
4815
+ }
4791
4816
  break;
4792
4817
  }
4793
4818
  case 'in-sync': {
@@ -4797,22 +4822,28 @@
4797
4822
  }
4798
4823
  break;
4799
4824
  }
4825
+ case 'outdated-server-rev':
4826
+ resyncNeeded = true;
4827
+ break;
4800
4828
  }
4801
4829
  }
4802
- return result;
4830
+ return {
4831
+ receivedUntils,
4832
+ resyncNeeded,
4833
+ };
4803
4834
  });
4804
4835
  }
4805
4836
 
4806
4837
  function updateYSyncStates(lastUpdateIdsBeforeSync, receivedUntilsAfterSync, db, serverRevision) {
4807
- var _a, _b;
4808
4838
  return __awaiter(this, void 0, void 0, function* () {
4839
+ var _a, _b, _c, _d, _e;
4809
4840
  // We want to update unsentFrom for each yTable to the value specified in first argument
4810
4841
  // because we got those values before we synced with server and here we are back from server
4811
4842
  // that has successfully received all those messages - no matter if the last update was a client or server update,
4812
4843
  // we can safely store unsentFrom to a value of the last update + 1 here.
4813
4844
  // We also want to update receivedUntil for each yTable to the value specified in the second argument,
4814
4845
  // because that contains the highest resulted id of each update from server after storing it.
4815
- // We could do these two tasks separately, but that would require two update calls on the same YSyncState, so
4846
+ // We could do these two tasks separately, but that would require two update calls on the same YSyncState, so
4816
4847
  // to optimize the dexie calls, we merge these two maps into a single one so we can do a single update request
4817
4848
  // per yTable.
4818
4849
  const mergedSpec = {};
@@ -4824,28 +4855,42 @@
4824
4855
  (_b = mergedSpec[yTable]) !== null && _b !== void 0 ? _b : (mergedSpec[yTable] = {});
4825
4856
  mergedSpec[yTable].receivedUntil = lastUpdateId;
4826
4857
  }
4827
- // Now go through the merged map and update YSyncStates accordingly:
4828
- for (const [yTable, { unsentFrom, receivedUntil }] of Object.entries(mergedSpec)) {
4858
+ // Now go through all yTables and update their YSyncStates:
4859
+ const allYTables = Object.values(db.dx._dbSchema)
4860
+ .filter((tblSchema) => tblSchema.yProps)
4861
+ .map((tblSchema) => tblSchema.yProps.map((yProp) => yProp.updatesTable))
4862
+ .flat();
4863
+ for (const yTable of allYTables) {
4864
+ const mergedEntry = mergedSpec[yTable];
4865
+ const unsentFrom = (_c = mergedEntry === null || mergedEntry === void 0 ? void 0 : mergedEntry.unsentFrom) !== null && _c !== void 0 ? _c : 1;
4866
+ const receivedUntil = (_e = (_d = mergedEntry === null || mergedEntry === void 0 ? void 0 : mergedEntry.receivedUntil) !== null && _d !== void 0 ? _d :
4867
+ // from local because we are in the same parent transaction (in sync.ts) that
4868
+ // applied all updates from the server
4869
+ (yield db
4870
+ .table(yTable)
4871
+ .where('i')
4872
+ .between(1, Infinity) // Because i might be string DEXIE_CLOUD_SYNCER_ID if not a number.
4873
+ .reverse()
4874
+ .limit(1)
4875
+ .primaryKeys())[0]) !== null && _e !== void 0 ? _e : 0;
4829
4876
  // We're already in a transaction, but for the sake of
4830
4877
  // code readability and correctness, let's launch an atomic sub transaction:
4831
4878
  yield db.transaction('rw', yTable, () => __awaiter(this, void 0, void 0, function* () {
4832
- const state = yield db.table(yTable).get(DEXIE_CLOUD_SYNCER_ID);
4879
+ const state = yield db
4880
+ .table(yTable)
4881
+ .get(DEXIE_CLOUD_SYNCER_ID);
4833
4882
  if (!state) {
4834
4883
  yield db.table(yTable).add({
4835
4884
  i: DEXIE_CLOUD_SYNCER_ID,
4836
- unsentFrom: unsentFrom || 1,
4837
- receivedUntil: receivedUntil || 0,
4885
+ unsentFrom,
4886
+ receivedUntil,
4838
4887
  serverRev: serverRevision,
4839
4888
  });
4840
4889
  }
4841
4890
  else {
4842
- if (unsentFrom) {
4843
- state.unsentFrom = Math.max(unsentFrom, state.unsentFrom || 1);
4844
- }
4845
- if (receivedUntil) {
4846
- state.receivedUntil = Math.max(receivedUntil, state.receivedUntil || 0);
4847
- state.serverRev = serverRevision;
4848
- }
4891
+ state.unsentFrom = Math.max(unsentFrom, state.unsentFrom || 1);
4892
+ state.receivedUntil = Math.max(receivedUntil, state.receivedUntil || 0);
4893
+ state.serverRev = serverRevision;
4849
4894
  yield db.table(yTable).put(state);
4850
4895
  }
4851
4896
  }));
@@ -4856,9 +4901,11 @@
4856
4901
  const BINSTREAM_TYPE_REALMID = 1;
4857
4902
  const BINSTREAM_TYPE_TABLE_AND_PROP = 2;
4858
4903
  const BINSTREAM_TYPE_DOCUMENT = 3;
4859
- function downloadYDocsFromServer(db, databaseUrl, { yDownloadedRealms, realms }) {
4860
- return __awaiter(this, void 0, void 0, function* () {
4861
- if (yDownloadedRealms && realms && realms.every(realmId => yDownloadedRealms[realmId] === '*')) {
4904
+ function downloadYDocsFromServer(db_1, databaseUrl_1, _a) {
4905
+ return __awaiter(this, arguments, void 0, function* (db, databaseUrl, { yDownloadedRealms, realms }) {
4906
+ if (yDownloadedRealms &&
4907
+ realms &&
4908
+ realms.every((realmId) => yDownloadedRealms[realmId] === '*')) {
4862
4909
  return; // Already done!
4863
4910
  }
4864
4911
  console.debug('Downloading Y.Docs from added realms');
@@ -4898,16 +4945,19 @@
4898
4945
  yield yTable.bulkAdd(docsToInsert);
4899
4946
  docsToInsert = [];
4900
4947
  }
4901
- if (currentRealmId && currentTable && currentProp && (lastDoc || completedRealm)) {
4902
- yield db.$syncState.update('syncState', completedRealm
4903
- ? '*'
4904
- : {
4905
- [`yDownloadedRealms.${currentRealmId}`]: {
4948
+ if (currentRealmId &&
4949
+ ((currentTable && currentProp && lastDoc) || completedRealm)) {
4950
+ yield db.$syncState.update('syncState', (syncState) => {
4951
+ const yDownloadedRealms = syncState.yDownloadedRealms || {};
4952
+ yDownloadedRealms[currentRealmId] = completedRealm
4953
+ ? '*'
4954
+ : {
4906
4955
  tbl: currentTable,
4907
4956
  prop: currentProp,
4908
4957
  key: lastDoc.k,
4909
- },
4910
- });
4958
+ };
4959
+ syncState.yDownloadedRealms = yDownloadedRealms;
4960
+ });
4911
4961
  }
4912
4962
  });
4913
4963
  }
@@ -5010,11 +5060,11 @@
5010
5060
  return Promise.reject(error);
5011
5061
  }));
5012
5062
  }
5013
- function _sync(db, options, schema, { isInitialSync, cancelToken, justCheckIfNeeded, purpose } = {
5014
- isInitialSync: false,
5015
- }) {
5016
- var _a;
5017
- return __awaiter(this, void 0, void 0, function* () {
5063
+ function _sync(db_1, options_1, schema_1) {
5064
+ return __awaiter(this, arguments, void 0, function* (db, options, schema, { isInitialSync, cancelToken, justCheckIfNeeded, purpose } = {
5065
+ isInitialSync: false,
5066
+ }) {
5067
+ var _a;
5018
5068
  if (!justCheckIfNeeded) {
5019
5069
  console.debug('SYNC STARTED', { isInitialSync, purpose });
5020
5070
  }
@@ -5056,8 +5106,8 @@
5056
5106
  const [clientChangeSet, syncState, baseRevs, { yMessages, lastUpdateIds }] = yield db.transaction('r', db.tables, () => __awaiter(this, void 0, void 0, function* () {
5057
5107
  const syncState = yield db.getPersistedSyncState();
5058
5108
  const baseRevs = yield db.$baseRevs.toArray();
5059
- let clientChanges = yield listClientChanges(mutationTables);
5060
- const yResults = yield listYClientMessagesAndStateVector(db);
5109
+ let clientChanges = yield listClientChanges(mutationTables, db);
5110
+ const yResults = yield listYClientMessagesAndStateVector(db, tablesToSync);
5061
5111
  throwIfCancelled(cancelToken);
5062
5112
  if (doSyncify) {
5063
5113
  const alreadySyncedRealms = [
@@ -5187,11 +5237,14 @@
5187
5237
  //
5188
5238
  // apply yMessages
5189
5239
  //
5190
- const receivedUntils = yield applyYServerMessages(res.yMessages, db);
5240
+ const { receivedUntils, resyncNeeded } = yield applyYServerMessages(res.yMessages, db);
5191
5241
  //
5192
5242
  // update Y SyncStates
5193
5243
  //
5194
5244
  yield updateYSyncStates(lastUpdateIds, receivedUntils, db, res.serverRevision);
5245
+ if (resyncNeeded) {
5246
+ newSyncState.yDownloadedRealms = {}; // Will trigger a full download of Y-documents below...
5247
+ }
5195
5248
  }
5196
5249
  //
5197
5250
  // Update regular syncState
@@ -5325,8 +5378,8 @@
5325
5378
  event.next(null);
5326
5379
  }
5327
5380
  function consumeQueue() {
5328
- var _a, _b, _c, _d, _e, _f;
5329
5381
  return __awaiter(this, void 0, void 0, function* () {
5382
+ var _a, _b, _c, _d, _e, _f;
5330
5383
  while (queue.length > 0) {
5331
5384
  const msg = queue.shift();
5332
5385
  try {
@@ -5641,8 +5694,8 @@
5641
5694
  }
5642
5695
  });
5643
5696
  }
5644
- function _logout(db, { deleteUnsyncedData = false } = {}) {
5645
- return __awaiter(this, void 0, void 0, function* () {
5697
+ function _logout(db_1) {
5698
+ return __awaiter(this, arguments, void 0, function* (db, { deleteUnsyncedData = false } = {}) {
5646
5699
  // Clear the database without emptying configuration options.
5647
5700
  const [numUnsynced, loggedOut] = yield db.dx.transaction('rw', db.dx.tables, (tx) => __awaiter(this, void 0, void 0, function* () {
5648
5701
  // @ts-ignore
@@ -5680,11 +5733,11 @@
5680
5733
 
5681
5734
  function otpFetchTokenCallback(db) {
5682
5735
  const { userInteraction } = db.cloud;
5683
- return function otpAuthenticate({ public_key, hints }) {
5684
- var _a;
5685
- return __awaiter(this, void 0, void 0, function* () {
5736
+ return function otpAuthenticate(_a) {
5737
+ return __awaiter(this, arguments, void 0, function* ({ public_key, hints }) {
5738
+ var _b;
5686
5739
  let tokenRequest;
5687
- const url = (_a = db.cloud.options) === null || _a === void 0 ? void 0 : _a.databaseUrl;
5740
+ const url = (_b = db.cloud.options) === null || _b === void 0 ? void 0 : _b.databaseUrl;
5688
5741
  if (!url)
5689
5742
  throw new Error(`No database URL given.`);
5690
5743
  if ((hints === null || hints === void 0 ? void 0 : hints.grant_type) === 'demo') {
@@ -5850,8 +5903,8 @@
5850
5903
  }
5851
5904
 
5852
5905
  function login(db, hints) {
5853
- var _a;
5854
5906
  return __awaiter(this, void 0, void 0, function* () {
5907
+ var _a;
5855
5908
  const currentUser = yield db.getCurrentUser();
5856
5909
  const origUserId = currentUser.userId;
5857
5910
  if (currentUser.isLoggedIn && (!hints || (!hints.email && !hints.userId))) {
@@ -6288,8 +6341,7 @@
6288
6341
  outstandingTransactions.next(outstandingTransactions.value);
6289
6342
  };
6290
6343
  const txComplete = () => {
6291
- if (tx.mutationsAdded &&
6292
- !isEagerSyncDisabled(db)) {
6344
+ if (tx.mutationsAdded && !isEagerSyncDisabled(db)) {
6293
6345
  triggerSync(db, 'push');
6294
6346
  }
6295
6347
  removeTransaction();
@@ -6371,26 +6423,107 @@
6371
6423
  : mutateAndLog(req);
6372
6424
  } }));
6373
6425
  function mutateAndLog(req) {
6426
+ var _a, _b;
6374
6427
  const trans = req.trans;
6375
- trans.mutationsAdded = true;
6428
+ const unsyncedProps = (_b = (_a = db.cloud.options) === null || _a === void 0 ? void 0 : _a.unsyncedProperties) === null || _b === void 0 ? void 0 : _b[tableName];
6376
6429
  const { txid, currentUser: { userId }, } = trans;
6377
6430
  const { type } = req;
6378
6431
  const opNo = ++trans.opCount;
6432
+ function stripChangeSpec(changeSpec) {
6433
+ if (!unsyncedProps)
6434
+ return changeSpec;
6435
+ let rv = changeSpec;
6436
+ for (const keyPath of Object.keys(changeSpec)) {
6437
+ if (unsyncedProps.some((p) => keyPath === p || keyPath.startsWith(p + '.'))) {
6438
+ if (rv === changeSpec)
6439
+ rv = Object.assign({}, changeSpec); // clone on demand
6440
+ delete rv[keyPath];
6441
+ }
6442
+ }
6443
+ return rv;
6444
+ }
6379
6445
  return table.mutate(req).then((res) => {
6446
+ var _a;
6380
6447
  const { numFailures: hasFailures, failures } = res;
6381
6448
  let keys = type === 'delete' ? req.keys : res.results;
6382
6449
  let values = 'values' in req ? req.values : [];
6383
- let updates = 'updates' in req && req.updates;
6450
+ let changeSpec = 'changeSpec' in req ? req.changeSpec : undefined;
6451
+ let updates = 'updates' in req ? req.updates : undefined;
6384
6452
  if (hasFailures) {
6385
6453
  keys = keys.filter((_, idx) => !failures[idx]);
6386
6454
  values = values.filter((_, idx) => !failures[idx]);
6387
6455
  }
6456
+ if (unsyncedProps) {
6457
+ // Filter out unsynced properties
6458
+ values = values.map((value) => {
6459
+ const newValue = Object.assign({}, value);
6460
+ for (const prop of unsyncedProps) {
6461
+ delete newValue[prop];
6462
+ }
6463
+ return newValue;
6464
+ });
6465
+ if (changeSpec) {
6466
+ // modify operation with criteria and changeSpec.
6467
+ // We must strip out unsynced properties from changeSpec.
6468
+ // We deal with criteria later.
6469
+ changeSpec = stripChangeSpec(changeSpec);
6470
+ if (Object.keys(changeSpec).length === 0) {
6471
+ // Nothing to change on server
6472
+ return res;
6473
+ }
6474
+ }
6475
+ if (updates) {
6476
+ let strippedChangeSpecs = updates.changeSpecs.map(stripChangeSpec);
6477
+ let newUpdates = {
6478
+ keys: [],
6479
+ changeSpecs: [],
6480
+ };
6481
+ const validKeys = new Dexie.RangeSet();
6482
+ let anyChangeSpecBecameEmpty = false;
6483
+ for (let i = 0, l = strippedChangeSpecs.length; i < l; ++i) {
6484
+ if (Object.keys(strippedChangeSpecs[i]).length > 0) {
6485
+ newUpdates.keys.push(updates.keys[i]);
6486
+ newUpdates.changeSpecs.push(strippedChangeSpecs[i]);
6487
+ validKeys.addKey(updates.keys[i]);
6488
+ }
6489
+ else {
6490
+ anyChangeSpecBecameEmpty = true;
6491
+ }
6492
+ }
6493
+ updates = newUpdates;
6494
+ if (anyChangeSpecBecameEmpty) {
6495
+ // Some keys were stripped. We must also strip them from keys and values
6496
+ let newKeys = [];
6497
+ let newValues = [];
6498
+ for (let i = 0, l = keys.length; i < l; ++i) {
6499
+ if (validKeys.hasKey(keys[i])) {
6500
+ newKeys.push(keys[i]);
6501
+ newValues.push(values[i]);
6502
+ }
6503
+ }
6504
+ keys = newKeys;
6505
+ values = newValues;
6506
+ }
6507
+ }
6508
+ }
6388
6509
  const ts = Date.now();
6389
6510
  // Canonicalize req.criteria.index to null if it's on the primary key.
6390
- const criteria = 'criteria' in req && req.criteria
6511
+ let criteria = 'criteria' in req && req.criteria
6391
6512
  ? Object.assign(Object.assign({}, req.criteria), { index: req.criteria.index === schema.primaryKey.keyPath // Use null to inform server that criteria is on primary key
6392
6513
  ? null // This will disable the server from trying to log consistent operations where it shouldnt.
6393
6514
  : req.criteria.index }) : undefined;
6515
+ if (unsyncedProps && (criteria === null || criteria === void 0 ? void 0 : criteria.index)) {
6516
+ const keyPaths = (_a = schema.indexes.find((idx) => idx.name === criteria.index)) === null || _a === void 0 ? void 0 : _a.keyPath;
6517
+ const involvedProps = keyPaths
6518
+ ? typeof keyPaths === 'string'
6519
+ ? [keyPaths]
6520
+ : keyPaths
6521
+ : [];
6522
+ if (involvedProps.some((p) => unsyncedProps === null || unsyncedProps === void 0 ? void 0 : unsyncedProps.includes(p))) {
6523
+ // Don't log criteria on unsynced properties as the server could not test them.
6524
+ criteria = undefined;
6525
+ }
6526
+ }
6394
6527
  const mut = req.type === 'delete'
6395
6528
  ? {
6396
6529
  type: 'delete',
@@ -6411,7 +6544,7 @@
6411
6544
  userId,
6412
6545
  values,
6413
6546
  }
6414
- : criteria && req.changeSpec
6547
+ : criteria && changeSpec
6415
6548
  ? {
6416
6549
  // Common changeSpec for all keys
6417
6550
  type: 'modify',
@@ -6419,37 +6552,51 @@
6419
6552
  opNo,
6420
6553
  keys,
6421
6554
  criteria,
6422
- changeSpec: req.changeSpec,
6555
+ changeSpec,
6423
6556
  txid,
6424
6557
  userId,
6425
6558
  }
6426
- : updates
6559
+ : changeSpec
6427
6560
  ? {
6428
- // One changeSpec per key
6561
+ // In case criteria involved an unsynced property, we go for keys instead.
6429
6562
  type: 'update',
6430
6563
  ts,
6431
6564
  opNo,
6432
- keys: updates.keys,
6433
- changeSpecs: updates.changeSpecs,
6434
- txid,
6435
- userId,
6436
- }
6437
- : {
6438
- type: 'upsert',
6439
- ts,
6440
- opNo,
6441
6565
  keys,
6442
- values,
6566
+ changeSpecs: keys.map(() => changeSpec),
6443
6567
  txid,
6444
6568
  userId,
6445
- };
6569
+ }
6570
+ : updates
6571
+ ? {
6572
+ // One changeSpec per key
6573
+ type: 'update',
6574
+ ts,
6575
+ opNo,
6576
+ keys: updates.keys,
6577
+ changeSpecs: updates.changeSpecs,
6578
+ txid,
6579
+ userId,
6580
+ }
6581
+ : {
6582
+ type: 'upsert',
6583
+ ts,
6584
+ opNo,
6585
+ keys,
6586
+ values,
6587
+ txid,
6588
+ userId,
6589
+ };
6446
6590
  if ('isAdditionalChunk' in req && req.isAdditionalChunk) {
6447
6591
  mut.isAdditionalChunk = true;
6448
6592
  }
6449
6593
  return keys.length > 0 || criteria
6450
6594
  ? mutsTable
6451
6595
  .mutate({ type: 'add', trans, values: [mut] }) // Log entry
6452
- .then(() => res) // Return original response
6596
+ .then(() => {
6597
+ trans.mutationsAdded = true; // Mark transaction as having added mutations to trigger eager sync
6598
+ return res; // Return original response
6599
+ })
6453
6600
  : res;
6454
6601
  });
6455
6602
  }
@@ -6616,38 +6763,51 @@
6616
6763
 
6617
6764
  function createYClientUpdateObservable(db) {
6618
6765
  const yTableRecords = flatten(db.tables
6619
- .filter((table) => { var _a; return ((_a = db.cloud.schema) === null || _a === void 0 ? void 0 : _a[table.name].markedForSync) && table.schema.yProps; })
6766
+ .filter((table) => { var _a, _b; return ((_b = (_a = db.cloud.schema) === null || _a === void 0 ? void 0 : _a[table.name]) === null || _b === void 0 ? void 0 : _b.markedForSync) && table.schema.yProps; })
6620
6767
  .map((table) => table.schema.yProps.map((p) => ({
6621
6768
  table: table.name,
6622
6769
  ydocProp: p.prop,
6623
6770
  updatesTable: p.updatesTable,
6624
6771
  }))));
6625
6772
  return rxjs.merge(...yTableRecords.map(({ table, ydocProp, updatesTable }) => {
6626
- let currentUnsentFrom = 1;
6627
- return Dexie.liveQuery(() => __awaiter(this, void 0, void 0, function* () {
6628
- const yTbl = db.table(updatesTable);
6629
- const unsentFrom = yield yTbl
6630
- .where({ i: DEXIE_CLOUD_SYNCER_ID })
6631
- .first()
6632
- .then((syncer) => (syncer === null || syncer === void 0 ? void 0 : syncer.unsentFrom) || 1);
6633
- currentUnsentFrom = Math.max(currentUnsentFrom, unsentFrom);
6634
- const addedUpdates = yield listUpdatesSince(yTbl, currentUnsentFrom);
6635
- // Update currentUnsentFrom to only listen for updates that will be newer than the ones we emitted.
6636
- currentUnsentFrom = Math.max(currentUnsentFrom, ...addedUpdates.map((update) => update.i + 1));
6637
- return addedUpdates
6638
- .filter((update) => update.f && update.f & 1) // Only include local updates
6639
- .map((update) => {
6640
- return {
6641
- type: 'u-c',
6642
- table,
6643
- prop: ydocProp,
6644
- k: update.k,
6645
- u: update.u,
6646
- i: update.i,
6647
- };
6648
- });
6773
+ // Per updates table (table+prop combo), we first read syncer.unsentFrom,
6774
+ // and then start listening for updates since that number.
6775
+ const yTbl = db.table(updatesTable);
6776
+ return rxjs.from(yTbl.get(DEXIE_CLOUD_SYNCER_ID)).pipe(rxjs.switchMap((syncer) => {
6777
+ let currentUnsentFrom = (syncer === null || syncer === void 0 ? void 0 : syncer.unsentFrom) || 1;
6778
+ return rxjs.from(Dexie.liveQuery(() => __awaiter(this, void 0, void 0, function* () {
6779
+ const addedUpdates = yield listUpdatesSince(yTbl, currentUnsentFrom);
6780
+ return addedUpdates
6781
+ .filter((update) => update.f && update.f & 1) // Only include local updates
6782
+ .map((update) => {
6783
+ return {
6784
+ type: 'u-c',
6785
+ table,
6786
+ prop: ydocProp,
6787
+ k: update.k,
6788
+ u: update.u,
6789
+ i: update.i,
6790
+ };
6791
+ });
6792
+ }))).pipe(rxjs.tap((addedUpdates) => {
6793
+ // Update currentUnsentFrom to only listen for updates that will be newer than the ones we emitted.
6794
+ // (Before, we did this within the liveQuery, but that caused a bug because
6795
+ // a cancelled emittion of a liveQuery would update the currentUnsentFrom without
6796
+ // emitting anything, leading to that we jumped over some updates. Here we update it
6797
+ // after the liveQuery has emitted its updates)
6798
+ if (addedUpdates.length > 0) {
6799
+ currentUnsentFrom = addedUpdates.at(-1).i + 1;
6800
+ }
6801
+ }));
6649
6802
  }));
6650
- })).pipe(rxjs.mergeMap((messages) => messages)); // Flattens the array of messages. If messageProducer emits empty array, nothing is emitted but if messageProducer emits array of messages, they are emitted one by one.
6803
+ })).pipe(
6804
+ // Flatten the array of messages.
6805
+ // If messageProducer emits empty array, nothing is emitted
6806
+ // but if messageProducer emits array of messages, they are
6807
+ // emitted one by one.
6808
+ rxjs.mergeMap((messages) => messages), rxjs.tap((message) => {
6809
+ console.debug('dexie-cloud emitting y-c', message);
6810
+ }));
6651
6811
  }
6652
6812
 
6653
6813
  function getAwarenessLibrary(db) {
@@ -6664,25 +6824,24 @@
6664
6824
  const CLIENT_PING_INTERVAL = 30000;
6665
6825
  const FAIL_RETRY_WAIT_TIME = 60000;
6666
6826
  class WSObservable extends rxjs.Observable {
6667
- constructor(db, rev, realmSetHash, clientIdentity, messageProducer, webSocketStatus, token, tokenExpiration) {
6668
- super((subscriber) => new WSConnection(db, rev, realmSetHash, clientIdentity, token, tokenExpiration, subscriber, messageProducer, webSocketStatus));
6827
+ constructor(db, rev, realmSetHash, clientIdentity, messageProducer, webSocketStatus, user) {
6828
+ super((subscriber) => new WSConnection(db, rev, realmSetHash, clientIdentity, user, subscriber, messageProducer, webSocketStatus));
6669
6829
  }
6670
6830
  }
6671
6831
  let counter = 0;
6672
6832
  class WSConnection extends rxjs.Subscription {
6673
- constructor(db, rev, realmSetHash, clientIdentity, token, tokenExpiration, subscriber, messageProducer, webSocketStatus) {
6833
+ constructor(db, rev, realmSetHash, clientIdentity, user, subscriber, messageProducer, webSocketStatus) {
6674
6834
  super(() => this.teardown());
6675
6835
  this.id = ++counter;
6676
6836
  this.subscriptions = new Set();
6677
6837
  this.reconnecting = false;
6678
- console.debug('New WebSocket Connection', this.id, token ? 'authorized' : 'unauthorized');
6838
+ console.debug('New WebSocket Connection', this.id, user.accessToken ? 'authorized' : 'unauthorized');
6679
6839
  this.db = db;
6680
6840
  this.databaseUrl = db.cloud.options.databaseUrl;
6681
6841
  this.rev = rev;
6682
6842
  this.realmSetHash = realmSetHash;
6683
6843
  this.clientIdentity = clientIdentity;
6684
- this.token = token;
6685
- this.tokenExpiration = tokenExpiration;
6844
+ this.user = user;
6686
6845
  this.subscriber = subscriber;
6687
6846
  this.lastUserActivity = new Date();
6688
6847
  this.messageProducer = messageProducer;
@@ -6742,7 +6901,8 @@
6742
6901
  //console.debug('SyncStatus: DUBB: Ooops it was closed!');
6743
6902
  return;
6744
6903
  }
6745
- if (this.tokenExpiration && this.tokenExpiration < new Date()) {
6904
+ const tokenExpiration = this.user.accessTokenExpiration;
6905
+ if (tokenExpiration && tokenExpiration < new Date()) {
6746
6906
  this.subscriber.error(new TokenExpiredError()); // Will be handled in connectWebSocket.ts.
6747
6907
  return;
6748
6908
  }
@@ -6797,8 +6957,8 @@
6797
6957
  searchParams.set('rev', this.rev);
6798
6958
  searchParams.set('realmsHash', this.realmSetHash);
6799
6959
  searchParams.set('clientId', this.clientIdentity);
6800
- if (this.token) {
6801
- searchParams.set('token', this.token);
6960
+ if (this.user.accessToken) {
6961
+ searchParams.set('token', this.user.accessToken);
6802
6962
  }
6803
6963
  // Connect the WebSocket to given url:
6804
6964
  console.debug('dexie-cloud WebSocket create');
@@ -6813,12 +6973,12 @@
6813
6973
  ws.onmessage = (event) => {
6814
6974
  if (!this.pinger)
6815
6975
  return;
6816
- console.debug('dexie-cloud WebSocket onmessage', event.data);
6817
6976
  this.lastServerActivity = new Date();
6818
6977
  try {
6819
6978
  const msg = typeof event.data === 'string'
6820
6979
  ? TSON.parse(event.data)
6821
6980
  : decodeYMessage(new Uint8Array(event.data));
6981
+ console.debug('dexie-cloud WebSocket onmessage', msg.type, msg);
6822
6982
  if (msg.type === 'error') {
6823
6983
  throw new Error(`Error message from dexie-cloud: ${msg.error}`);
6824
6984
  }
@@ -6839,6 +6999,10 @@
6839
6999
  else if (msg.type === 'u-ack' || msg.type === 'u-reject' || msg.type === 'u-s' || msg.type === 'in-sync') {
6840
7000
  applyYServerMessages([msg], this.db);
6841
7001
  }
7002
+ else if (msg.type === 'outdated-server-rev') {
7003
+ // Won't happen but need this for typing.
7004
+ throw new Error('Outdated server revision not expected over WebSocket - only in sync using fetch()');
7005
+ }
6842
7006
  else if (msg.type !== 'pong') {
6843
7007
  this.subscriber.next(msg);
6844
7008
  }
@@ -6874,17 +7038,27 @@
6874
7038
  this.webSocketStatus.value !== 'connected') {
6875
7039
  this.webSocketStatus.next('connected');
6876
7040
  }
7041
+ console.debug('dexie-cloud WebSocket send', msg.type, msg);
6877
7042
  if (msg.type === 'ready') {
6878
7043
  (_a = this.ws) === null || _a === void 0 ? void 0 : _a.send(TSON.stringify(msg));
6879
7044
  }
6880
7045
  else {
6881
7046
  // If it's not a "ready" message, it's an YMessage.
6882
7047
  // YMessages can be sent binary encoded.
7048
+ if (msg.type === 'u-c') {
7049
+ console.log("u-c:B", ++gotClientUpdateB);
7050
+ }
6883
7051
  (_b = this.ws) === null || _b === void 0 ? void 0 : _b.send(encodeYMessage(msg));
6884
7052
  }
6885
7053
  }
6886
7054
  }));
6887
- this.subscriptions.add(createYClientUpdateObservable(this.db).subscribe(this.db.messageProducer));
7055
+ if (this.user.isLoggedIn && !isEagerSyncDisabled(this.db)) {
7056
+ this.subscriptions.add(createYClientUpdateObservable(this.db).pipe(rxjs.tap((msg) => {
7057
+ if (msg.type === 'u-c') {
7058
+ console.log("u-c:A", ++gotClientUpdateA, msg.i);
7059
+ }
7060
+ })).subscribe(this.db.messageProducer));
7061
+ }
6888
7062
  }
6889
7063
  catch (error) {
6890
7064
  this.pauseUntil = new Date(Date.now() + FAIL_RETRY_WAIT_TIME);
@@ -6892,6 +7066,8 @@
6892
7066
  });
6893
7067
  }
6894
7068
  }
7069
+ let gotClientUpdateA = 0;
7070
+ let gotClientUpdateB = 0;
6895
7071
 
6896
7072
  class InvalidLicenseError extends Error {
6897
7073
  constructor(license) {
@@ -6954,7 +7130,7 @@
6954
7130
  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]));
6955
7131
  }
6956
7132
  return new rxjs.BehaviorSubject([userLogin, syncState]);
6957
- }), 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]) => {
7133
+ }), 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]) => {
6958
7134
  var _a;
6959
7135
  if (!((_a = db.cloud.persistedSyncState) === null || _a === void 0 ? void 0 : _a.value)) {
6960
7136
  // Restart the flow if persistedSyncState is not yet available.
@@ -6964,7 +7140,7 @@
6964
7140
  // If no new entries, server won't bother the client. If new entries, server sends only those
6965
7141
  // and the baseRev of the last from same client-ID.
6966
7142
  if (userLogin) {
6967
- return new WSObservable(db, db.cloud.persistedSyncState.value.serverRevision, realmSetHash, db.cloud.persistedSyncState.value.clientIdentity, messageProducer, db.cloud.webSocketStatus, userLogin.accessToken, userLogin.accessTokenExpiration);
7143
+ return new WSObservable(db, db.cloud.persistedSyncState.value.serverRevision, realmSetHash, db.cloud.persistedSyncState.value.clientIdentity, messageProducer, db.cloud.webSocketStatus, userLogin);
6968
7144
  }
6969
7145
  else {
6970
7146
  return rxjs.from([]);
@@ -7015,8 +7191,8 @@
7015
7191
  }
7016
7192
 
7017
7193
  function isSyncNeeded(db) {
7018
- var _a;
7019
7194
  return __awaiter(this, void 0, void 0, function* () {
7195
+ var _a;
7020
7196
  return ((_a = db.cloud.options) === null || _a === void 0 ? void 0 : _a.databaseUrl) && db.cloud.schema
7021
7197
  ? yield sync(db, db.cloud.options, db.cloud.schema, { justCheckIfNeeded: true })
7022
7198
  : false;
@@ -7700,6 +7876,7 @@
7700
7876
  // If user can update any prop in any table in this realm, return true unless
7701
7877
  // it regards to ownership change:
7702
7878
  if (this.permissions.update === '*') {
7879
+ // @ts-ignore
7703
7880
  return props.every((prop) => prop !== 'owner');
7704
7881
  }
7705
7882
  const tablePermissions = (_b = this.permissions.update) === null || _b === void 0 ? void 0 : _b[this.tableName];
@@ -7771,125 +7948,139 @@
7771
7948
  });
7772
7949
 
7773
7950
  function createYHandler(db) {
7774
- const awap = getAwarenessLibrary(db);
7775
7951
  return (provider) => {
7776
7952
  var _a;
7777
7953
  const doc = provider.doc;
7778
- const { parentTable, parentId, parentProp, updatesTable } = doc.meta;
7954
+ const { parentTable } = doc.meta || {};
7779
7955
  if (!((_a = db.cloud.schema) === null || _a === void 0 ? void 0 : _a[parentTable].markedForSync)) {
7780
7956
  return; // The table that holds the doc is not marked for sync - leave it to dexie. No syncing, no awareness.
7781
7957
  }
7782
- let awareness = new awap.Awareness(doc);
7783
- awarenessWeakMap.set(doc, awareness);
7784
- provider.awareness = awareness;
7785
- awareness.on('update', ({ added, updated, removed }, origin) => {
7786
- // Send the update
7787
- const changedClients = added.concat(updated).concat(removed);
7788
- if (origin !== 'server') {
7789
- const update = awap.encodeAwarenessUpdate(awareness, changedClients);
7958
+ let awareness;
7959
+ Object.defineProperty(provider, "awareness", {
7960
+ get() {
7961
+ if (awareness)
7962
+ return awareness;
7963
+ awareness = createAwareness(db, doc, provider);
7964
+ awarenessWeakMap.set(doc, awareness);
7965
+ return awareness;
7966
+ }
7967
+ });
7968
+ };
7969
+ }
7970
+ function createAwareness(db, doc, provider) {
7971
+ const { parentTable, parentId, parentProp, updatesTable } = doc.meta;
7972
+ const awap = getAwarenessLibrary(db);
7973
+ const awareness = new awap.Awareness(doc);
7974
+ awareness.on('update', ({ added, updated, removed }, origin) => {
7975
+ // Send the update
7976
+ const changedClients = added.concat(updated).concat(removed);
7977
+ const user = db.cloud.currentUser.value;
7978
+ if (origin !== 'server' && user.isLoggedIn && !isEagerSyncDisabled(db)) {
7979
+ const update = awap.encodeAwarenessUpdate(awareness, changedClients);
7980
+ db.messageProducer.next({
7981
+ type: 'aware',
7982
+ table: parentTable,
7983
+ prop: parentProp,
7984
+ k: doc.meta.parentId,
7985
+ u: update,
7986
+ });
7987
+ if (provider.destroyed) {
7988
+ // We're called from awareness.on('destroy') that did
7989
+ // removeAwarenessStates.
7990
+ // It's time to also send the doc-close message that dexie-cloud understands
7991
+ // and uses to stop subscribing for updates and awareness updates and brings
7992
+ // down the cached information in memory on the WS connection for this.
7790
7993
  db.messageProducer.next({
7791
- type: 'aware',
7994
+ type: 'doc-close',
7792
7995
  table: parentTable,
7793
7996
  prop: parentProp,
7794
- k: doc.meta.parentId,
7795
- u: update,
7997
+ k: doc.meta.parentId
7796
7998
  });
7797
- if (provider.destroyed) {
7798
- // We're called from awareness.on('destroy') that did
7799
- // removeAwarenessStates.
7800
- // It's time to also send the doc-close message that dexie-cloud understands
7801
- // and uses to stop subscribing for updates and awareness updates and brings
7802
- // down the cached information in memory on the WS connection for this.
7803
- db.messageProducer.next({
7804
- type: 'doc-close',
7805
- table: parentTable,
7806
- prop: parentProp,
7807
- k: doc.meta.parentId
7808
- });
7809
- }
7810
7999
  }
7811
- });
7812
- awareness.on('destroy', () => {
7813
- // Signal to server that this provider is destroyed (the update event will be triggered, which
7814
- // in turn will trigger db.messageProducer that will send the message to the server if WS is connected)
7815
- awap.removeAwarenessStates(awareness, [doc.clientID], 'provider destroyed');
7816
- });
7817
- // Now wait til document is loaded and then open the document on the server
7818
- provider.on('load', () => __awaiter(this, void 0, void 0, function* () {
8000
+ }
8001
+ });
8002
+ awareness.on('destroy', () => {
8003
+ // Signal to server that this provider is destroyed (the update event will be triggered, which
8004
+ // in turn will trigger db.messageProducer that will send the message to the server if WS is connected)
8005
+ awap.removeAwarenessStates(awareness, [doc.clientID], 'provider destroyed');
8006
+ });
8007
+ // Open the document on the server
8008
+ (() => __awaiter(this, void 0, void 0, function* () {
8009
+ if (provider.destroyed)
8010
+ return;
8011
+ let connected = false;
8012
+ let currentFlowId = 1;
8013
+ const subscription = db.cloud.webSocketStatus.subscribe((wsStatus) => {
7819
8014
  if (provider.destroyed)
7820
8015
  return;
7821
- let connected = false;
7822
- let currentFlowId = 1;
7823
- const subscription = db.cloud.webSocketStatus.subscribe((wsStatus) => {
7824
- if (provider.destroyed)
8016
+ // Keep "connected" state in a variable so we can check it after async operations
8017
+ connected = wsStatus === 'connected';
8018
+ // We are or got connected. Open the document on the server.
8019
+ const user = db.cloud.currentUser.value;
8020
+ if (wsStatus === "connected" && user.isLoggedIn && !isEagerSyncDisabled(db)) {
8021
+ ++currentFlowId;
8022
+ openDocumentOnServer().catch(error => {
8023
+ console.warn(`Error catched in createYHandler.ts: ${error}`);
8024
+ });
8025
+ }
8026
+ });
8027
+ // Wait until WebSocket is connected
8028
+ provider.addCleanupHandler(subscription);
8029
+ /** Sends an 'doc-open' message to server whenever websocket becomes
8030
+ * connected, or if it is already connected.
8031
+ * The flow is aborted in case websocket is disconnected while querying
8032
+ * information required to compute the state vector. Flow is also
8033
+ * aborted in case document or provider has been destroyed during
8034
+ * the async parts of the task.
8035
+ *
8036
+ * The state vector is only computed from the updates that have occured
8037
+ * after the last full sync - which could very often be zero - in which
8038
+ * case no state vector is sent (then the server already knows us by
8039
+ * revision)
8040
+ *
8041
+ * When server gets the doc-open message, it will authorized us for
8042
+ * whether we are allowed to read / write to this document, and then
8043
+ * keep the cached information in memory on the WS connection for this
8044
+ * particular document, as well as subscribe to updates and awareness updates
8045
+ * from other clients on the document.
8046
+ */
8047
+ function openDocumentOnServer(wsStatus) {
8048
+ return __awaiter(this, void 0, void 0, function* () {
8049
+ const myFlow = currentFlowId; // So we can abort when a new flow is started
8050
+ const yTbl = db.table(updatesTable);
8051
+ const syncState = yield yTbl.get(DEXIE_CLOUD_SYNCER_ID);
8052
+ // After every await, check if we still should be working on this task.
8053
+ if (provider.destroyed || currentFlowId !== myFlow || !connected)
7825
8054
  return;
7826
- // Keep "connected" state in a variable so we can check it after async operations
7827
- connected = wsStatus === 'connected';
7828
- // We are or got connected. Open the document on the server.
7829
- if (wsStatus === "connected") {
7830
- ++currentFlowId;
7831
- openDocumentOnServer().catch(error => {
7832
- console.warn(`Error catched in createYHandler.ts: ${error}`);
7833
- });
8055
+ const receivedUntil = (syncState === null || syncState === void 0 ? void 0 : syncState.receivedUntil) || 0;
8056
+ const docOpenMsg = {
8057
+ type: 'doc-open',
8058
+ table: parentTable,
8059
+ prop: parentProp,
8060
+ k: parentId,
8061
+ serverRev: syncState === null || syncState === void 0 ? void 0 : syncState.serverRev,
8062
+ };
8063
+ const serverUpdatesSinceLastSync = yield yTbl
8064
+ .where('i')
8065
+ .between(receivedUntil, Infinity, false)
8066
+ .filter((update) => Dexie.cmp(update.k, parentId) === 0 && // Only updates for this document
8067
+ ((update.f || 0) & 1) === 0 // Don't include local changes
8068
+ )
8069
+ .toArray();
8070
+ // After every await, check if we still should be working on this task.
8071
+ if (provider.destroyed || currentFlowId !== myFlow || !connected)
8072
+ return;
8073
+ if (serverUpdatesSinceLastSync.length > 0) {
8074
+ const Y = $Y(db); // Get the Yjs library from Dexie constructor options
8075
+ const mergedUpdate = Y.mergeUpdatesV2(serverUpdatesSinceLastSync.map((update) => update.u));
8076
+ const stateVector = Y.encodeStateVectorFromUpdateV2(mergedUpdate);
8077
+ docOpenMsg.sv = stateVector;
7834
8078
  }
8079
+ db.messageProducer.next(docOpenMsg);
7835
8080
  });
7836
- // Wait until WebSocket is connected
7837
- provider.addCleanupHandler(subscription);
7838
- /** Sends an 'doc-open' message to server whenever websocket becomes
7839
- * connected, or if it is already connected.
7840
- * The flow is aborted in case websocket is disconnected while querying
7841
- * information required to compute the state vector. Flow is also
7842
- * aborted in case document or provider has been destroyed during
7843
- * the async parts of the task.
7844
- *
7845
- * The state vector is only computed from the updates that have occured
7846
- * after the last full sync - which could very often be zero - in which
7847
- * case no state vector is sent (then the server already knows us by
7848
- * revision)
7849
- *
7850
- * When server gets the doc-open message, it will authorized us for
7851
- * whether we are allowed to read / write to this document, and then
7852
- * keep the cached information in memory on the WS connection for this
7853
- * particular document, as well as subscribe to updates and awareness updates
7854
- * from other clients on the document.
7855
- */
7856
- function openDocumentOnServer(wsStatus) {
7857
- return __awaiter(this, void 0, void 0, function* () {
7858
- const myFlow = currentFlowId; // So we can abort when a new flow is started
7859
- const yTbl = db.table(updatesTable);
7860
- const syncState = yield yTbl.get(DEXIE_CLOUD_SYNCER_ID);
7861
- // After every await, check if we still should be working on this task.
7862
- if (provider.destroyed || currentFlowId !== myFlow || !connected)
7863
- return;
7864
- const receivedUntil = (syncState === null || syncState === void 0 ? void 0 : syncState.receivedUntil) || 0;
7865
- const docOpenMsg = {
7866
- type: 'doc-open',
7867
- table: parentTable,
7868
- prop: parentProp,
7869
- k: parentId,
7870
- serverRev: syncState === null || syncState === void 0 ? void 0 : syncState.serverRev,
7871
- };
7872
- const serverUpdatesSinceLastSync = yield yTbl
7873
- .where('i')
7874
- .between(receivedUntil, Infinity, false)
7875
- .filter((update) => Dexie.cmp(update.k, parentId) === 0 && // Only updates for this document
7876
- ((update.f || 0) & 1) === 0 // Don't include local changes
7877
- )
7878
- .toArray();
7879
- // After every await, check if we still should be working on this task.
7880
- if (provider.destroyed || currentFlowId !== myFlow || !connected)
7881
- return;
7882
- if (serverUpdatesSinceLastSync.length > 0) {
7883
- const Y = $Y(db); // Get the Yjs library from Dexie constructor options
7884
- const mergedUpdate = Y.mergeUpdatesV2(serverUpdatesSinceLastSync.map((update) => update.u));
7885
- const stateVector = Y.encodeStateVectorFromUpdateV2(mergedUpdate);
7886
- docOpenMsg.sv = stateVector;
7887
- }
7888
- db.messageProducer.next(docOpenMsg);
7889
- });
7890
- }
7891
- }));
7892
- };
8081
+ }
8082
+ }))();
8083
+ return awareness;
7893
8084
  }
7894
8085
 
7895
8086
  const DEFAULT_OPTIONS = {
@@ -7932,7 +8123,7 @@
7932
8123
  const syncComplete = new rxjs.Subject();
7933
8124
  dexie.cloud = {
7934
8125
  // @ts-ignore
7935
- version: "4.1.0-alpha.2",
8126
+ version: "4.1.0-alpha.21",
7936
8127
  options: Object.assign({}, DEFAULT_OPTIONS),
7937
8128
  schema: null,
7938
8129
  get currentUserId() {
@@ -7968,16 +8159,16 @@
7968
8159
  }
7969
8160
  updateSchemaFromOptions(dexie.cloud.schema, dexie.cloud.options);
7970
8161
  },
7971
- logout({ force } = {}) {
7972
- return __awaiter(this, void 0, void 0, function* () {
8162
+ logout() {
8163
+ return __awaiter(this, arguments, void 0, function* ({ force } = {}) {
7973
8164
  force
7974
8165
  ? yield _logout(DexieCloudDB(dexie), { deleteUnsyncedData: true })
7975
8166
  : yield logout(DexieCloudDB(dexie));
7976
8167
  });
7977
8168
  },
7978
- sync({ wait, purpose } = { wait: true, purpose: 'push' }) {
7979
- var _a;
7980
- return __awaiter(this, void 0, void 0, function* () {
8169
+ sync() {
8170
+ return __awaiter(this, arguments, void 0, function* ({ wait, purpose } = { wait: true, purpose: 'push' }) {
8171
+ var _a;
7981
8172
  if (wait === undefined)
7982
8173
  wait = true;
7983
8174
  const db = DexieCloudDB(dexie);
@@ -8035,8 +8226,8 @@
8035
8226
  dexie.use(createImplicitPropSetterMiddleware(DexieCloudDB(dexie)));
8036
8227
  dexie.use(createIdGenerationMiddleware(DexieCloudDB(dexie)));
8037
8228
  function onDbReady(dexie) {
8038
- var _a, _b, _c, _d, _e, _f, _g;
8039
8229
  return __awaiter(this, void 0, void 0, function* () {
8230
+ var _a, _b, _c, _d, _e, _f, _g;
8040
8231
  closed = false; // As Dexie calls us, we are not closed anymore. Maybe reopened? Remember db.ready event is registered with sticky flag!
8041
8232
  const db = DexieCloudDB(dexie);
8042
8233
  // Setup default GUI:
@@ -8059,7 +8250,7 @@
8059
8250
  ? yield navigator.serviceWorker.getRegistrations()
8060
8251
  : [];
8061
8252
  const [initiallySynced, lastSyncedRealms] = yield db.transaction('rw', db.$syncState, () => __awaiter(this, void 0, void 0, function* () {
8062
- var _h, _j;
8253
+ var _a, _b;
8063
8254
  const { options, schema } = db.cloud;
8064
8255
  const [persistedOptions, persistedSchema, persistedSyncState] = yield Promise.all([
8065
8256
  db.getOptions(),
@@ -8081,7 +8272,7 @@
8081
8272
  delete newPersistedOptions.awarenessProtocol;
8082
8273
  yield db.$syncState.put(newPersistedOptions, 'options');
8083
8274
  }
8084
- if (((_h = db.cloud.options) === null || _h === void 0 ? void 0 : _h.tryUseServiceWorker) &&
8275
+ if (((_a = db.cloud.options) === null || _a === void 0 ? void 0 : _a.tryUseServiceWorker) &&
8085
8276
  'serviceWorker' in navigator &&
8086
8277
  swRegistrations.length > 0 &&
8087
8278
  !DISABLE_SERVICEWORKER_STRATEGY) {
@@ -8095,7 +8286,7 @@
8095
8286
  // Not configured for using service worker or no service worker
8096
8287
  // registration exists. Don't rely on service worker to do any job.
8097
8288
  // Use LocalSyncWorker instead.
8098
- if (((_j = db.cloud.options) === null || _j === void 0 ? void 0 : _j.tryUseServiceWorker) &&
8289
+ if (((_b = db.cloud.options) === null || _b === void 0 ? void 0 : _b.tryUseServiceWorker) &&
8099
8290
  !db.cloud.isServiceWorkerDB) {
8100
8291
  console.debug('dexie-cloud-addon: Not using service worker.', swRegistrations.length === 0
8101
8292
  ? 'No SW registrations found.'
@@ -8234,7 +8425,7 @@
8234
8425
  }
8235
8426
  }
8236
8427
  // @ts-ignore
8237
- dexieCloud.version = "4.1.0-alpha.2";
8428
+ dexieCloud.version = "4.1.0-alpha.21";
8238
8429
  Dexie.Cloud = dexieCloud;
8239
8430
 
8240
8431
  // In case the SW lives for a while, let it reuse already opened connections:
@@ -8264,8 +8455,8 @@
8264
8455
  }
8265
8456
  return promise;
8266
8457
  function _syncDB(dbName, purpose) {
8267
- var _a;
8268
8458
  return __awaiter(this, void 0, void 0, function* () {
8459
+ var _a;
8269
8460
  let db = managedDBs.get(dbName);
8270
8461
  if (!db) {
8271
8462
  console.debug('Dexie Cloud SW: Creating new Dexie instance for', dbName);