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

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 (35) 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 +548 -231
  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 +390 -229
  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/listYClientMessagesAndStateVector.d.ts +3 -1
  16. package/dist/umd/DexieCloudOptions.d.ts +3 -0
  17. package/dist/umd/TSON.d.ts +1 -1
  18. package/dist/umd/WSObservable.d.ts +4 -4
  19. package/dist/umd/define-ydoc-trigger.d.ts +2 -0
  20. package/dist/umd/dexie-cloud-addon.d.ts +1 -0
  21. package/dist/umd/dexie-cloud-addon.js +546 -228
  22. package/dist/umd/dexie-cloud-addon.js.map +1 -1
  23. package/dist/umd/dexie-cloud-addon.min.js +1 -1
  24. package/dist/umd/dexie-cloud-addon.min.js.map +1 -1
  25. package/dist/umd/getInvitesObservable.d.ts +11 -11
  26. package/dist/umd/service-worker.js +388 -227
  27. package/dist/umd/service-worker.js.map +1 -1
  28. package/dist/umd/service-worker.min.js +1 -1
  29. package/dist/umd/service-worker.min.js.map +1 -1
  30. package/dist/umd/yjs/listYClientMessagesAndStateVector.d.ts +3 -1
  31. package/package.json +3 -3
  32. package/dist/modern/yjs/listYClientMessages.d.ts +0 -3
  33. package/dist/umd/yjs/listYClientMessages.d.ts +0 -3
  34. /package/dist/modern/yjs/{y.d.ts → Y.d.ts} +0 -0
  35. /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.20, Wed Oct 23 2024
12
12
  *
13
13
  * https://dexie.org
14
14
  *
@@ -16,8 +16,8 @@
16
16
  *
17
17
  */
18
18
 
19
- import Dexie, { PropModification, cmp, DexieYProvider, liveQuery } from 'dexie';
20
- import { Observable as Observable$1, BehaviorSubject, firstValueFrom, Subject, from as from$1, filter as filter$1, fromEvent, of, merge, mergeMap as mergeMap$1, Subscription as Subscription$1, throwError, combineLatest, map as map$1, share, timer as timer$1 } from 'rxjs';
19
+ import Dexie, { PropModification, cmp, DexieYProvider, RangeSet, liveQuery } from 'dexie';
20
+ import { Observable as Observable$1, BehaviorSubject, firstValueFrom, Subject, from as from$1, filter as filter$1, fromEvent, of, merge, switchMap as switchMap$1, tap as tap$1, mergeMap as mergeMap$1, Subscription as Subscription$1, throwError, combineLatest, map as map$1, share, timer as timer$1 } 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];
@@ -3516,8 +3516,8 @@ function confirmLogout(userInteraction, currentUserId, numUnsyncedChanges) {
3516
3516
  }
3517
3517
 
