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
  *
@@ -16,8 +16,8 @@
16
16
  *
17
17
  */
18
18
 
19
- import Dexie, { PropModification, cmp, DexieYProvider, liveQuery } from 'dexie';
20
- import { firstValueFrom, from as from$1, filter as filter$1, Observable as Observable$1, BehaviorSubject, Subject, 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 { firstValueFrom, from as from$1, filter as filter$1, Observable as Observable$1, BehaviorSubject, Subject, 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.
@@ -979,13 +979,17 @@ const writeAny = (encoder, data) => {
979
979
  function encodeYMessage(msg) {
980
980
  const encoder = new Encoder();
981
981
  writeVarString(encoder, msg.type);
982
- writeVarString(encoder, msg.table);
983
- writeVarString(encoder, msg.prop);
982
+ if ('table' in msg)
983
+ writeVarString(encoder, msg.table);
984
+ if ('prop' in msg)
985
+ writeVarString(encoder, msg.prop);
984
986
  switch (msg.type) {
985
987
  case 'u-ack':
986
988
  case 'u-reject':
987
989
  writeBigUint64(encoder, BigInt(msg.i));
988
990
  break;
991
+ case 'outdated-server-rev':
992
+ break;
989
993
  default:
990
994
  writeAny(encoder, msg.k);
991
995
  switch (msg.type) {
@@ -1329,6 +1333,9 @@ const readAny = decoder => readAnyLookupTable[127 - readUint8(decoder)](decoder)
1329
1333
  function decodeYMessage(a) {
1330
1334
  const decoder = new Decoder(a);
1331
1335
  const type = readVarString(decoder);
1336
+ if (type === 'outdated-server-rev') {
1337
+ return { type };
1338
+ }
1332
1339
  const table = readVarString(decoder);
1333
1340
  const prop = readVarString(decoder);
1334
1341
  switch (type) {
@@ -2987,8 +2994,8 @@ function registerSyncEvent(db, purpose) {
2987
2994
  });
2988
2995
  }
2989
2996
  function registerPeriodicSyncEvent(db) {
2990
- var _a;
2991
2997
  return __awaiter(this, void 0, void 0, function* () {
2998
+ var _a;
2992
2999
  try {
2993
3000
  // Register periodicSync event to SW:
2994
3001
  // @ts-ignore
@@ -3199,8 +3206,8 @@ function confirmLogout(userInteraction, currentUserId, numUnsyncedChanges) {
3199
3206
  }
3200
3207
 
3201
3208
  function loadAccessToken(db) {
3202
- var _a, _b, _c;
3203
3209
  return __awaiter(this, void 0, void 0, function* () {
3210
+ var _a, _b, _c;
3204
3211
  const currentUser = yield db.getCurrentUser();
3205
3212
  const { accessToken, accessTokenExpiration, refreshToken, refreshTokenExpiration, claims, } = currentUser;
3206
3213
  if (!accessToken)
@@ -3472,8 +3479,8 @@ function logout(db) {
3472
3479
  }
3473
3480
  });
3474
3481
  }
3475
- function _logout(db, { deleteUnsyncedData = false } = {}) {
3476
- return __awaiter(this, void 0, void 0, function* () {
3482
+ function _logout(db_1) {
3483
+ return __awaiter(this, arguments, void 0, function* (db, { deleteUnsyncedData = false } = {}) {
3477
3484
  // Clear the database without emptying configuration options.
3478
3485
  const [numUnsynced, loggedOut] = yield db.dx.transaction('rw', db.dx.tables, (tx) => __awaiter(this, void 0, void 0, function* () {
3479
3486
  // @ts-ignore
@@ -3521,11 +3528,11 @@ class HttpError extends Error {
3521
3528
 
3522
3529
  function otpFetchTokenCallback(db) {
3523
3530
  const { userInteraction } = db.cloud;
3524
- return function otpAuthenticate({ public_key, hints }) {
3525
- var _a;
3526
- return __awaiter(this, void 0, void 0, function* () {
3531
+ return function otpAuthenticate(_a) {
3532
+ return __awaiter(this, arguments, void 0, function* ({ public_key, hints }) {
3533
+ var _b;
3527
3534
  let tokenRequest;
3528
- const url = (_a = db.cloud.options) === null || _a === void 0 ? void 0 : _a.databaseUrl;
3535
+ const url = (_b = db.cloud.options) === null || _b === void 0 ? void 0 : _b.databaseUrl;
3529
3536
  if (!url)
3530
3537
  throw new Error(`No database URL given.`);
3531
3538
  if ((hints === null || hints === void 0 ? void 0 : hints.grant_type) === 'demo') {
@@ -3691,8 +3698,8 @@ function setCurrentUser(db, user) {
3691
3698
  }
3692
3699
 
3693
3700
  function login(db, hints) {
3694
- var _a;
3695
3701
  return __awaiter(this, void 0, void 0, function* () {
3702
+ var _a;
3696
3703
  const currentUser = yield db.getCurrentUser();
3697
3704
  const origUserId = currentUser.userId;
3698
3705
  if (currentUser.isLoggedIn && (!hints || (!hints.email && !hints.userId))) {
@@ -3880,8 +3887,8 @@ class BroadcastedAndLocalEvent extends Observable$1 {
3880
3887
  }
3881
3888
  }
3882
3889
 
3883
- function computeRealmSetHash({ realms, inviteRealms, }) {
3884
- return __awaiter(this, void 0, void 0, function* () {
3890
+ function computeRealmSetHash(_a) {
3891
+ return __awaiter(this, arguments, void 0, function* ({ realms, inviteRealms, }) {
3885
3892
  const data = JSON.stringify([
3886
3893
  ...realms.map((realmId) => ({ realmId, accepted: true })),
3887
3894
  ...inviteRealms.map((realmId) => ({ realmId, accepted: false })),
@@ -3917,8 +3924,8 @@ function flatten(a) {
3917
3924
  return concat.apply([], a);
3918
3925
  }
3919
3926
 
3920
- function listClientChanges(mutationTables, db, { since = {}, limit = Infinity } = {}) {
3921
- return __awaiter(this, void 0, void 0, function* () {
3927
+ function listClientChanges(mutationTables_1, db_1) {
3928
+ return __awaiter(this, arguments, void 0, function* (mutationTables, db, { since = {}, limit = Infinity } = {}) {
3922
3929
  const allMutsOnTables = yield Promise.all(mutationTables.map((mutationTable) => __awaiter(this, void 0, void 0, function* () {
3923
3930
  const tableName = getTableFromMutationTable(mutationTable.name);
3924
3931
  const lastRevision = since[tableName];
@@ -4653,8 +4660,8 @@ function cloneChange(change, rewriteValues) {
4653
4660
  // seconds (given that there is a Ratelimit-Reset header).
4654
4661
  let syncRatelimitDelays = new WeakMap();
4655
4662
  function checkSyncRateLimitDelay(db) {
4656
- var _a, _b;
4657
4663
  return __awaiter(this, void 0, void 0, function* () {
4664
+ var _a, _b;
4658
4665
  const delatMilliseconds = ((_b = (_a = syncRatelimitDelays.get(db)) === null || _a === void 0 ? void 0 : _a.getTime()) !== null && _b !== void 0 ? _b : 0) - Date.now();
4659
4666
  if (delatMilliseconds > 0) {
4660
4667
  console.debug(`Stalling sync request ${delatMilliseconds} ms to spare ratelimits`);
@@ -4944,7 +4951,7 @@ function listUpdatesSince(yTable, sinceIncluding) {
4944
4951
  .toArray();
4945
4952
  }
4946
4953
 
4947
- function $Y(db) {
4954
+ function $Y$1(db) {
4948
4955
  const $Y = db.dx._options.Y;
4949
4956
  if (!$Y)
4950
4957
  throw new Error('Y library not supplied to Dexie constructor');
@@ -4967,15 +4974,14 @@ function $Y(db) {
4967
4974
  * @param db
4968
4975
  * @returns
4969
4976
  */
4970
- function listYClientMessagesAndStateVector(db) {
4971
- var _a;
4977
+ function listYClientMessagesAndStateVector(db, tablesToSync) {
4972
4978
  return __awaiter(this, void 0, void 0, function* () {
4973
4979
  const result = [];
4974
4980
  const lastUpdateIds = {};
4975
- for (const table of db.tables) {
4976
- if (table.schema.yProps && ((_a = db.cloud.schema) === null || _a === void 0 ? void 0 : _a[table.name].markedForSync)) {
4981
+ for (const table of tablesToSync) {
4982
+ if (table.schema.yProps) {
4977
4983
  for (const yProp of table.schema.yProps) {
4978
- const Y = $Y(db); // This is how we retrieve the user-provided Y library
4984
+ const Y = $Y$1(db); // This is how we retrieve the user-provided Y library
4979
4985
  const yTable = db.table(yProp.updatesTable); // the updates-table for this combo of table+propName
4980
4986
  const syncState = (yield yTable.get(DEXIE_CLOUD_SYNCER_ID));
4981
4987
  // unsentFrom = the `i` value of updates that aren't yet sent to server (or at least not acked by the server yet)
@@ -5060,12 +5066,14 @@ function getUpdatesTable(db, table, ydocProp) {
5060
5066
 
5061
5067
  function applyYServerMessages(yMessages, db) {
5062
5068
  return __awaiter(this, void 0, void 0, function* () {
5063
- const result = {};
5069
+ var _a;
5070
+ const receivedUntils = {};
5071
+ let resyncNeeded = false;
5064
5072
  for (const m of yMessages) {
5065
5073
  switch (m.type) {
5066
5074
  case 'u-s': {
5067
5075
  const utbl = getUpdatesTable(db, m.table, m.prop);
5068
- result[utbl.name] = yield utbl.add({
5076
+ receivedUntils[utbl.name] = yield utbl.add({
5069
5077
  k: m.k,
5070
5078
  u: m.u,
5071
5079
  });
@@ -5090,7 +5098,24 @@ function applyYServerMessages(yMessages, db) {
5090
5098
  // See my question in https://discuss.yjs.dev/t/generate-an-inverse-update/2765
5091
5099
  console.debug(`Y update rejected. Deleting it.`);
5092
5100
  const utbl = getUpdatesTable(db, m.table, m.prop);
5093
- yield utbl.delete(m.i);
5101
+ // Delete the rejected update and all local updates since (avoid holes in the CRDT)
5102
+ // and destroy it's open document if there is one.
5103
+ const primaryKey = (_a = (yield utbl.get(m.i))) === null || _a === void 0 ? void 0 : _a.k;
5104
+ if (primaryKey != null) {
5105
+ yield db.transaction('rw', utbl, (tx) => {
5106
+ // @ts-ignore
5107
+ tx.idbtrans._rejecting_y_ypdate = true; // Inform ydoc triggers that we delete because of a rejection and not GC
5108
+ return utbl
5109
+ .where('i')
5110
+ .aboveOrEqual(m.i)
5111
+ .filter((u) => cmp(u.k, primaryKey) === 0 && ((u.f || 0) & 1) === 1)
5112
+ .delete();
5113
+ });
5114
+ // Destroy active doc
5115
+ const activeDoc = DexieYProvider.getDocCache(db.dx).find(m.table, primaryKey, m.prop);
5116
+ if (activeDoc)
5117
+ activeDoc.destroy(); // Destroy the document so that editors don't continue to work on it
5118
+ }
5094
5119
  break;
5095
5120
  }
5096
5121
  case 'in-sync': {
@@ -5100,22 +5125,28 @@ function applyYServerMessages(yMessages, db) {
5100
5125
  }
5101
5126
  break;
5102
5127
  }
5128
+ case 'outdated-server-rev':
5129
+ resyncNeeded = true;
5130
+ break;
5103
5131
  }
5104
5132
  }
5105
- return result;
5133
+ return {
5134
+ receivedUntils,
5135
+ resyncNeeded,
5136
+ };
5106
5137
  });
5107
5138
  }
5108
5139
 
5109
5140
  function updateYSyncStates(lastUpdateIdsBeforeSync, receivedUntilsAfterSync, db, serverRevision) {
5110
- var _a, _b;
5111
5141
  return __awaiter(this, void 0, void 0, function* () {
5142
+ var _a, _b, _c, _d, _e;
5112
5143
  // We want to update unsentFrom for each yTable to the value specified in first argument
5113
5144
  // because we got those values before we synced with server and here we are back from server
5114
5145
  // that has successfully received all those messages - no matter if the last update was a client or server update,
5115
5146
  // we can safely store unsentFrom to a value of the last update + 1 here.
5116
5147
  // We also want to update receivedUntil for each yTable to the value specified in the second argument,
5117
5148
  // because that contains the highest resulted id of each update from server after storing it.
5118
- // We could do these two tasks separately, but that would require two update calls on the same YSyncState, so
5149
+ // We could do these two tasks separately, but that would require two update calls on the same YSyncState, so
5119
5150
  // to optimize the dexie calls, we merge these two maps into a single one so we can do a single update request
5120
5151
  // per yTable.
5121
5152
  const mergedSpec = {};
@@ -5127,28 +5158,42 @@ function updateYSyncStates(lastUpdateIdsBeforeSync, receivedUntilsAfterSync, db,
5127
5158
  (_b = mergedSpec[yTable]) !== null && _b !== void 0 ? _b : (mergedSpec[yTable] = {});
5128
5159
  mergedSpec[yTable].receivedUntil = lastUpdateId;
5129
5160
  }
5130
- // Now go through the merged map and update YSyncStates accordingly:
5131
- for (const [yTable, { unsentFrom, receivedUntil }] of Object.entries(mergedSpec)) {
5161
+ // Now go through all yTables and update their YSyncStates:
5162
+ const allYTables = Object.values(db.dx._dbSchema)
5163
+ .filter((tblSchema) => tblSchema.yProps)
5164
+ .map((tblSchema) => tblSchema.yProps.map((yProp) => yProp.updatesTable))
5165
+ .flat();
5166
+ for (const yTable of allYTables) {
5167
+ const mergedEntry = mergedSpec[yTable];
5168
+ const unsentFrom = (_c = mergedEntry === null || mergedEntry === void 0 ? void 0 : mergedEntry.unsentFrom) !== null && _c !== void 0 ? _c : 1;
5169
+ const receivedUntil = (_e = (_d = mergedEntry === null || mergedEntry === void 0 ? void 0 : mergedEntry.receivedUntil) !== null && _d !== void 0 ? _d :
5170
+ // from local because we are in the same parent transaction (in sync.ts) that
5171
+ // applied all updates from the server
5172
+ (yield db
5173
+ .table(yTable)
5174
+ .where('i')
5175
+ .between(1, Infinity) // Because i might be string DEXIE_CLOUD_SYNCER_ID if not a number.
5176
+ .reverse()
5177
+ .limit(1)
5178
+ .primaryKeys())[0]) !== null && _e !== void 0 ? _e : 0;
5132
5179
  // We're already in a transaction, but for the sake of
5133
5180
  // code readability and correctness, let's launch an atomic sub transaction:
5134
5181
  yield db.transaction('rw', yTable, () => __awaiter(this, void 0, void 0, function* () {
5135
- const state = yield db.table(yTable).get(DEXIE_CLOUD_SYNCER_ID);
5182
+ const state = yield db
5183
+ .table(yTable)
5184
+ .get(DEXIE_CLOUD_SYNCER_ID);
5136
5185
  if (!state) {
5137
5186
  yield db.table(yTable).add({
5138
5187
  i: DEXIE_CLOUD_SYNCER_ID,
5139
- unsentFrom: unsentFrom || 1,
5140
- receivedUntil: receivedUntil || 0,
5188
+ unsentFrom,
5189
+ receivedUntil,
5141
5190
  serverRev: serverRevision,
5142
5191
  });
5143
5192
  }
5144
5193
  else {
5145
- if (unsentFrom) {
5146
- state.unsentFrom = Math.max(unsentFrom, state.unsentFrom || 1);
5147
- }
5148
- if (receivedUntil) {
5149
- state.receivedUntil = Math.max(receivedUntil, state.receivedUntil || 0);
5150
- state.serverRev = serverRevision;
5151
- }
5194
+ state.unsentFrom = Math.max(unsentFrom, state.unsentFrom || 1);
5195
+ state.receivedUntil = Math.max(receivedUntil, state.receivedUntil || 0);
5196
+ state.serverRev = serverRevision;
5152
5197
  yield db.table(yTable).put(state);
5153
5198
  }
5154
5199
  }));
@@ -5159,9 +5204,11 @@ function updateYSyncStates(lastUpdateIdsBeforeSync, receivedUntilsAfterSync, db,
5159
5204
  const BINSTREAM_TYPE_REALMID = 1;
5160
5205
  const BINSTREAM_TYPE_TABLE_AND_PROP = 2;
5161
5206
  const BINSTREAM_TYPE_DOCUMENT = 3;
5162
- function downloadYDocsFromServer(db, databaseUrl, { yDownloadedRealms, realms }) {
5163
- return __awaiter(this, void 0, void 0, function* () {
5164
- if (yDownloadedRealms && realms && realms.every(realmId => yDownloadedRealms[realmId] === '*')) {
5207
+ function downloadYDocsFromServer(db_1, databaseUrl_1, _a) {
5208
+ return __awaiter(this, arguments, void 0, function* (db, databaseUrl, { yDownloadedRealms, realms }) {
5209
+ if (yDownloadedRealms &&
5210
+ realms &&
5211
+ realms.every((realmId) => yDownloadedRealms[realmId] === '*')) {
5165
5212
  return; // Already done!
5166
5213
  }
5167
5214
  console.debug('Downloading Y.Docs from added realms');
@@ -5201,16 +5248,19 @@ function downloadYDocsFromServer(db, databaseUrl, { yDownloadedRealms, realms })
5201
5248
  yield yTable.bulkAdd(docsToInsert);
5202
5249
  docsToInsert = [];
5203
5250
  }
5204
- if (currentRealmId && currentTable && currentProp && (lastDoc || completedRealm)) {
5205
- yield db.$syncState.update('syncState', completedRealm
5206
- ? '*'
5207
- : {
5208
- [`yDownloadedRealms.${currentRealmId}`]: {
5251
+ if (currentRealmId &&
5252
+ ((currentTable && currentProp && lastDoc) || completedRealm)) {
5253
+ yield db.$syncState.update('syncState', (syncState) => {
5254
+ const yDownloadedRealms = syncState.yDownloadedRealms || {};
5255
+ yDownloadedRealms[currentRealmId] = completedRealm
5256
+ ? '*'
5257
+ : {
5209
5258
  tbl: currentTable,
5210
5259
  prop: currentProp,
5211
5260
  key: lastDoc.k,
5212
- },
5213
- });
5261
+ };
5262
+ syncState.yDownloadedRealms = yDownloadedRealms;
5263
+ });
5214
5264
  }
5215
5265
  });
5216
5266
  }
@@ -5313,11 +5363,11 @@ function sync(db, options, schema, syncOptions) {
5313
5363
  return Promise.reject(error);
5314
5364
  }));
5315
5365
  }
5316
- function _sync(db, options, schema, { isInitialSync, cancelToken, justCheckIfNeeded, purpose } = {
5317
- isInitialSync: false,
5318
- }) {
5319
- var _a;
5320
- return __awaiter(this, void 0, void 0, function* () {
5366
+ function _sync(db_1, options_1, schema_1) {
5367
+ return __awaiter(this, arguments, void 0, function* (db, options, schema, { isInitialSync, cancelToken, justCheckIfNeeded, purpose } = {
5368
+ isInitialSync: false,
5369
+ }) {
5370
+ var _a;
5321
5371
  if (!justCheckIfNeeded) {
5322
5372
  console.debug('SYNC STARTED', { isInitialSync, purpose });
5323
5373
  }
@@ -5359,8 +5409,8 @@ function _sync(db, options, schema, { isInitialSync, cancelToken, justCheckIfNee
5359
5409
  const [clientChangeSet, syncState, baseRevs, { yMessages, lastUpdateIds }] = yield db.transaction('r', db.tables, () => __awaiter(this, void 0, void 0, function* () {
5360
5410
  const syncState = yield db.getPersistedSyncState();
5361
5411
  const baseRevs = yield db.$baseRevs.toArray();
5362
- let clientChanges = yield listClientChanges(mutationTables);
5363
- const yResults = yield listYClientMessagesAndStateVector(db);
5412
+ let clientChanges = yield listClientChanges(mutationTables, db);
5413
+ const yResults = yield listYClientMessagesAndStateVector(db, tablesToSync);
5364
5414
  throwIfCancelled(cancelToken);
5365
5415
  if (doSyncify) {
5366
5416
  const alreadySyncedRealms = [
@@ -5490,11 +5540,14 @@ function _sync(db, options, schema, { isInitialSync, cancelToken, justCheckIfNee
5490
5540
  //
5491
5541
  // apply yMessages
5492
5542
  //
5493
- const receivedUntils = yield applyYServerMessages(res.yMessages, db);
5543
+ const { receivedUntils, resyncNeeded } = yield applyYServerMessages(res.yMessages, db);
5494
5544
  //
5495
5545
  // update Y SyncStates
5496
5546
  //
5497
5547
  yield updateYSyncStates(lastUpdateIds, receivedUntils, db, res.serverRevision);
5548
+ if (resyncNeeded) {
5549
+ newSyncState.yDownloadedRealms = {}; // Will trigger a full download of Y-documents below...
5550
+ }
5498
5551
  }
5499
5552
  //
5500
5553
  // Update regular syncState
@@ -5628,8 +5681,8 @@ function MessagesFromServerConsumer(db) {
5628
5681
  event.next(null);
5629
5682
  }
5630
5683
  function consumeQueue() {
5631
- var _a, _b, _c, _d, _e, _f;
5632
5684
  return __awaiter(this, void 0, void 0, function* () {
5685
+ var _a, _b, _c, _d, _e, _f;
5633
5686
  while (queue.length > 0) {
5634
5687
  const msg = queue.shift();
5635
5688
  try {
@@ -6285,8 +6338,7 @@ function createMutationTrackingMiddleware({ currentUserObservable, db, }) {
6285
6338
  outstandingTransactions.next(outstandingTransactions.value);
6286
6339
  };
6287
6340
  const txComplete = () => {
6288
- if (tx.mutationsAdded &&
6289
- !isEagerSyncDisabled(db)) {
6341
+ if (tx.mutationsAdded && !isEagerSyncDisabled(db)) {
6290
6342
  triggerSync(db, 'push');
6291
6343
  }
6292
6344
  removeTransaction();
@@ -6368,26 +6420,107 @@ function createMutationTrackingMiddleware({ currentUserObservable, db, }) {
6368
6420
  : mutateAndLog(req);
6369
6421
  } }));
6370
6422
  function mutateAndLog(req) {
6423
+ var _a, _b;
6371
6424
  const trans = req.trans;
6372
- trans.mutationsAdded = true;
6425
+ const unsyncedProps = (_b = (_a = db.cloud.options) === null || _a === void 0 ? void 0 : _a.unsyncedProperties) === null || _b === void 0 ? void 0 : _b[tableName];
6373
6426
  const { txid, currentUser: { userId }, } = trans;
6374
6427
  const { type } = req;
6375
6428
  const opNo = ++trans.opCount;
6429
+ function stripChangeSpec(changeSpec) {
6430
+ if (!unsyncedProps)
6431
+ return changeSpec;
6432
+ let rv = changeSpec;
6433
+ for (const keyPath of Object.keys(changeSpec)) {
6434
+ if (unsyncedProps.some((p) => keyPath === p || keyPath.startsWith(p + '.'))) {
6435
+ if (rv === changeSpec)
6436
+ rv = Object.assign({}, changeSpec); // clone on demand
6437
+ delete rv[keyPath];
6438
+ }
6439
+ }
6440
+ return rv;
6441
+ }
6376
6442
  return table.mutate(req).then((res) => {
6443
+ var _a;
6377
6444
  const { numFailures: hasFailures, failures } = res;
6378
6445
  let keys = type === 'delete' ? req.keys : res.results;
6379
6446
  let values = 'values' in req ? req.values : [];
6380
- let updates = 'updates' in req && req.updates;
6447
+ let changeSpec = 'changeSpec' in req ? req.changeSpec : undefined;
6448
+ let updates = 'updates' in req ? req.updates : undefined;
6381
6449
  if (hasFailures) {
6382
6450
  keys = keys.filter((_, idx) => !failures[idx]);
6383
6451
  values = values.filter((_, idx) => !failures[idx]);
6384
6452
  }
6453
+ if (unsyncedProps) {
6454
+ // Filter out unsynced properties
6455
+ values = values.map((value) => {
6456
+ const newValue = Object.assign({}, value);
6457
+ for (const prop of unsyncedProps) {
6458
+ delete newValue[prop];
6459
+ }
6460
+ return newValue;
6461
+ });
6462
+ if (changeSpec) {
6463
+ // modify operation with criteria and changeSpec.
6464
+ // We must strip out unsynced properties from changeSpec.
6465
+ // We deal with criteria later.
6466
+ changeSpec = stripChangeSpec(changeSpec);
6467
+ if (Object.keys(changeSpec).length === 0) {
6468
+ // Nothing to change on server
6469
+ return res;
6470
+ }
6471
+ }
6472
+ if (updates) {
6473
+ let strippedChangeSpecs = updates.changeSpecs.map(stripChangeSpec);
6474
+ let newUpdates = {
6475
+ keys: [],
6476
+ changeSpecs: [],
6477
+ };
6478
+ const validKeys = new RangeSet();
6479
+ let anyChangeSpecBecameEmpty = false;
6480
+ for (let i = 0, l = strippedChangeSpecs.length; i < l; ++i) {
6481
+ if (Object.keys(strippedChangeSpecs[i]).length > 0) {
6482
+ newUpdates.keys.push(updates.keys[i]);
6483
+ newUpdates.changeSpecs.push(strippedChangeSpecs[i]);
6484
+ validKeys.addKey(updates.keys[i]);
6485
+ }
6486
+ else {
6487
+ anyChangeSpecBecameEmpty = true;
6488
+ }
6489
+ }
6490
+ updates = newUpdates;
6491
+ if (anyChangeSpecBecameEmpty) {
6492
+ // Some keys were stripped. We must also strip them from keys and values
6493
+ let newKeys = [];
6494
+ let newValues = [];
6495
+ for (let i = 0, l = keys.length; i < l; ++i) {
6496
+ if (validKeys.hasKey(keys[i])) {
6497
+ newKeys.push(keys[i]);
6498
+ newValues.push(values[i]);
6499
+ }
6500
+ }
6501
+ keys = newKeys;
6502
+ values = newValues;
6503
+ }
6504
+ }
6505
+ }
6385
6506
  const ts = Date.now();
6386
6507
  // Canonicalize req.criteria.index to null if it's on the primary key.
6387
- const criteria = 'criteria' in req && req.criteria
6508
+ let criteria = 'criteria' in req && req.criteria
6388
6509
  ? 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
6510
  ? null // This will disable the server from trying to log consistent operations where it shouldnt.
6390
6511
  : req.criteria.index }) : undefined;
6512
+ if (unsyncedProps && (criteria === null || criteria === void 0 ? void 0 : criteria.index)) {
6513
+ const keyPaths = (_a = schema.indexes.find((idx) => idx.name === criteria.index)) === null || _a === void 0 ? void 0 : _a.keyPath;
6514
+ const involvedProps = keyPaths
6515
+ ? typeof keyPaths === 'string'
6516
+ ? [keyPaths]
6517
+ : keyPaths
6518
+ : [];
6519
+ if (involvedProps.some((p) => unsyncedProps === null || unsyncedProps === void 0 ? void 0 : unsyncedProps.includes(p))) {
6520
+ // Don't log criteria on unsynced properties as the server could not test them.
6521
+ criteria = undefined;
6522
+ }
6523
+ }
6391
6524
  const mut = req.type === 'delete'
6392
6525
  ? {
6393
6526
  type: 'delete',
@@ -6408,7 +6541,7 @@ function createMutationTrackingMiddleware({ currentUserObservable, db, }) {
6408
6541
  userId,
6409
6542
  values,
6410
6543
  }
6411
- : criteria && req.changeSpec
6544
+ : criteria && changeSpec
6412
6545
  ? {
6413
6546
  // Common changeSpec for all keys
6414
6547
  type: 'modify',
@@ -6416,37 +6549,51 @@ function createMutationTrackingMiddleware({ currentUserObservable, db, }) {
6416
6549
  opNo,
6417
6550
  keys,
6418
6551
  criteria,
6419
- changeSpec: req.changeSpec,
6552
+ changeSpec,
6420
6553
  txid,
6421
6554
  userId,
6422
6555
  }
6423
- : updates
6556
+ : changeSpec
6424
6557
  ? {
6425
- // One changeSpec per key
6558
+ // In case criteria involved an unsynced property, we go for keys instead.
6426
6559
  type: 'update',
6427
6560
  ts,
6428
6561
  opNo,
6429
- keys: updates.keys,
6430
- changeSpecs: updates.changeSpecs,
6431
- txid,
6432
- userId,
6433
- }
6434
- : {
6435
- type: 'upsert',
6436
- ts,
6437
- opNo,
6438
6562
  keys,
6439
- values,
6563
+ changeSpecs: keys.map(() => changeSpec),
6440
6564
  txid,
6441
6565
  userId,
6442
- };
6566
+ }
6567
+ : updates
6568
+ ? {
6569
+ // One changeSpec per key
6570
+ type: 'update',
6571
+ ts,
6572
+ opNo,
6573
+ keys: updates.keys,
6574
+ changeSpecs: updates.changeSpecs,
6575
+ txid,
6576
+ userId,
6577
+ }
6578
+ : {
6579
+ type: 'upsert',
6580
+ ts,
6581
+ opNo,
6582
+ keys,
6583
+ values,
6584
+ txid,
6585
+ userId,
6586
+ };
6443
6587
  if ('isAdditionalChunk' in req && req.isAdditionalChunk) {
6444
6588
  mut.isAdditionalChunk = true;
6445
6589
  }
6446
6590
  return keys.length > 0 || criteria
6447
6591
  ? mutsTable
6448
6592
  .mutate({ type: 'add', trans, values: [mut] }) // Log entry
6449
- .then(() => res) // Return original response
6593
+ .then(() => {
6594
+ trans.mutationsAdded = true; // Mark transaction as having added mutations to trigger eager sync
6595
+ return res; // Return original response
6596
+ })
6450
6597
  : res;
6451
6598
  });
6452
6599
  }
@@ -6613,38 +6760,51 @@ class TokenExpiredError extends Error {
6613
6760
 
6614
6761
  function createYClientUpdateObservable(db) {
6615
6762
  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; })
6763
+ .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
6764
  .map((table) => table.schema.yProps.map((p) => ({
6618
6765
  table: table.name,
6619
6766
  ydocProp: p.prop,
6620
6767
  updatesTable: p.updatesTable,
6621
6768
  }))));
6622
6769
  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
- });
6770
+ // Per updates table (table+prop combo), we first read syncer.unsentFrom,
6771
+ // and then start listening for updates since that number.
6772
+ const yTbl = db.table(updatesTable);
6773
+ return from$1(yTbl.get(DEXIE_CLOUD_SYNCER_ID)).pipe(switchMap$1((syncer) => {
6774
+ let currentUnsentFrom = (syncer === null || syncer === void 0 ? void 0 : syncer.unsentFrom) || 1;
6775
+ return from$1(liveQuery(() => __awaiter(this, void 0, void 0, function* () {
6776
+ const addedUpdates = yield listUpdatesSince(yTbl, currentUnsentFrom);
6777
+ return addedUpdates
6778
+ .filter((update) => update.f && update.f & 1) // Only include local updates
6779
+ .map((update) => {
6780
+ return {
6781
+ type: 'u-c',
6782
+ table,
6783
+ prop: ydocProp,
6784
+ k: update.k,
6785
+ u: update.u,
6786
+ i: update.i,
6787
+ };
6788
+ });
6789
+ }))).pipe(tap$1((addedUpdates) => {
6790
+ // Update currentUnsentFrom to only listen for updates that will be newer than the ones we emitted.
6791
+ // (Before, we did this within the liveQuery, but that caused a bug because
6792
+ // a cancelled emittion of a liveQuery would update the currentUnsentFrom without
6793
+ // emitting anything, leading to that we jumped over some updates. Here we update it
6794
+ // after the liveQuery has emitted its updates)
6795
+ if (addedUpdates.length > 0) {
6796
+ currentUnsentFrom = addedUpdates.at(-1).i + 1;
6797
+ }
6798
+ }));
6646
6799
  }));
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.
6800
+ })).pipe(
6801
+ // Flatten the array of messages.
6802
+ // If messageProducer emits empty array, nothing is emitted
6803
+ // but if messageProducer emits array of messages, they are
6804
+ // emitted one by one.
6805
+ mergeMap$1((messages) => messages), tap$1((message) => {
6806
+ console.debug('dexie-cloud emitting y-c', message);
6807
+ }));
6648
6808
  }
6649
6809
 
6650
6810
  function getAwarenessLibrary(db) {
@@ -6661,25 +6821,24 @@ const SERVER_PING_TIMEOUT = 20000;
6661
6821
  const CLIENT_PING_INTERVAL = 30000;
6662
6822
  const FAIL_RETRY_WAIT_TIME = 60000;
6663
6823
  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));
6824
+ constructor(db, rev, realmSetHash, clientIdentity, messageProducer, webSocketStatus, user) {
6825
+ super((subscriber) => new WSConnection(db, rev, realmSetHash, clientIdentity, user, subscriber, messageProducer, webSocketStatus));
6666
6826
  }
6667
6827
  }
6668
6828
  let counter = 0;
6669
6829
  class WSConnection extends Subscription$1 {
6670
- constructor(db, rev, realmSetHash, clientIdentity, token, tokenExpiration, subscriber, messageProducer, webSocketStatus) {
6830
+ constructor(db, rev, realmSetHash, clientIdentity, user, subscriber, messageProducer, webSocketStatus) {
6671
6831
  super(() => this.teardown());
6672
6832
  this.id = ++counter;
6673
6833
  this.subscriptions = new Set();
6674
6834
  this.reconnecting = false;
6675
- console.debug('New WebSocket Connection', this.id, token ? 'authorized' : 'unauthorized');
6835
+ console.debug('New WebSocket Connection', this.id, user.accessToken ? 'authorized' : 'unauthorized');
6676
6836
  this.db = db;
6677
6837
  this.databaseUrl = db.cloud.options.databaseUrl;
6678
6838
  this.rev = rev;
6679
6839
  this.realmSetHash = realmSetHash;
6680
6840
  this.clientIdentity = clientIdentity;
6681
- this.token = token;
6682
- this.tokenExpiration = tokenExpiration;
6841
+ this.user = user;
6683
6842
  this.subscriber = subscriber;
6684
6843
  this.lastUserActivity = new Date();
6685
6844
  this.messageProducer = messageProducer;
@@ -6739,7 +6898,8 @@ class WSConnection extends Subscription$1 {
6739
6898
  //console.debug('SyncStatus: DUBB: Ooops it was closed!');
6740
6899
  return;
6741
6900
  }
6742
- if (this.tokenExpiration && this.tokenExpiration < new Date()) {
6901
+ const tokenExpiration = this.user.accessTokenExpiration;
6902
+ if (tokenExpiration && tokenExpiration < new Date()) {
6743
6903
  this.subscriber.error(new TokenExpiredError()); // Will be handled in connectWebSocket.ts.
6744
6904
  return;
6745
6905
  }
@@ -6794,8 +6954,8 @@ class WSConnection extends Subscription$1 {
6794
6954
  searchParams.set('rev', this.rev);
6795
6955
  searchParams.set('realmsHash', this.realmSetHash);
6796
6956
  searchParams.set('clientId', this.clientIdentity);
6797
- if (this.token) {
6798
- searchParams.set('token', this.token);
6957
+ if (this.user.accessToken) {
6958
+ searchParams.set('token', this.user.accessToken);
6799
6959
  }
6800
6960
  // Connect the WebSocket to given url:
6801
6961
  console.debug('dexie-cloud WebSocket create');
@@ -6810,12 +6970,12 @@ class WSConnection extends Subscription$1 {
6810
6970
  ws.onmessage = (event) => {
6811
6971
  if (!this.pinger)
6812
6972
  return;
6813
- console.debug('dexie-cloud WebSocket onmessage', event.data);
6814
6973
  this.lastServerActivity = new Date();
6815
6974
  try {
6816
6975
  const msg = typeof event.data === 'string'
6817
6976
  ? TSON.parse(event.data)
6818
6977
  : decodeYMessage(new Uint8Array(event.data));
6978
+ console.debug('dexie-cloud WebSocket onmessage', msg.type, msg);
6819
6979
  if (msg.type === 'error') {
6820
6980
  throw new Error(`Error message from dexie-cloud: ${msg.error}`);
6821
6981
  }
@@ -6836,6 +6996,10 @@ class WSConnection extends Subscription$1 {
6836
6996
  else if (msg.type === 'u-ack' || msg.type === 'u-reject' || msg.type === 'u-s' || msg.type === 'in-sync') {
6837
6997
  applyYServerMessages([msg], this.db);
6838
6998
  }
6999
+ else if (msg.type === 'outdated-server-rev') {
7000
+ // Won't happen but need this for typing.
7001
+ throw new Error('Outdated server revision not expected over WebSocket - only in sync using fetch()');
7002
+ }
6839
7003
  else if (msg.type !== 'pong') {
6840
7004
  this.subscriber.next(msg);
6841
7005
  }
@@ -6871,17 +7035,27 @@ class WSConnection extends Subscription$1 {
6871
7035
  this.webSocketStatus.value !== 'connected') {
6872
7036
  this.webSocketStatus.next('connected');
6873
7037
  }
7038
+ console.debug('dexie-cloud WebSocket send', msg.type, msg);
6874
7039
  if (msg.type === 'ready') {
6875
7040
  (_a = this.ws) === null || _a === void 0 ? void 0 : _a.send(TSON.stringify(msg));
6876
7041
  }
6877
7042
  else {
6878
7043
  // If it's not a "ready" message, it's an YMessage.
6879
7044
  // YMessages can be sent binary encoded.
7045
+ if (msg.type === 'u-c') {
7046
+ console.log("u-c:B", ++gotClientUpdateB);
7047
+ }
6880
7048
  (_b = this.ws) === null || _b === void 0 ? void 0 : _b.send(encodeYMessage(msg));
6881
7049
  }
6882
7050
  }
6883
7051
  }));
6884
- this.subscriptions.add(createYClientUpdateObservable(this.db).subscribe(this.db.messageProducer));
7052
+ if (this.user.isLoggedIn && !isEagerSyncDisabled(this.db)) {
7053
+ this.subscriptions.add(createYClientUpdateObservable(this.db).pipe(tap$1((msg) => {
7054
+ if (msg.type === 'u-c') {
7055
+ console.log("u-c:A", ++gotClientUpdateA, msg.i);
7056
+ }
7057
+ })).subscribe(this.db.messageProducer));
7058
+ }
6885
7059
  }
6886
7060
  catch (error) {
6887
7061
  this.pauseUntil = new Date(Date.now() + FAIL_RETRY_WAIT_TIME);
@@ -6889,6 +7063,8 @@ class WSConnection extends Subscription$1 {
6889
7063
  });
6890
7064
  }
6891
7065
  }
7066
+ let gotClientUpdateA = 0;
7067
+ let gotClientUpdateB = 0;
6892
7068
 
6893
7069
  class InvalidLicenseError extends Error {
6894
7070
  constructor(license) {
@@ -6951,7 +7127,7 @@ function connectWebSocket(db) {
6951
7127
  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
7128
  }
6953
7129
  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]) => {
7130
+ }), 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
7131
  var _a;
6956
7132
  if (!((_a = db.cloud.persistedSyncState) === null || _a === void 0 ? void 0 : _a.value)) {
6957
7133
  // Restart the flow if persistedSyncState is not yet available.
@@ -6961,7 +7137,7 @@ function connectWebSocket(db) {
6961
7137
  // If no new entries, server won't bother the client. If new entries, server sends only those
6962
7138
  // and the baseRev of the last from same client-ID.
6963
7139
  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);
7140
+ return new WSObservable(db, db.cloud.persistedSyncState.value.serverRevision, realmSetHash, db.cloud.persistedSyncState.value.clientIdentity, messageProducer, db.cloud.webSocketStatus, userLogin);
6965
7141
  }
6966
7142
  else {
6967
7143
  return from$1([]);
@@ -7012,8 +7188,8 @@ function connectWebSocket(db) {
7012
7188
  }
7013
7189
 
7014
7190
  function isSyncNeeded(db) {
7015
- var _a;
7016
7191
  return __awaiter(this, void 0, void 0, function* () {
7192
+ var _a;
7017
7193
  return ((_a = db.cloud.options) === null || _a === void 0 ? void 0 : _a.databaseUrl) && db.cloud.schema
7018
7194
  ? yield sync(db, db.cloud.options, db.cloud.schema, { justCheckIfNeeded: true })
7019
7195
  : false;
@@ -7697,6 +7873,7 @@ class PermissionChecker {
7697
7873
  // If user can update any prop in any table in this realm, return true unless
7698
7874
  // it regards to ownership change:
7699
7875
  if (this.permissions.update === '*') {
7876
+ // @ts-ignore
7700
7877
  return props.every((prop) => prop !== 'owner');
7701
7878
  }
7702
7879
  const tablePermissions = (_b = this.permissions.update) === null || _b === void 0 ? void 0 : _b[this.tableName];
@@ -7768,125 +7945,139 @@ const getInvitesObservable = associate((db) => {
7768
7945
  });
7769
7946
 
7770
7947
  function createYHandler(db) {
7771
- const awap = getAwarenessLibrary(db);
7772
7948
  return (provider) => {
7773
7949
  var _a;
7774
7950
  const doc = provider.doc;
7775
- const { parentTable, parentId, parentProp, updatesTable } = doc.meta;
7951
+ const { parentTable } = doc.meta || {};
7776
7952
  if (!((_a = db.cloud.schema) === null || _a === void 0 ? void 0 : _a[parentTable].markedForSync)) {
7777
7953
  return; // The table that holds the doc is not marked for sync - leave it to dexie. No syncing, no awareness.
7778
7954
  }
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);
7955
+ let awareness;
7956
+ Object.defineProperty(provider, "awareness", {
7957
+ get() {
7958
+ if (awareness)
7959
+ return awareness;
7960
+ awareness = createAwareness(db, doc, provider);
7961
+ awarenessWeakMap.set(doc, awareness);
7962
+ return awareness;
7963
+ }
7964
+ });
7965
+ };
7966
+ }
7967
+ function createAwareness(db, doc, provider) {
7968
+ const { parentTable, parentId, parentProp, updatesTable } = doc.meta;
7969
+ const awap = getAwarenessLibrary(db);
7970
+ const awareness = new awap.Awareness(doc);
7971
+ awareness.on('update', ({ added, updated, removed }, origin) => {
7972
+ // Send the update
7973
+ const changedClients = added.concat(updated).concat(removed);
7974
+ const user = db.cloud.currentUser.value;
7975
+ if (origin !== 'server' && user.isLoggedIn && !isEagerSyncDisabled(db)) {
7976
+ const update = awap.encodeAwarenessUpdate(awareness, changedClients);
7977
+ db.messageProducer.next({
7978
+ type: 'aware',
7979
+ table: parentTable,
7980
+ prop: parentProp,
7981
+ k: doc.meta.parentId,
7982
+ u: update,
7983
+ });
7984
+ if (provider.destroyed) {
7985
+ // We're called from awareness.on('destroy') that did
7986
+ // removeAwarenessStates.
7987
+ // It's time to also send the doc-close message that dexie-cloud understands
7988
+ // and uses to stop subscribing for updates and awareness updates and brings
7989
+ // down the cached information in memory on the WS connection for this.
7787
7990
  db.messageProducer.next({
7788
- type: 'aware',
7991
+ type: 'doc-close',
7789
7992
  table: parentTable,
7790
7993
  prop: parentProp,
7791
- k: doc.meta.parentId,
7792
- u: update,
7994
+ k: doc.meta.parentId
7793
7995
  });
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
7996
  }
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* () {
7997
+ }
7998
+ });
7999
+ awareness.on('destroy', () => {
8000
+ // Signal to server that this provider is destroyed (the update event will be triggered, which
8001
+ // in turn will trigger db.messageProducer that will send the message to the server if WS is connected)
8002
+ awap.removeAwarenessStates(awareness, [doc.clientID], 'provider destroyed');
8003
+ });
8004
+ // Open the document on the server
8005
+ (() => __awaiter(this, void 0, void 0, function* () {
8006
+ if (provider.destroyed)
8007
+ return;
8008
+ let connected = false;
8009
+ let currentFlowId = 1;
8010
+ const subscription = db.cloud.webSocketStatus.subscribe((wsStatus) => {
7816
8011
  if (provider.destroyed)
7817
8012
  return;
7818
- let connected = false;
7819
- let currentFlowId = 1;
7820
- const subscription = db.cloud.webSocketStatus.subscribe((wsStatus) => {
7821
- if (provider.destroyed)
8013
+ // Keep "connected" state in a variable so we can check it after async operations
8014
+ connected = wsStatus === 'connected';
8015
+ // We are or got connected. Open the document on the server.
8016
+ const user = db.cloud.currentUser.value;
8017
+ if (wsStatus === "connected" && user.isLoggedIn && !isEagerSyncDisabled(db)) {
8018
+ ++currentFlowId;
8019
+ openDocumentOnServer().catch(error => {
8020
+ console.warn(`Error catched in createYHandler.ts: ${error}`);
8021
+ });
8022
+ }
8023
+ });
8024
+ // Wait until WebSocket is connected
8025
+ provider.addCleanupHandler(subscription);
8026
+ /** Sends an 'doc-open' message to server whenever websocket becomes
8027
+ * connected, or if it is already connected.
8028
+ * The flow is aborted in case websocket is disconnected while querying
8029
+ * information required to compute the state vector. Flow is also
8030
+ * aborted in case document or provider has been destroyed during
8031
+ * the async parts of the task.
8032
+ *
8033
+ * The state vector is only computed from the updates that have occured
8034
+ * after the last full sync - which could very often be zero - in which
8035
+ * case no state vector is sent (then the server already knows us by
8036
+ * revision)
8037
+ *
8038
+ * When server gets the doc-open message, it will authorized us for
8039
+ * whether we are allowed to read / write to this document, and then
8040
+ * keep the cached information in memory on the WS connection for this
8041
+ * particular document, as well as subscribe to updates and awareness updates
8042
+ * from other clients on the document.
8043
+ */
8044
+ function openDocumentOnServer(wsStatus) {
8045
+ return __awaiter(this, void 0, void 0, function* () {
8046
+ const myFlow = currentFlowId; // So we can abort when a new flow is started
8047
+ const yTbl = db.table(updatesTable);
8048
+ const syncState = yield yTbl.get(DEXIE_CLOUD_SYNCER_ID);
8049
+ // After every await, check if we still should be working on this task.
8050
+ if (provider.destroyed || currentFlowId !== myFlow || !connected)
7822
8051
  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
- });
8052
+ const receivedUntil = (syncState === null || syncState === void 0 ? void 0 : syncState.receivedUntil) || 0;
8053
+ const docOpenMsg = {
8054
+ type: 'doc-open',
8055
+ table: parentTable,
8056
+ prop: parentProp,
8057
+ k: parentId,
8058
+ serverRev: syncState === null || syncState === void 0 ? void 0 : syncState.serverRev,
8059
+ };
8060
+ const serverUpdatesSinceLastSync = yield yTbl
8061
+ .where('i')
8062
+ .between(receivedUntil, Infinity, false)
8063
+ .filter((update) => cmp(update.k, parentId) === 0 && // Only updates for this document
8064
+ ((update.f || 0) & 1) === 0 // Don't include local changes
8065
+ )
8066
+ .toArray();
8067
+ // After every await, check if we still should be working on this task.
8068
+ if (provider.destroyed || currentFlowId !== myFlow || !connected)
8069
+ return;
8070
+ if (serverUpdatesSinceLastSync.length > 0) {
8071
+ const Y = $Y$1(db); // Get the Yjs library from Dexie constructor options
8072
+ const mergedUpdate = Y.mergeUpdatesV2(serverUpdatesSinceLastSync.map((update) => update.u));
8073
+ const stateVector = Y.encodeStateVectorFromUpdateV2(mergedUpdate);
8074
+ docOpenMsg.sv = stateVector;
7831
8075
  }
8076
+ db.messageProducer.next(docOpenMsg);
7832
8077
  });
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
- };
8078
+ }
8079
+ }))();
8080
+ return awareness;
7890
8081
  }
7891
8082
 
7892
8083
  function getTiedRealmId(objectId) {
@@ -7936,7 +8127,7 @@ function dexieCloud(dexie) {
7936
8127
  const syncComplete = new Subject();
7937
8128
  dexie.cloud = {
7938
8129
  // @ts-ignore
7939
- version: "4.1.0-alpha.2",
8130
+ version: "4.1.0-alpha.21",
7940
8131
  options: Object.assign({}, DEFAULT_OPTIONS),
7941
8132
  schema: null,
7942
8133
  get currentUserId() {
@@ -7972,16 +8163,16 @@ function dexieCloud(dexie) {
7972
8163
  }
7973
8164
  updateSchemaFromOptions(dexie.cloud.schema, dexie.cloud.options);
7974
8165
  },
7975
- logout({ force } = {}) {
7976
- return __awaiter(this, void 0, void 0, function* () {
8166
+ logout() {
8167
+ return __awaiter(this, arguments, void 0, function* ({ force } = {}) {
7977
8168
  force
7978
8169
  ? yield _logout(DexieCloudDB(dexie), { deleteUnsyncedData: true })
7979
8170
  : yield logout(DexieCloudDB(dexie));
7980
8171
  });
7981
8172
  },
7982
- sync({ wait, purpose } = { wait: true, purpose: 'push' }) {
7983
- var _a;
7984
- return __awaiter(this, void 0, void 0, function* () {
8173
+ sync() {
8174
+ return __awaiter(this, arguments, void 0, function* ({ wait, purpose } = { wait: true, purpose: 'push' }) {
8175
+ var _a;
7985
8176
  if (wait === undefined)
7986
8177
  wait = true;
7987
8178
  const db = DexieCloudDB(dexie);
@@ -8039,8 +8230,8 @@ function dexieCloud(dexie) {
8039
8230
  dexie.use(createImplicitPropSetterMiddleware(DexieCloudDB(dexie)));
8040
8231
  dexie.use(createIdGenerationMiddleware(DexieCloudDB(dexie)));
8041
8232
  function onDbReady(dexie) {
8042
- var _a, _b, _c, _d, _e, _f, _g;
8043
8233
  return __awaiter(this, void 0, void 0, function* () {
8234
+ var _a, _b, _c, _d, _e, _f, _g;
8044
8235
  closed = false; // As Dexie calls us, we are not closed anymore. Maybe reopened? Remember db.ready event is registered with sticky flag!
8045
8236
  const db = DexieCloudDB(dexie);
8046
8237
  // Setup default GUI:
@@ -8063,7 +8254,7 @@ function dexieCloud(dexie) {
8063
8254
  ? yield navigator.serviceWorker.getRegistrations()
8064
8255
  : [];
8065
8256
  const [initiallySynced, lastSyncedRealms] = yield db.transaction('rw', db.$syncState, () => __awaiter(this, void 0, void 0, function* () {
8066
- var _h, _j;
8257
+ var _a, _b;
8067
8258
  const { options, schema } = db.cloud;
8068
8259
  const [persistedOptions, persistedSchema, persistedSyncState] = yield Promise.all([
8069
8260
  db.getOptions(),
@@ -8085,7 +8276,7 @@ function dexieCloud(dexie) {
8085
8276
  delete newPersistedOptions.awarenessProtocol;
8086
8277
  yield db.$syncState.put(newPersistedOptions, 'options');
8087
8278
  }
8088
- if (((_h = db.cloud.options) === null || _h === void 0 ? void 0 : _h.tryUseServiceWorker) &&
8279
+ if (((_a = db.cloud.options) === null || _a === void 0 ? void 0 : _a.tryUseServiceWorker) &&
8089
8280
  'serviceWorker' in navigator &&
8090
8281
  swRegistrations.length > 0 &&
8091
8282
  !DISABLE_SERVICEWORKER_STRATEGY) {
@@ -8099,7 +8290,7 @@ function dexieCloud(dexie) {
8099
8290
  // Not configured for using service worker or no service worker
8100
8291
  // registration exists. Don't rely on service worker to do any job.
8101
8292
  // Use LocalSyncWorker instead.
8102
- if (((_j = db.cloud.options) === null || _j === void 0 ? void 0 : _j.tryUseServiceWorker) &&
8293
+ if (((_b = db.cloud.options) === null || _b === void 0 ? void 0 : _b.tryUseServiceWorker) &&
8103
8294
  !db.cloud.isServiceWorkerDB) {
8104
8295
  console.debug('dexie-cloud-addon: Not using service worker.', swRegistrations.length === 0
8105
8296
  ? 'No SW registrations found.'
@@ -8238,8 +8429,164 @@ function dexieCloud(dexie) {
8238
8429
  }
8239
8430
  }
8240
8431
  // @ts-ignore
8241
- dexieCloud.version = "4.1.0-alpha.2";
8432
+ dexieCloud.version = "4.1.0-alpha.21";
8242
8433
  Dexie.Cloud = dexieCloud;
8243
8434
 
8244
- export { dexieCloud as default, dexieCloud, getTiedObjectId, getTiedRealmId, resolveText };
8435
+ const ydocTriggers = {};
8436
+ const docIsAlreadyHooked = new WeakSet();
8437
+ const middlewares = new WeakMap();
8438
+ const createMiddleware = (db) => ({
8439
+ stack: 'dbcore',
8440
+ level: 10,
8441
+ name: 'yTriggerMiddleware',
8442
+ create: (down) => {
8443
+ return Object.assign(Object.assign({}, down), { transaction: (stores, mode, options) => {
8444
+ const idbtrans = down.transaction(stores, mode, options);
8445
+ idbtrans.addEventListener('complete', onTransactionCommitted);
8446
+ return idbtrans;
8447
+ }, table: (tblName) => {
8448
+ const coreTable = down.table(tblName);
8449
+ const triggerSpec = ydocTriggers[tblName];
8450
+ if (!triggerSpec)
8451
+ return coreTable;
8452
+ const { trigger, parentTable, prop } = triggerSpec;
8453
+ return Object.assign(Object.assign({}, coreTable), { mutate(req) {
8454
+ switch (req.type) {
8455
+ case 'add': {
8456
+ for (const yUpdateRow of req.values) {
8457
+ if (yUpdateRow.k == undefined)
8458
+ continue; // A syncer or garbage collection state does not point to a key
8459
+ const primaryKey = yUpdateRow.k;
8460
+ const doc = DexieYProvider.getDocCache(db).find(parentTable, primaryKey, prop);
8461
+ if (doc) {
8462
+ if (!docIsAlreadyHooked.has(doc)) {
8463
+ hookToDoc(doc, primaryKey, trigger);
8464
+ docIsAlreadyHooked.add(doc);
8465
+ }
8466
+ }
8467
+ else {
8468
+ enqueueTrigger(db, tblName, primaryKey, trigger);
8469
+ }
8470
+ }
8471
+ break;
8472
+ }
8473
+ case 'delete':
8474
+ // @ts-ignore
8475
+ if (req.trans._rejecting_y_ypdate) {
8476
+ // The deletion came from a rejection, not garbage collection.
8477
+ // When that happens, let the triggers run to compute new values
8478
+ // based on the deleted updates.
8479
+ coreTable
8480
+ .getMany({
8481
+ keys: req.keys,
8482
+ trans: req.trans,
8483
+ cache: 'immutable',
8484
+ })
8485
+ .then((updates) => {
8486
+ const keySet = new RangeSet();
8487
+ for (const { k } of updates) {
8488
+ if (k != undefined)
8489
+ keySet.addKey(k);
8490
+ }
8491
+ for (const interval of keySet) {
8492
+ enqueueTrigger(db, tblName, interval.from, trigger);
8493
+ }
8494
+ });
8495
+ }
8496
+ break;
8497
+ }
8498
+ return coreTable.mutate(req);
8499
+ } });
8500
+ } });
8501
+ },
8502
+ });
8503
+ let triggerExecPromise = null;
8504
+ let triggerScheduled = false;
8505
+ let scheduledTriggers = [];
8506
+ function $Y(db) {
8507
+ const $Y = db._options.Y;
8508
+ if (!$Y)
8509
+ throw new Error('Y library not supplied to Dexie constructor');
8510
+ return $Y;
8511
+ }
8512
+ function executeTriggers(triggersToRun) {
8513
+ return __awaiter(this, void 0, void 0, function* () {
8514
+ for (const { db, parentId, trigger, updatesTable } of triggersToRun) {
8515
+ // Load entire document into an Y.Doc instance:
8516
+ const updates = yield db
8517
+ .table(updatesTable)
8518
+ .where({ k: parentId })
8519
+ .toArray();
8520
+ const Y = $Y(db);
8521
+ const yDoc = new Y.Doc();
8522
+ for (const update of updates) {
8523
+ Y.applyUpdateV2(yDoc, update.u);
8524
+ }
8525
+ try {
8526
+ yield trigger(yDoc, parentId);
8527
+ }
8528
+ catch (error) {
8529
+ console.error(`Error in YDocTrigger ${error}`);
8530
+ }
8531
+ }
8532
+ });
8533
+ }
8534
+ function enqueueTrigger(db, updatesTable, parentId, trigger) {
8535
+ scheduledTriggers.push({
8536
+ db,
8537
+ updatesTable,
8538
+ parentId,
8539
+ trigger,
8540
+ });
8541
+ }
8542
+ function onTransactionCommitted() {
8543
+ return __awaiter(this, void 0, void 0, function* () {
8544
+ if (!triggerScheduled && scheduledTriggers.length > 0) {
8545
+ triggerScheduled = true;
8546
+ if (triggerExecPromise)
8547
+ yield triggerExecPromise.catch(() => { });
8548
+ setTimeout(() => {
8549
+ // setTimeout() is to escape from Promise.PSD zones and never run within liveQueries or transaction scopes
8550
+ triggerScheduled = false;
8551
+ const triggersToRun = scheduledTriggers;
8552
+ scheduledTriggers = [];
8553
+ triggerExecPromise = executeTriggers(triggersToRun).finally(() => (triggerExecPromise = null));
8554
+ }, 0);
8555
+ }
8556
+ });
8557
+ }
8558
+ function hookToDoc(doc, parentId, trigger) {
8559
+ // From now on, keep listening to doc updates and execute the trigger when it happens there instead
8560
+ doc.on('updateV2', (update, origin) => {
8561
+ //Dexie.ignoreTransaction(()=>{
8562
+ trigger(doc, parentId);
8563
+ //});
8564
+ });
8565
+ /*
8566
+ NOT NEEDED because DexieYProvider's docCache will also listen to destroy and remove it from its cache:
8567
+ doc.on('destroy', ()=>{
8568
+ docIsAlreadyHooked.delete(doc);
8569
+ })
8570
+ */
8571
+ }
8572
+ function defineYDocTrigger(table, prop, trigger) {
8573
+ var _a, _b;
8574
+ const updatesTable = (_b = (_a = table.schema.yProps) === null || _a === void 0 ? void 0 : _a.find((p) => p.prop === prop)) === null || _b === void 0 ? void 0 : _b.updatesTable;
8575
+ if (!updatesTable)
8576
+ throw new Error(`Table ${table.name} does not have a Yjs property named ${prop}`);
8577
+ ydocTriggers[updatesTable] = {
8578
+ trigger,
8579
+ parentTable: table.name,
8580
+ prop,
8581
+ };
8582
+ const db = table.db._novip;
8583
+ let mw = middlewares.get(db);
8584
+ if (!mw) {
8585
+ mw = createMiddleware(db);
8586
+ middlewares.set(db, mw);
8587
+ }
8588
+ db.use(mw);
8589
+ }
8590
+
8591
+ export { dexieCloud as default, defineYDocTrigger, dexieCloud, getTiedObjectId, getTiedRealmId, resolveText };
8245
8592
  //# sourceMappingURL=dexie-cloud-addon.js.map