3518
3518
  function loadAccessToken(db) {
3519
- var _a, _b, _c;
3520
3519
  return __awaiter(this, void 0, void 0, function* () {
3520
+ var _a, _b, _c;
3521
3521
  const currentUser = yield db.getCurrentUser();
3522
3522
  const { accessToken, accessTokenExpiration, refreshToken, refreshTokenExpiration, claims, } = currentUser;
3523
3523
  if (!accessToken)
@@ -4347,8 +4347,8 @@ function cloneChange(change, rewriteValues) {
4347
4347
  // seconds (given that there is a Ratelimit-Reset header).
4348
4348
  let syncRatelimitDelays = new WeakMap();
4349
4349
  function checkSyncRateLimitDelay(db) {
4350
- var _a, _b;
4351
4350
  return __awaiter(this, void 0, void 0, function* () {
4351
+ var _a, _b;
4352
4352
  const delatMilliseconds = ((_b = (_a = syncRatelimitDelays.get(db)) === null || _a === void 0 ? void 0 : _a.getTime()) !== null && _b !== void 0 ? _b : 0) - Date.now();
4353
4353
  if (delatMilliseconds > 0) {
4354
4354
  console.debug(`Stalling sync request ${delatMilliseconds} ms to spare ratelimits`);
@@ -4661,13 +4661,12 @@ function $Y(db) {
4661
4661
  * @param db
4662
4662
  * @returns
4663
4663
  */
4664
- function listYClientMessagesAndStateVector(db) {
4665
- var _a;
4664
+ function listYClientMessagesAndStateVector(db, tablesToSync) {
4666
4665
  return __awaiter(this, void 0, void 0, function* () {
4667
4666
  const result = [];
4668
4667
  const lastUpdateIds = {};
4669
- for (const table of db.tables) {
4670
- if (table.schema.yProps && ((_a = db.cloud.schema) === null || _a === void 0 ? void 0 : _a[table.name].markedForSync)) {
4668
+ for (const table of tablesToSync) {
4669
+ if (table.schema.yProps) {
4671
4670
  for (const yProp of table.schema.yProps) {
4672
4671
  const Y = $Y(db); // This is how we retrieve the user-provided Y library
4673
4672
  const yTable = db.table(yProp.updatesTable); // the updates-table for this combo of table+propName
@@ -4754,6 +4753,7 @@ function getUpdatesTable(db, table, ydocProp) {
4754
4753
 
4755
4754
  function applyYServerMessages(yMessages, db) {
4756
4755
  return __awaiter(this, void 0, void 0, function* () {
4756
+ var _a;
4757
4757
  const result = {};
4758
4758
  for (const m of yMessages) {
4759
4759
  switch (m.type) {
@@ -4784,7 +4784,24 @@ function applyYServerMessages(yMessages, db) {
4784
4784
  // See my question in https://discuss.yjs.dev/t/generate-an-inverse-update/2765
4785
4785
  console.debug(`Y update rejected. Deleting it.`);
4786
4786
  const utbl = getUpdatesTable(db, m.table, m.prop);
4787
- yield utbl.delete(m.i);
4787
+ // Delete the rejected update and all local updates since (avoid holes in the CRDT)
4788
+ // and destroy it's open document if there is one.
4789
+ const primaryKey = (_a = (yield utbl.get(m.i))) === null || _a === void 0 ? void 0 : _a.k;
4790
+ if (primaryKey != null) {
4791
+ yield db.transaction('rw', utbl, tx => {
4792
+ // @ts-ignore
4793
+ tx.idbtrans._rejecting_y_ypdate = true; // Inform ydoc triggers that we delete because of a rejection and not GC
4794
+ return utbl
4795
+ .where('i')
4796
+ .aboveOrEqual(m.i)
4797
+ .filter(u => cmp(u.k, primaryKey) === 0 && ((u.f || 0) & 1) === 1)
4798
+ .delete();
4799
+ });
4800
+ // Destroy active doc
4801
+ const activeDoc = DexieYProvider.getDocCache(db.dx).find(m.table, primaryKey, m.prop);
4802
+ if (activeDoc)
4803
+ activeDoc.destroy(); // Destroy the document so that editors don't continue to work on it
4804
+ }
4788
4805
  break;
4789
4806
  }
4790
4807
  case 'in-sync': {
@@ -4801,15 +4818,15 @@ function applyYServerMessages(yMessages, db) {
4801
4818
  }
4802
4819
 
4803
4820
  function updateYSyncStates(lastUpdateIdsBeforeSync, receivedUntilsAfterSync, db, serverRevision) {
4804
- var _a, _b;
4805
4821
  return __awaiter(this, void 0, void 0, function* () {
4822
+ var _a, _b, _c, _d, _e;
4806
4823
  // We want to update unsentFrom for each yTable to the value specified in first argument
4807
4824
  // because we got those values before we synced with server and here we are back from server
4808
4825
  // that has successfully received all those messages - no matter if the last update was a client or server update,
4809
4826
  // we can safely store unsentFrom to a value of the last update + 1 here.
4810
4827
  // We also want to update receivedUntil for each yTable to the value specified in the second argument,
4811
4828
  // because that contains the highest resulted id of each update from server after storing it.
4812
- // We could do these two tasks separately, but that would require two update calls on the same YSyncState, so
4829
+ // We could do these two tasks separately, but that would require two update calls on the same YSyncState, so
4813
4830
  // to optimize the dexie calls, we merge these two maps into a single one so we can do a single update request
4814
4831
  // per yTable.
4815
4832
  const mergedSpec = {};
@@ -4821,28 +4838,42 @@ function updateYSyncStates(lastUpdateIdsBeforeSync, receivedUntilsAfterSync, db,
4821
4838
  (_b = mergedSpec[yTable]) !== null && _b !== void 0 ? _b : (mergedSpec[yTable] = {});
4822
4839
  mergedSpec[yTable].receivedUntil = lastUpdateId;
4823
4840
  }
4824
- // Now go through the merged map and update YSyncStates accordingly:
4825
- for (const [yTable, { unsentFrom, receivedUntil }] of Object.entries(mergedSpec)) {
4841
+ // Now go through all yTables and update their YSyncStates:
4842
+ const allYTables = Object.values(db.dx._dbSchema)
4843
+ .filter((tblSchema) => tblSchema.yProps)
4844
+ .map((tblSchema) => tblSchema.yProps.map((yProp) => yProp.updatesTable))
4845
+ .flat();
4846
+ for (const yTable of allYTables) {
4847
+ const mergedEntry = mergedSpec[yTable];
4848
+ const unsentFrom = (_c = mergedEntry === null || mergedEntry === void 0 ? void 0 : mergedEntry.unsentFrom) !== null && _c !== void 0 ? _c : 1;
4849
+ const receivedUntil = (_e = (_d = mergedEntry === null || mergedEntry === void 0 ? void 0 : mergedEntry.receivedUntil) !== null && _d !== void 0 ? _d :
4850
+ // from local because we are in the same parent transaction (in sync.ts) that
4851
+ // applied all updates from the server
4852
+ (yield db
4853
+ .table(yTable)
4854
+ .where('i')
4855
+ .between(1, Infinity) // Because i might be string DEXIE_CLOUD_SYNCER_ID if not a number.
4856
+ .reverse()
4857
+ .limit(1)
4858
+ .primaryKeys())[0]) !== null && _e !== void 0 ? _e : 0;
4826
4859
  // We're already in a transaction, but for the sake of
4827
4860
  // code readability and correctness, let's launch an atomic sub transaction:
4828
4861
  yield db.transaction('rw', yTable, () => __awaiter(this, void 0, void 0, function* () {
4829
- const state = yield db.table(yTable).get(DEXIE_CLOUD_SYNCER_ID);
4862
+ const state = yield db
4863
+ .table(yTable)
4864
+ .get(DEXIE_CLOUD_SYNCER_ID);
4830
4865
  if (!state) {
4831
4866
  yield db.table(yTable).add({
4832
4867
  i: DEXIE_CLOUD_SYNCER_ID,
4833
- unsentFrom: unsentFrom || 1,
4834
- receivedUntil: receivedUntil || 0,
4868
+ unsentFrom,
4869
+ receivedUntil,
4835
4870
  serverRev: serverRevision,
4836
4871
  });
4837
4872
  }
4838
4873
  else {
4839
- if (unsentFrom) {
4840
- state.unsentFrom = Math.max(unsentFrom, state.unsentFrom || 1);
4841
- }
4842
- if (receivedUntil) {
4843
- state.receivedUntil = Math.max(receivedUntil, state.receivedUntil || 0);
4844
- state.serverRev = serverRevision;
4845
- }
4874
+ state.unsentFrom = Math.max(unsentFrom, state.unsentFrom || 1);
4875
+ state.receivedUntil = Math.max(receivedUntil, state.receivedUntil || 0);
4876
+ state.serverRev = serverRevision;
4846
4877
  yield db.table(yTable).put(state);
4847
4878
  }
4848
4879
  }));
@@ -4853,9 +4884,11 @@ function updateYSyncStates(lastUpdateIdsBeforeSync, receivedUntilsAfterSync, db,
4853
4884
  const BINSTREAM_TYPE_REALMID = 1;
4854
4885
  const BINSTREAM_TYPE_TABLE_AND_PROP = 2;
4855
4886
  const BINSTREAM_TYPE_DOCUMENT = 3;
4856
- function downloadYDocsFromServer(db, databaseUrl, { yDownloadedRealms, realms }) {
4857
- return __awaiter(this, void 0, void 0, function* () {
4858
- if (yDownloadedRealms && realms && realms.every(realmId => yDownloadedRealms[realmId] === '*')) {
4887
+ function downloadYDocsFromServer(db_1, databaseUrl_1, _a) {
4888
+ return __awaiter(this, arguments, void 0, function* (db, databaseUrl, { yDownloadedRealms, realms }) {
4889
+ if (yDownloadedRealms &&
4890
+ realms &&
4891
+ realms.every((realmId) => yDownloadedRealms[realmId] === '*')) {
4859
4892
  return; // Already done!
4860
4893
  }
4861
4894
  console.debug('Downloading Y.Docs from added realms');
@@ -4895,16 +4928,19 @@ function downloadYDocsFromServer(db, databaseUrl, { yDownloadedRealms, realms })
4895
4928
  yield yTable.bulkAdd(docsToInsert);
4896
4929
  docsToInsert = [];
4897
4930
  }
4898
- if (currentRealmId && currentTable && currentProp && (lastDoc || completedRealm)) {
4899
- yield db.$syncState.update('syncState', completedRealm
4900
- ? '*'
4901
- : {
4902
- [`yDownloadedRealms.${currentRealmId}`]: {
4931
+ if (currentRealmId &&
4932
+ ((currentTable && currentProp && lastDoc) || completedRealm)) {
4933
+ yield db.$syncState.update('syncState', (syncState) => {
4934
+ const yDownloadedRealms = syncState.yDownloadedRealms || {};
4935
+ yDownloadedRealms[currentRealmId] = completedRealm
4936
+ ? '*'
4937
+ : {
4903
4938
  tbl: currentTable,
4904
4939
  prop: currentProp,
4905
4940
  key: lastDoc.k,
4906
- },
4907
- });
4941
+ };
4942
+ syncState.yDownloadedRealms = yDownloadedRealms;
4943
+ });
4908
4944
  }
4909
4945
  });
4910
4946
  }
@@ -5007,11 +5043,11 @@ function sync(db, options, schema, syncOptions) {
5007
5043
  return Promise.reject(error);
5008
5044
  }));
5009
5045
  }
5010
- function _sync(db, options, schema, { isInitialSync, cancelToken, justCheckIfNeeded, purpose } = {
5011
- isInitialSync: false,
5012
- }) {
5013
- var _a;
5014
- return __awaiter(this, void 0, void 0, function* () {
5046
+ function _sync(db_1, options_1, schema_1) {
5047
+ return __awaiter(this, arguments, void 0, function* (db, options, schema, { isInitialSync, cancelToken, justCheckIfNeeded, purpose } = {
5048
+ isInitialSync: false,
5049
+ }) {
5050
+ var _a;
5015
5051
  if (!justCheckIfNeeded) {
5016
5052
  console.debug('SYNC STARTED', { isInitialSync, purpose });
5017
5053
  }
@@ -5053,8 +5089,8 @@ function _sync(db, options, schema, { isInitialSync, cancelToken, justCheckIfNee
5053
5089
  const [clientChangeSet, syncState, baseRevs, { yMessages, lastUpdateIds }] = yield db.transaction('r', db.tables, () => __awaiter(this, void 0, void 0, function* () {
5054
5090
  const syncState = yield db.getPersistedSyncState();
5055
5091
  const baseRevs = yield db.$baseRevs.toArray();
5056
- let clientChanges = yield listClientChanges(mutationTables);
5057
- const yResults = yield listYClientMessagesAndStateVector(db);
5092
+ let clientChanges = yield listClientChanges(mutationTables, db);
5093
+ const yResults = yield listYClientMessagesAndStateVector(db, tablesToSync);
5058
5094
  throwIfCancelled(cancelToken);
5059
5095
  if (doSyncify) {
5060
5096
  const alreadySyncedRealms = [
@@ -5322,8 +5358,8 @@ function MessagesFromServerConsumer(db) {
5322
5358
  event.next(null);
5323
5359
  }
5324
5360
  function consumeQueue() {
5325
- var _a, _b, _c, _d, _e, _f;
5326
5361
  return __awaiter(this, void 0, void 0, function* () {
5362
+ var _a, _b, _c, _d, _e, _f;
5327
5363
  while (queue.length > 0) {
5328
5364
  const msg = queue.shift();
5329
5365
  try {
@@ -5638,8 +5674,8 @@ function logout(db) {
5638
5674
  }
5639
5675
  });
5640
5676
  }
5641
- function _logout(db, { deleteUnsyncedData = false } = {}) {
5642
- return __awaiter(this, void 0, void 0, function* () {
5677
+ function _logout(db_1) {
5678
+ return __awaiter(this, arguments, void 0, function* (db, { deleteUnsyncedData = false } = {}) {
5643
5679
  // Clear the database without emptying configuration options.
5644
5680
  const [numUnsynced, loggedOut] = yield db.dx.transaction('rw', db.dx.tables, (tx) => __awaiter(this, void 0, void 0, function* () {
5645
5681
  // @ts-ignore
@@ -5677,11 +5713,11 @@ function _logout(db, { deleteUnsyncedData = false } = {}) {
5677
5713
 
5678
5714
  function otpFetchTokenCallback(db) {
5679
5715
  const { userInteraction } = db.cloud;
5680
- return function otpAuthenticate({ public_key, hints }) {
5681
- var _a;
5682
- return __awaiter(this, void 0, void 0, function* () {
5716
+ return function otpAuthenticate(_a) {
5717
+ return __awaiter(this, arguments, void 0, function* ({ public_key, hints }) {
5718
+ var _b;
5683
5719
  let tokenRequest;
5684
- const url = (_a = db.cloud.options) === null || _a === void 0 ? void 0 : _a.databaseUrl;
5720
+ const url = (_b = db.cloud.options) === null || _b === void 0 ? void 0 : _b.databaseUrl;
5685
5721
  if (!url)
5686
5722
  throw new Error(`No database URL given.`);
5687
5723
  if ((hints === null || hints === void 0 ? void 0 : hints.grant_type) === 'demo') {
@@ -5847,8 +5883,8 @@ function setCurrentUser(db, user) {
5847
5883
  }
5848
5884
 
5849
5885
  function login(db, hints) {
5850
- var _a;
5851
5886
  return __awaiter(this, void 0, void 0, function* () {
5887
+ var _a;
5852
5888
  const currentUser = yield db.getCurrentUser();
5853
5889
  const origUserId = currentUser.userId;
5854
5890
  if (currentUser.isLoggedIn && (!hints || (!hints.email && !hints.userId))) {
@@ -6285,8 +6321,7 @@ function createMutationTrackingMiddleware({ currentUserObservable, db, }) {
6285
6321
  outstandingTransactions.next(outstandingTransactions.value);
6286
6322
  };
6287
6323
  const txComplete = () => {
6288
- if (tx.mutationsAdded &&
6289
- !isEagerSyncDisabled(db)) {
6324
+ if (tx.mutationsAdded && !isEagerSyncDisabled(db)) {
6290
6325
  triggerSync(db, 'push');
6291
6326
  }
6292
6327
  removeTransaction();
@@ -6368,26 +6403,107 @@ function createMutationTrackingMiddleware({ currentUserObservable, db, }) {
6368
6403
  : mutateAndLog(req);
6369
6404
  } }));
6370
6405
  function mutateAndLog(req) {
6406
+ var _a, _b;
6371
6407
  const trans = req.trans;
6372
- trans.mutationsAdded = true;
6408
+ const unsyncedProps = (_b = (_a = db.cloud.options) === null || _a === void 0 ? void 0 : _a.unsyncedProperties) === null || _b === void 0 ? void 0 : _b[tableName];
6373
6409
  const { txid, currentUser: { userId }, } = trans;
6374
6410
  const { type } = req;
6375
6411
  const opNo = ++trans.opCount;
6412
+ function stripChangeSpec(changeSpec) {
6413
+ if (!unsyncedProps)
6414
+ return changeSpec;
6415
+ let rv = changeSpec;
6416
+ for (const keyPath of Object.keys(changeSpec)) {
6417
+ if (unsyncedProps.some((p) => keyPath === p || keyPath.startsWith(p + '.'))) {
6418
+ if (rv === changeSpec)
6419
+ rv = Object.assign({}, changeSpec); // clone on demand
6420
+ delete rv[keyPath];
6421
+ }
6422
+ }
6423
+ return rv;
6424
+ }
6376
6425
  return table.mutate(req).then((res) => {
6426
+ var _a;
6377
6427
  const { numFailures: hasFailures, failures } = res;
6378
6428
  let keys = type === 'delete' ? req.keys : res.results;
6379
6429
  let values = 'values' in req ? req.values : [];
6380
- let updates = 'updates' in req && req.updates;
6430
+ let changeSpec = 'changeSpec' in req ? req.changeSpec : undefined;
6431
+ let updates = 'updates' in req ? req.updates : undefined;
6381
6432
  if (hasFailures) {
6382
6433
  keys = keys.filter((_, idx) => !failures[idx]);
6383
6434
  values = values.filter((_, idx) => !failures[idx]);
6384
6435
  }
6436
+ if (unsyncedProps) {
6437
+ // Filter out unsynced properties
6438
+ values = values.map((value) => {
6439
+ const newValue = Object.assign({}, value);
6440
+ for (const prop of unsyncedProps) {
6441
+ delete newValue[prop];
6442
+ }
6443
+ return newValue;
6444
+ });
6445
+ if (changeSpec) {
6446
+ // modify operation with criteria and changeSpec.
6447
+ // We must strip out unsynced properties from changeSpec.
6448
+ // We deal with criteria later.
6449
+ changeSpec = stripChangeSpec(changeSpec);
6450
+ if (Object.keys(changeSpec).length === 0) {
6451
+ // Nothing to change on server
6452
+ return res;
6453
+ }
6454
+ }
6455
+ if (updates) {
6456
+ let strippedChangeSpecs = updates.changeSpecs.map(stripChangeSpec);
6457
+ let newUpdates = {
6458
+ keys: [],
6459
+ changeSpecs: [],
6460
+ };
6461
+ const validKeys = new RangeSet();
6462
+ let anyChangeSpecBecameEmpty = false;
6463
+ for (let i = 0, l = strippedChangeSpecs.length; i < l; ++i) {
6464
+ if (Object.keys(strippedChangeSpecs[i]).length > 0) {
6465
+ newUpdates.keys.push(updates.keys[i]);
6466
+ newUpdates.changeSpecs.push(strippedChangeSpecs[i]);
6467
+ validKeys.addKey(updates.keys[i]);
6468
+ }
6469
+ else {
6470
+ anyChangeSpecBecameEmpty = true;
6471
+ }
6472
+ }
6473
+ updates = newUpdates;
6474
+ if (anyChangeSpecBecameEmpty) {
6475
+ // Some keys were stripped. We must also strip them from keys and values
6476
+ let newKeys = [];
6477
+ let newValues = [];
6478
+ for (let i = 0, l = keys.length; i < l; ++i) {
6479
+ if (validKeys.hasKey(keys[i])) {
6480
+ newKeys.push(keys[i]);
6481
+ newValues.push(values[i]);
6482
+ }
6483
+ }
6484
+ keys = newKeys;
6485
+ values = newValues;
6486
+ }
6487
+ }
6488
+ }
6385
6489
  const ts = Date.now();
6386
6490
  // Canonicalize req.criteria.index to null if it's on the primary key.
6387
- const criteria = 'criteria' in req && req.criteria
6491
+ let criteria = 'criteria' in req && req.criteria
6388
6492
  ? Object.assign(Object.assign({}, req.criteria), { index: req.criteria.index === schema.primaryKey.keyPath // Use null to inform server that criteria is on primary key
6389
6493
  ? null // This will disable the server from trying to log consistent operations where it shouldnt.
6390
6494
  : req.criteria.index }) : undefined;
6495
+ if (unsyncedProps && (criteria === null || criteria === void 0 ? void 0 : criteria.index)) {
6496
+ const keyPaths = (_a = schema.indexes.find((idx) => idx.name === criteria.index)) === null || _a === void 0 ? void 0 : _a.keyPath;
6497
+ const involvedProps = keyPaths
6498
+ ? typeof keyPaths === 'string'
6499
+ ? [keyPaths]
6500
+ : keyPaths
6501
+ : [];
6502
+ if (involvedProps.some((p) => unsyncedProps === null || unsyncedProps === void 0 ? void 0 : unsyncedProps.includes(p))) {
6503
+ // Don't log criteria on unsynced properties as the server could not test them.
6504
+ criteria = undefined;
6505
+ }
6506
+ }
6391
6507
  const mut = req.type === 'delete'
6392
6508
  ? {
6393
6509
  type: 'delete',
@@ -6408,7 +6524,7 @@ function createMutationTrackingMiddleware({ currentUserObservable, db, }) {
6408
6524
  userId,
6409
6525
  values,
6410
6526
  }
6411
- : criteria && req.changeSpec
6527
+ : criteria && changeSpec
6412
6528
  ? {
6413
6529
  // Common changeSpec for all keys
6414
6530
  type: 'modify',
@@ -6416,37 +6532,51 @@ function createMutationTrackingMiddleware({ currentUserObservable, db, }) {
6416
6532
  opNo,
6417
6533
  keys,
6418
6534
  criteria,
6419
- changeSpec: req.changeSpec,
6535
+ changeSpec,
6420
6536
  txid,
6421
6537
  userId,
6422
6538
  }
6423
- : updates
6539
+ : changeSpec
6424
6540
  ? {
6425
- // One changeSpec per key
6541
+ // In case criteria involved an unsynced property, we go for keys instead.
6426
6542
  type: 'update',
6427
6543
  ts,
6428
6544
  opNo,
6429
- keys: updates.keys,
6430
- changeSpecs: updates.changeSpecs,
6431
- txid,
6432
- userId,
6433
- }
6434
- : {
6435
- type: 'upsert',
6436
- ts,
6437
- opNo,
6438
6545
  keys,
6439
- values,
6546
+ changeSpecs: keys.map(() => changeSpec),
6440
6547
  txid,
6441
6548
  userId,
6442
- };
6549
+ }
6550
+ : updates
6551
+ ? {
6552
+ // One changeSpec per key
6553
+ type: 'update',
6554
+ ts,
6555
+ opNo,
6556
+ keys: updates.keys,
6557
+ changeSpecs: updates.changeSpecs,
6558
+ txid,
6559
+ userId,
6560
+ }
6561
+ : {
6562
+ type: 'upsert',
6563
+ ts,
6564
+ opNo,
6565
+ keys,
6566
+ values,
6567
+ txid,
6568
+ userId,
6569
+ };
6443
6570
  if ('isAdditionalChunk' in req && req.isAdditionalChunk) {
6444
6571
  mut.isAdditionalChunk = true;
6445
6572
  }
6446
6573
  return keys.length > 0 || criteria
6447
6574
  ? mutsTable
6448
6575
  .mutate({ type: 'add', trans, values: [mut] }) // Log entry
6449
- .then(() => res) // Return original response
6576
+ .then(() => {
6577
+ trans.mutationsAdded = true; // Mark transaction as having added mutations to trigger eager sync
6578
+ return res; // Return original response
6579
+ })
6450
6580
  : res;
6451
6581
  });
6452
6582
  }
@@ -6613,38 +6743,51 @@ class TokenExpiredError extends Error {
6613
6743
 
6614
6744
  function createYClientUpdateObservable(db) {
6615
6745
  const yTableRecords = flatten(db.tables
6616
- .filter((table) => { var _a; return ((_a = db.cloud.schema) === null || _a === void 0 ? void 0 : _a[table.name].markedForSync) && table.schema.yProps; })
6746
+ .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; })
6617
6747
  .map((table) => table.schema.yProps.map((p) => ({
6618
6748
  table: table.name,
6619
6749
  ydocProp: p.prop,
6620
6750
  updatesTable: p.updatesTable,
6621
6751
  }))));
6622
6752
  return merge(...yTableRecords.map(({ table, ydocProp, updatesTable }) => {
6623
- let currentUnsentFrom = 1;
6624
- return liveQuery(() => __awaiter(this, void 0, void 0, function* () {
6625
- const yTbl = db.table(updatesTable);
6626
- const unsentFrom = yield yTbl
6627
- .where({ i: DEXIE_CLOUD_SYNCER_ID })
6628
- .first()
6629
- .then((syncer) => (syncer === null || syncer === void 0 ? void 0 : syncer.unsentFrom) || 1);
6630
- currentUnsentFrom = Math.max(currentUnsentFrom, unsentFrom);
6631
- const addedUpdates = yield listUpdatesSince(yTbl, currentUnsentFrom);
6632
- // Update currentUnsentFrom to only listen for updates that will be newer than the ones we emitted.
6633
- currentUnsentFrom = Math.max(currentUnsentFrom, ...addedUpdates.map((update) => update.i + 1));
6634
- return addedUpdates
6635
- .filter((update) => update.f && update.f & 1) // Only include local updates
6636
- .map((update) => {
6637
- return {
6638
- type: 'u-c',
6639
- table,
6640
- prop: ydocProp,
6641
- k: update.k,
6642
- u: update.u,
6643
- i: update.i,
6644
- };
6645
- });
6753
+ // Per updates table (table+prop combo), we first read syncer.unsentFrom,
6754
+ // and then start listening for updates since that number.
6755
+ const yTbl = db.table(updatesTable);
6756
+ return from$1(yTbl.get(DEXIE_CLOUD_SYNCER_ID)).pipe(switchMap$1((syncer) => {
6757
+ let currentUnsentFrom = (syncer === null || syncer === void 0 ? void 0 : syncer.unsentFrom) || 1;
6758
+ return from$1(liveQuery(() => __awaiter(this, void 0, void 0, function* () {
6759
+ const addedUpdates = yield listUpdatesSince(yTbl, currentUnsentFrom);
6760
+ return addedUpdates
6761
+ .filter((update) => update.f && update.f & 1) // Only include local updates
6762
+ .map((update) => {
6763
+ return {
6764
+ type: 'u-c',
6765
+ table,
6766
+ prop: ydocProp,
6767
+ k: update.k,
6768
+ u: update.u,
6769
+ i: update.i,
6770
+ };
6771
+ });
6772
+ }))).pipe(tap$1((addedUpdates) => {
6773
+ // Update currentUnsentFrom to only listen for updates that will be newer than the ones we emitted.
6774
+ // (Before, we did this within the liveQuery, but that caused a bug because
6775
+ // a cancelled emittion of a liveQuery would update the currentUnsentFrom without
6776
+ // emitting anything, leading to that we jumped over some updates. Here we update it
6777
+ // after the liveQuery has emitted its updates)
6778
+ if (addedUpdates.length > 0) {
6779
+ currentUnsentFrom = addedUpdates.at(-1).i + 1;
6780
+ }
6781
+ }));
6646
6782
  }));
6647
- })).pipe(mergeMap$1((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.
6783
+ })).pipe(
6784
+ // Flatten the array of messages.
6785
+ // If messageProducer emits empty array, nothing is emitted
6786
+ // but if messageProducer emits array of messages, they are
6787
+ // emitted one by one.
6788
+ mergeMap$1((messages) => messages), tap$1((message) => {
6789
+ console.debug('dexie-cloud emitting y-c', message);
6790
+ }));
6648
6791
  }
6649
6792
 
6650
6793
  function getAwarenessLibrary(db) {
@@ -6661,25 +6804,24 @@ const SERVER_PING_TIMEOUT = 20000;
6661
6804
  const CLIENT_PING_INTERVAL = 30000;
6662
6805
  const FAIL_RETRY_WAIT_TIME = 60000;
6663
6806
  class WSObservable extends Observable$1 {
6664
- constructor(db, rev, realmSetHash, clientIdentity, messageProducer, webSocketStatus, token, tokenExpiration) {
6665
- super((subscriber) => new WSConnection(db, rev, realmSetHash, clientIdentity, token, tokenExpiration, subscriber, messageProducer, webSocketStatus));
6807
+ constructor(db, rev, realmSetHash, clientIdentity, messageProducer, webSocketStatus, user) {
6808
+ super((subscriber) => new WSConnection(db, rev, realmSetHash, clientIdentity, user, subscriber, messageProducer, webSocketStatus));
6666
6809
  }
6667
6810
  }
6668
6811
  let counter = 0;
6669
6812
  class WSConnection extends Subscription$1 {
6670
- constructor(db, rev, realmSetHash, clientIdentity, token, tokenExpiration, subscriber, messageProducer, webSocketStatus) {
6813
+ constructor(db, rev, realmSetHash, clientIdentity, user, subscriber, messageProducer, webSocketStatus) {
6671
6814
  super(() => this.teardown());
6672
6815
  this.id = ++counter;
6673
6816
  this.subscriptions = new Set();
6674
6817
  this.reconnecting = false;
6675
- console.debug('New WebSocket Connection', this.id, token ? 'authorized' : 'unauthorized');
6818
+ console.debug('New WebSocket Connection', this.id, user.accessToken ? 'authorized' : 'unauthorized');
6676
6819
  this.db = db;
6677
6820
  this.databaseUrl = db.cloud.options.databaseUrl;
6678
6821
  this.rev = rev;
6679
6822
  this.realmSetHash = realmSetHash;
6680
6823
  this.clientIdentity = clientIdentity;
6681
- this.token = token;
6682
- this.tokenExpiration = tokenExpiration;
6824
+ this.user = user;
6683
6825
  this.subscriber = subscriber;
6684
6826
  this.lastUserActivity = new Date();
6685
6827
  this.messageProducer = messageProducer;
@@ -6739,7 +6881,8 @@ class WSConnection extends Subscription$1 {
6739
6881
  //console.debug('SyncStatus: DUBB: Ooops it was closed!');
6740
6882
  return;
6741
6883
  }
6742
- if (this.tokenExpiration && this.tokenExpiration < new Date()) {
6884
+ const tokenExpiration = this.user.accessTokenExpiration;
6885
+ if (tokenExpiration && tokenExpiration < new Date()) {
6743
6886
  this.subscriber.error(new TokenExpiredError()); // Will be handled in connectWebSocket.ts.
6744
6887
  return;
6745
6888
  }
@@ -6794,8 +6937,8 @@ class WSConnection extends Subscription$1 {
6794
6937
  searchParams.set('rev', this.rev);
6795
6938
  searchParams.set('realmsHash', this.realmSetHash);
6796
6939
  searchParams.set('clientId', this.clientIdentity);
6797
- if (this.token) {
6798
- searchParams.set('token', this.token);
6940
+ if (this.user.accessToken) {
6941
+ searchParams.set('token', this.user.accessToken);
6799
6942
  }
6800
6943
  // Connect the WebSocket to given url:
6801
6944
  console.debug('dexie-cloud WebSocket create');
@@ -6810,12 +6953,12 @@ class WSConnection extends Subscription$1 {
6810
6953
  ws.onmessage = (event) => {
6811
6954
  if (!this.pinger)
6812
6955
  return;
6813
- console.debug('dexie-cloud WebSocket onmessage', event.data);
6814
6956
  this.lastServerActivity = new Date();
6815
6957
  try {
6816
6958
  const msg = typeof event.data === 'string'
6817
6959
  ? TSON.parse(event.data)
6818
6960
  : decodeYMessage(new Uint8Array(event.data));
6961
+ console.debug('dexie-cloud WebSocket onmessage', msg.type, msg);
6819
6962
  if (msg.type === 'error') {
6820
6963
  throw new Error(`Error message from dexie-cloud: ${msg.error}`);
6821
6964
  }
@@ -6871,6 +7014,7 @@ class WSConnection extends Subscription$1 {
6871
7014
  this.webSocketStatus.value !== 'connected') {
6872
7015
  this.webSocketStatus.next('connected');
6873
7016
  }
7017
+ console.debug('dexie-cloud WebSocket send', msg.type, msg);
6874
7018
  if (msg.type === 'ready') {
6875
7019
  (_a = this.ws) === null || _a === void 0 ? void 0 : _a.send(TSON.stringify(msg));
6876
7020
  }
@@ -6881,7 +7025,9 @@ class WSConnection extends Subscription$1 {
6881
7025
  }
6882
7026
  }
6883
7027
  }));
6884
- this.subscriptions.add(createYClientUpdateObservable(this.db).subscribe(this.db.messageProducer));
7028
+ if (this.user.isLoggedIn && !isEagerSyncDisabled(this.db)) {
7029
+ this.subscriptions.add(createYClientUpdateObservable(this.db).subscribe(this.db.messageProducer));
7030
+ }
6885
7031
  }
6886
7032
  catch (error) {
6887
7033
  this.pauseUntil = new Date(Date.now() + FAIL_RETRY_WAIT_TIME);
@@ -6951,7 +7097,7 @@ function connectWebSocket(db) {
6951
7097
  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]));
6952
7098
  }
6953
7099
  return new BehaviorSubject([userLogin, syncState]);
6954
- }), 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]) => {
7100
+ }), 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]) => {
6955
7101
  var _a;
6956
7102
  if (!((_a = db.cloud.persistedSyncState) === null || _a === void 0 ? void 0 : _a.value)) {
6957
7103
  // Restart the flow if persistedSyncState is not yet available.
@@ -6961,7 +7107,7 @@ function connectWebSocket(db) {
6961
7107
  // If no new entries, server won't bother the client. If new entries, server sends only those
6962
7108
  // and the baseRev of the last from same client-ID.
6963
7109
  if (userLogin) {
6964
- return new WSObservable(db, db.cloud.persistedSyncState.value.serverRevision, realmSetHash, db.cloud.persistedSyncState.value.clientIdentity, messageProducer, db.cloud.webSocketStatus, userLogin.accessToken, userLogin.accessTokenExpiration);
7110
+ return new WSObservable(db, db.cloud.persistedSyncState.value.serverRevision, realmSetHash, db.cloud.persistedSyncState.value.clientIdentity, messageProducer, db.cloud.webSocketStatus, userLogin);
6965
7111
  }
6966
7112
  else {
6967
7113
  return from$1([]);
@@ -7012,8 +7158,8 @@ function connectWebSocket(db) {
7012
7158
  }
7013
7159
 
7014
7160
  function isSyncNeeded(db) {
7015
- var _a;
7016
7161
  return __awaiter(this, void 0, void 0, function* () {
7162
+ var _a;
7017
7163
  return ((_a = db.cloud.options) === null || _a === void 0 ? void 0 : _a.databaseUrl) && db.cloud.schema
7018
7164
  ? yield sync(db, db.cloud.options, db.cloud.schema, { justCheckIfNeeded: true })
7019
7165
  : false;
@@ -7697,6 +7843,7 @@ class PermissionChecker {
7697
7843
  // If user can update any prop in any table in this realm, return true unless
7698
7844
  // it regards to ownership change:
7699
7845
  if (this.permissions.update === '*') {
7846
+ // @ts-ignore
7700
7847
  return props.every((prop) => prop !== 'owner');
7701
7848
  }
7702
7849
  const tablePermissions = (_b = this.permissions.update) === null || _b === void 0 ? void 0 : _b[this.tableName];
@@ -7768,125 +7915,139 @@ const getInvitesObservable = associate((db) => {
7768
7915
  });
7769
7916
 
7770
7917
  function createYHandler(db) {
7771
- const awap = getAwarenessLibrary(db);
7772
7918
  return (provider) => {
7773
7919
  var _a;
7774
7920
  const doc = provider.doc;
7775
- const { parentTable, parentId, parentProp, updatesTable } = doc.meta;
7921
+ const { parentTable } = doc.meta || {};
7776
7922
  if (!((_a = db.cloud.schema) === null || _a === void 0 ? void 0 : _a[parentTable].markedForSync)) {
7777
7923
  return; // The table that holds the doc is not marked for sync - leave it to dexie. No syncing, no awareness.
7778
7924
  }
7779
- let awareness = new awap.Awareness(doc);
7780
- awarenessWeakMap.set(doc, awareness);
7781
- provider.awareness = awareness;
7782
- awareness.on('update', ({ added, updated, removed }, origin) => {
7783
- // Send the update
7784
- const changedClients = added.concat(updated).concat(removed);
7785
- if (origin !== 'server') {
7786
- const update = awap.encodeAwarenessUpdate(awareness, changedClients);
7925
+ let awareness;
7926
+ Object.defineProperty(provider, "awareness", {
7927
+ get() {
7928
+ if (awareness)
7929
+ return awareness;
7930
+ awareness = createAwareness(db, doc, provider);
7931
+ awarenessWeakMap.set(doc, awareness);
7932
+ return awareness;
7933
+ }
7934
+ });
7935
+ };
7936
+ }
7937
+ function createAwareness(db, doc, provider) {
7938
+ const { parentTable, parentId, parentProp, updatesTable } = doc.meta;
7939
+ const awap = getAwarenessLibrary(db);
7940
+ const awareness = new awap.Awareness(doc);
7941
+ awareness.on('update', ({ added, updated, removed }, origin) => {
7942
+ // Send the update
7943
+ const changedClients = added.concat(updated).concat(removed);
7944
+ const user = db.cloud.currentUser.value;
7945
+ if (origin !== 'server' && user.isLoggedIn && !isEagerSyncDisabled(db)) {
7946
+ const update = awap.encodeAwarenessUpdate(awareness, changedClients);
7947
+ db.messageProducer.next({
7948
+ type: 'aware',
7949
+ table: parentTable,
7950
+ prop: parentProp,
7951
+ k: doc.meta.parentId,
7952
+ u: update,
7953
+ });
7954
+ if (provider.destroyed) {
7955
+ // We're called from awareness.on('destroy') that did
7956
+ // removeAwarenessStates.
7957
+ // It's time to also send the doc-close message that dexie-cloud understands
7958
+ // and uses to stop subscribing for updates and awareness updates and brings
7959
+ // down the cached information in memory on the WS connection for this.
7787
7960
  db.messageProducer.next({
7788
- type: 'aware',
7961
+ type: 'doc-close',
7789
7962
  table: parentTable,
7790
7963
  prop: parentProp,
7791
- k: doc.meta.parentId,
7792
- u: update,
7964
+ k: doc.meta.parentId
7793
7965
  });
7794
- if (provider.destroyed) {
7795
- // We're called from awareness.on('destroy') that did
7796
- // removeAwarenessStates.
7797
- // It's time to also send the doc-close message that dexie-cloud understands
7798
- // and uses to stop subscribing for updates and awareness updates and brings
7799
- // down the cached information in memory on the WS connection for this.
7800
- db.messageProducer.next({
7801
- type: 'doc-close',
7802
- table: parentTable,
7803
- prop: parentProp,
7804
- k: doc.meta.parentId
7805
- });
7806
- }
7807
7966
  }
7808
- });
7809
- awareness.on('destroy', () => {
7810
- // Signal to server that this provider is destroyed (the update event will be triggered, which
7811
- // in turn will trigger db.messageProducer that will send the message to the server if WS is connected)
7812
- awap.removeAwarenessStates(awareness, [doc.clientID], 'provider destroyed');
7813
- });
7814
- // Now wait til document is loaded and then open the document on the server
7815
- provider.on('load', () => __awaiter(this, void 0, void 0, function* () {
7967
+ }
7968
+ });
7969
+ awareness.on('destroy', () => {
7970
+ // Signal to server that this provider is destroyed (the update event will be triggered, which
7971
+ // in turn will trigger db.messageProducer that will send the message to the server if WS is connected)
7972
+ awap.removeAwarenessStates(awareness, [doc.clientID], 'provider destroyed');
7973
+ });
7974
+ // Open the document on the server
7975
+ (() => __awaiter(this, void 0, void 0, function* () {
7976
+ if (provider.destroyed)
7977
+ return;
7978
+ let connected = false;
7979
+ let currentFlowId = 1;
7980
+ const subscription = db.cloud.webSocketStatus.subscribe((wsStatus) => {
7816
7981
  if (provider.destroyed)
7817
7982
  return;
7818
- let connected = false;
7819
- let currentFlowId = 1;
7820
- const subscription = db.cloud.webSocketStatus.subscribe((wsStatus) => {
7821
- if (provider.destroyed)
7983
+ // Keep "connected" state in a variable so we can check it after async operations
7984
+ connected = wsStatus === 'connected';
7985
+ // We are or got connected. Open the document on the server.
7986
+ const user = db.cloud.currentUser.value;
7987
+ if (wsStatus === "connected" && user.isLoggedIn && !isEagerSyncDisabled(db)) {
7988
+ ++currentFlowId;
7989
+ openDocumentOnServer().catch(error => {
7990
+ console.warn(`Error catched in createYHandler.ts: ${error}`);
7991
+ });
7992
+ }
7993
+ });
7994
+ // Wait until WebSocket is connected
7995
+ provider.addCleanupHandler(subscription);
7996
+ /** Sends an 'doc-open' message to server whenever websocket becomes
7997
+ * connected, or if it is already connected.
7998
+ * The flow is aborted in case websocket is disconnected while querying
7999
+ * information required to compute the state vector. Flow is also
8000
+ * aborted in case document or provider has been destroyed during
8001
+ * the async parts of the task.
8002
+ *
8003
+ * The state vector is only computed from the updates that have occured
8004
+ * after the last full sync - which could very often be zero - in which
8005
+ * case no state vector is sent (then the server already knows us by
8006
+ * revision)
8007
+ *
8008
+ * When server gets the doc-open message, it will authorized us for
8009
+ * whether we are allowed to read / write to this document, and then
8010
+ * keep the cached information in memory on the WS connection for this
8011
+ * particular document, as well as subscribe to updates and awareness updates
8012
+ * from other clients on the document.
8013
+ */
8014
+ function openDocumentOnServer(wsStatus) {
8015
+ return __awaiter(this, void 0, void 0, function* () {
8016
+ const myFlow = currentFlowId; // So we can abort when a new flow is started
8017
+ const yTbl = db.table(updatesTable);
8018
+ const syncState = yield yTbl.get(DEXIE_CLOUD_SYNCER_ID);
8019
+ // After every await, check if we still should be working on this task.
8020
+ if (provider.destroyed || currentFlowId !== myFlow || !connected)
7822
8021
  return;
7823
- // Keep "connected" state in a variable so we can check it after async operations
7824
- connected = wsStatus === 'connected';
7825
- // We are or got connected. Open the document on the server.
7826
- if (wsStatus === "connected") {
7827
- ++currentFlowId;
7828
- openDocumentOnServer().catch(error => {
7829
- console.warn(`Error catched in createYHandler.ts: ${error}`);
7830
- });
8022
+ const receivedUntil = (syncState === null || syncState === void 0 ? void 0 : syncState.receivedUntil) || 0;
8023
+ const docOpenMsg = {
8024
+ type: 'doc-open',
8025
+ table: parentTable,
8026
+ prop: parentProp,
8027
+ k: parentId,
8028
+ serverRev: syncState === null || syncState === void 0 ? void 0 : syncState.serverRev,
8029
+ };
8030
+ const serverUpdatesSinceLastSync = yield yTbl
8031
+ .where('i')
8032
+ .between(receivedUntil, Infinity, false)
8033
+ .filter((update) => cmp(update.k, parentId) === 0 && // Only updates for this document
8034
+ ((update.f || 0) & 1) === 0 // Don't include local changes
8035
+ )
8036
+ .toArray();
8037
+ // After every await, check if we still should be working on this task.
8038
+ if (provider.destroyed || currentFlowId !== myFlow || !connected)
8039
+ return;
8040
+ if (serverUpdatesSinceLastSync.length > 0) {
8041
+ const Y = $Y(db); // Get the Yjs library from Dexie constructor options
8042
+ const mergedUpdate = Y.mergeUpdatesV2(serverUpdatesSinceLastSync.map((update) => update.u));
8043
+ const stateVector = Y.encodeStateVectorFromUpdateV2(mergedUpdate);
8044
+ docOpenMsg.sv = stateVector;
7831
8045
  }
8046
+ db.messageProducer.next(docOpenMsg);
7832
8047
  });
7833
- // Wait until WebSocket is connected
7834
- provider.addCleanupHandler(subscription);
7835
- /** Sends an 'doc-open' message to server whenever websocket becomes
7836
- * connected, or if it is already connected.
7837
- * The flow is aborted in case websocket is disconnected while querying
7838
- * information required to compute the state vector. Flow is also
7839
- * aborted in case document or provider has been destroyed during
7840
- * the async parts of the task.
7841
- *
7842
- * The state vector is only computed from the updates that have occured
7843
- * after the last full sync - which could very often be zero - in which
7844
- * case no state vector is sent (then the server already knows us by
7845
- * revision)
7846
- *
7847
- * When server gets the doc-open message, it will authorized us for
7848
- * whether we are allowed to read / write to this document, and then
7849
- * keep the cached information in memory on the WS connection for this
7850
- * particular document, as well as subscribe to updates and awareness updates
7851
- * from other clients on the document.
7852
- */
7853
- function openDocumentOnServer(wsStatus) {
7854
- return __awaiter(this, void 0, void 0, function* () {
7855
- const myFlow = currentFlowId; // So we can abort when a new flow is started
7856
- const yTbl = db.table(updatesTable);
7857
- const syncState = yield yTbl.get(DEXIE_CLOUD_SYNCER_ID);
7858
- // After every await, check if we still should be working on this task.
7859
- if (provider.destroyed || currentFlowId !== myFlow || !connected)
7860
- return;
7861
- const receivedUntil = (syncState === null || syncState === void 0 ? void 0 : syncState.receivedUntil) || 0;
7862
- const docOpenMsg = {
7863
- type: 'doc-open',
7864
- table: parentTable,
7865
- prop: parentProp,
7866
- k: parentId,
7867
- serverRev: syncState === null || syncState === void 0 ? void 0 : syncState.serverRev,
7868
- };
7869
- const serverUpdatesSinceLastSync = yield yTbl
7870
- .where('i')
7871
- .between(receivedUntil, Infinity, false)
7872
- .filter((update) => cmp(update.k, parentId) === 0 && // Only updates for this document
7873
- ((update.f || 0) & 1) === 0 // Don't include local changes
7874
- )
7875
- .toArray();
7876
- // After every await, check if we still should be working on this task.
7877
- if (provider.destroyed || currentFlowId !== myFlow || !connected)
7878
- return;
7879
- if (serverUpdatesSinceLastSync.length > 0) {
7880
- const Y = $Y(db); // Get the Yjs library from Dexie constructor options
7881
- const mergedUpdate = Y.mergeUpdatesV2(serverUpdatesSinceLastSync.map((update) => update.u));
7882
- const stateVector = Y.encodeStateVectorFromUpdateV2(mergedUpdate);
7883
- docOpenMsg.sv = stateVector;
7884
- }
7885
- db.messageProducer.next(docOpenMsg);
7886
- });
7887
- }
7888
- }));
7889
- };
8048
+ }
8049
+ }))();
8050
+ return awareness;
7890
8051
  }
7891
8052
 
7892
8053
  const DEFAULT_OPTIONS = {
@@ -7929,7 +8090,7 @@ function dexieCloud(dexie) {
7929
8090
  const syncComplete = new Subject();
7930
8091
  dexie.cloud = {
7931
8092
  // @ts-ignore
7932
- version: "4.1.0-alpha.2",
8093
+ version: "4.1.0-alpha.20",
7933
8094
  options: Object.assign({}, DEFAULT_OPTIONS),
7934
8095
  schema: null,
7935
8096
  get currentUserId() {
@@ -7965,16 +8126,16 @@ function dexieCloud(dexie) {
7965
8126
  }
7966
8127
  updateSchemaFromOptions(dexie.cloud.schema, dexie.cloud.options);
7967
8128
  },
7968
- logout({ force } = {}) {
7969
- return __awaiter(this, void 0, void 0, function* () {
8129
+ logout() {
8130
+ return __awaiter(this, arguments, void 0, function* ({ force } = {}) {
7970
8131
  force
7971
8132
  ? yield _logout(DexieCloudDB(dexie), { deleteUnsyncedData: true })
7972
8133
  : yield logout(DexieCloudDB(dexie));
7973
8134
  });
7974
8135
  },
7975
- sync({ wait, purpose } = { wait: true, purpose: 'push' }) {
7976
- var _a;
7977
- return __awaiter(this, void 0, void 0, function* () {
8136
+ sync() {
8137
+ return __awaiter(this, arguments, void 0, function* ({ wait, purpose } = { wait: true, purpose: 'push' }) {
8138
+ var _a;
7978
8139
  if (wait === undefined)
7979
8140
  wait = true;
7980
8141
  const db = DexieCloudDB(dexie);
@@ -8032,8 +8193,8 @@ function dexieCloud(dexie) {
8032
8193
  dexie.use(createImplicitPropSetterMiddleware(DexieCloudDB(dexie)));
8033
8194
  dexie.use(createIdGenerationMiddleware(DexieCloudDB(dexie)));
8034
8195
  function onDbReady(dexie) {
8035
- var _a, _b, _c, _d, _e, _f, _g;
8036
8196
  return __awaiter(this, void 0, void 0, function* () {
8197
+ var _a, _b, _c, _d, _e, _f, _g;
8037
8198
  closed = false; // As Dexie calls us, we are not closed anymore. Maybe reopened? Remember db.ready event is registered with sticky flag!
8038
8199
  const db = DexieCloudDB(dexie);
8039
8200
  // Setup default GUI:
@@ -8056,7 +8217,7 @@ function dexieCloud(dexie) {
8056
8217
  ? yield navigator.serviceWorker.getRegistrations()
8057
8218
  : [];
8058
8219
  const [initiallySynced, lastSyncedRealms] = yield db.transaction('rw', db.$syncState, () => __awaiter(this, void 0, void 0, function* () {
8059
- var _h, _j;
8220
+ var _a, _b;
8060
8221
  const { options, schema } = db.cloud;
8061
8222
  const [persistedOptions, persistedSchema, persistedSyncState] = yield Promise.all([
8062
8223
  db.getOptions(),
@@ -8078,7 +8239,7 @@ function dexieCloud(dexie) {
8078
8239
  delete newPersistedOptions.awarenessProtocol;
8079
8240
  yield db.$syncState.put(newPersistedOptions, 'options');
8080
8241
  }
8081
- if (((_h = db.cloud.options) === null || _h === void 0 ? void 0 : _h.tryUseServiceWorker) &&
8242
+ if (((_a = db.cloud.options) === null || _a === void 0 ? void 0 : _a.tryUseServiceWorker) &&
8082
8243
  'serviceWorker' in navigator &&
8083
8244
  swRegistrations.length > 0 &&
8084
8245
  !DISABLE_SERVICEWORKER_STRATEGY) {
@@ -8092,7 +8253,7 @@ function dexieCloud(dexie) {
8092
8253
  // Not configured for using service worker or no service worker
8093
8254
  // registration exists. Don't rely on service worker to do any job.
8094
8255
  // Use LocalSyncWorker instead.
8095
- if (((_j = db.cloud.options) === null || _j === void 0 ? void 0 : _j.tryUseServiceWorker) &&
8256
+ if (((_b = db.cloud.options) === null || _b === void 0 ? void 0 : _b.tryUseServiceWorker) &&
8096
8257
  !db.cloud.isServiceWorkerDB) {
8097
8258
  console.debug('dexie-cloud-addon: Not using service worker.', swRegistrations.length === 0
8098
8259
  ? 'No SW registrations found.'
@@ -8231,7 +8392,7 @@ function dexieCloud(dexie) {
8231
8392
  }
8232
8393
  }
8233
8394
  // @ts-ignore
8234
- dexieCloud.version = "4.1.0-alpha.2";
8395
+ dexieCloud.version = "4.1.0-alpha.20";
8235
8396
  Dexie.Cloud = dexieCloud;
8236
8397
 
8237
8398
  // In case the SW lives for a while, let it reuse already opened connections:
@@ -8261,8 +8422,8 @@ function syncDB(dbName, purpose) {
8261
8422
  }
8262
8423
  return promise;
8263
8424
  function _syncDB(dbName, purpose) {
8264
- var _a;
8265
8425
  return __awaiter(this, void 0, void 0, function* () {
8426
+ var _a;
8266
8427
  let db = managedDBs.get(dbName);
8267
8428
  if (!db) {
8268
8429
  console.debug('Dexie Cloud SW: Creating new Dexie instance for', dbName);