dexie-cloud-addon 4.1.0-alpha.8 → 4.1.0-beta.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/dist/modern/DexieCloudOptions.d.ts +3 -0
  2. package/dist/modern/TSON.d.ts +1 -1
  3. package/dist/modern/db/entities/PersistedSyncState.d.ts +1 -0
  4. package/dist/modern/define-ydoc-trigger.d.ts +2 -0
  5. package/dist/modern/dexie-cloud-addon.d.ts +1 -0
  6. package/dist/modern/dexie-cloud-addon.js +622 -257
  7. package/dist/modern/dexie-cloud-addon.js.map +1 -1
  8. package/dist/modern/dexie-cloud-addon.min.js +1 -1
  9. package/dist/modern/dexie-cloud-addon.min.js.map +1 -1
  10. package/dist/modern/getInvitesObservable.d.ts +11 -11
  11. package/dist/modern/service-worker.js +464 -255
  12. package/dist/modern/service-worker.js.map +1 -1
  13. package/dist/modern/service-worker.min.js +1 -1
  14. package/dist/modern/service-worker.min.js.map +1 -1
  15. package/dist/modern/yjs/YDexieCloudSyncState.d.ts +0 -1
  16. package/dist/modern/yjs/applyYMessages.d.ts +5 -1
  17. package/dist/modern/yjs/reopenDocSignal.d.ts +10 -0
  18. package/dist/modern/yjs/updateYSyncStates.d.ts +1 -1
  19. package/dist/umd/DexieCloudOptions.d.ts +3 -0
  20. package/dist/umd/TSON.d.ts +1 -1
  21. package/dist/umd/db/entities/PersistedSyncState.d.ts +1 -0
  22. package/dist/umd/define-ydoc-trigger.d.ts +2 -0
  23. package/dist/umd/dexie-cloud-addon.d.ts +1 -0
  24. package/dist/umd/dexie-cloud-addon.js +620 -254
  25. package/dist/umd/dexie-cloud-addon.js.map +1 -1
  26. package/dist/umd/dexie-cloud-addon.min.js +1 -1
  27. package/dist/umd/dexie-cloud-addon.min.js.map +1 -1
  28. package/dist/umd/getInvitesObservable.d.ts +11 -11
  29. package/dist/umd/service-worker.js +462 -253
  30. package/dist/umd/service-worker.js.map +1 -1
  31. package/dist/umd/service-worker.min.js +1 -1
  32. package/dist/umd/service-worker.min.js.map +1 -1
  33. package/dist/umd/yjs/YDexieCloudSyncState.d.ts +0 -1
  34. package/dist/umd/yjs/applyYMessages.d.ts +5 -1
  35. package/dist/umd/yjs/reopenDocSignal.d.ts +10 -0
  36. package/dist/umd/yjs/updateYSyncStates.d.ts +1 -1
  37. package/package.json +6 -5
@@ -8,7 +8,7 @@
8
8
  *
9
9
  * ==========================================================================
10
10
  *
11
- * Version 4.1.0-alpha.8, Tue Oct 15 2024
11
+ * Version 4.1.0-beta.25, Wed Dec 04 2024
12
12
  *
13
13
  * https://dexie.org
14
14
  *
@@ -16,8 +16,8 @@
16
16
  *
17
17
  */
18
18
 
19
- import Dexie, { PropModification, cmp, DexieYProvider, liveQuery } from 'dexie';
20
- import { 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, startWith as startWith$1 } from 'rxjs';
21
21
 
22
22
  /******************************************************************************
23
23
  Copyright (c) Microsoft Corporation.
@@ -979,13 +979,20 @@ 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;
993
+ case 'y-complete-sync-done':
994
+ writeVarString(encoder, msg.yServerRev);
995
+ break;
989
996
  default:
990
997
  writeAny(encoder, msg.k);
991
998
  switch (msg.type) {
@@ -1329,6 +1336,12 @@ const readAny = decoder => readAnyLookupTable[127 - readUint8(decoder)](decoder)
1329
1336
  function decodeYMessage(a) {
1330
1337
  const decoder = new Decoder(a);
1331
1338
  const type = readVarString(decoder);
1339
+ if (type === 'outdated-server-rev') {
1340
+ return { type };
1341
+ }
1342
+ if (type === 'y-complete-sync-done') {
1343
+ return { type, yServerRev: readVarString(decoder) };
1344
+ }
1332
1345
  const table = readVarString(decoder);
1333
1346
  const prop = readVarString(decoder);
1334
1347
  switch (type) {
@@ -2987,8 +3000,8 @@ function registerSyncEvent(db, purpose) {
2987
3000
  });
2988
3001
  }
2989
3002
  function registerPeriodicSyncEvent(db) {
2990
- var _a;
2991
3003
  return __awaiter(this, void 0, void 0, function* () {
3004
+ var _a;
2992
3005
  try {
2993
3006
  // Register periodicSync event to SW:
2994
3007
  // @ts-ignore
@@ -3199,8 +3212,8 @@ function confirmLogout(userInteraction, currentUserId, numUnsyncedChanges) {
3199
3212
  }
3200
3213
 
3201
3214
  function loadAccessToken(db) {
3202
- var _a, _b, _c;
3203
3215
  return __awaiter(this, void 0, void 0, function* () {
3216
+ var _a, _b, _c;
3204
3217
  const currentUser = yield db.getCurrentUser();
3205
3218
  const { accessToken, accessTokenExpiration, refreshToken, refreshTokenExpiration, claims, } = currentUser;
3206
3219
  if (!accessToken)
@@ -3414,10 +3427,10 @@ function formatAsPem(str) {
3414
3427
  }
3415
3428
 
3416
3429
  // Emulate true-private property db. Why? So it's not stored in DB.
3417
- const wm$1 = new WeakMap();
3430
+ const wm$2 = new WeakMap();
3418
3431
  class AuthPersistedContext {
3419
3432
  constructor(db, userLogin) {
3420
- wm$1.set(this, db);
3433
+ wm$2.set(this, db);
3421
3434
  Object.assign(this, userLogin);
3422
3435
  }
3423
3436
  static load(db, userId) {
@@ -3434,7 +3447,7 @@ class AuthPersistedContext {
3434
3447
  }
3435
3448
  save() {
3436
3449
  return __awaiter(this, void 0, void 0, function* () {
3437
- const db = wm$1.get(this);
3450
+ const db = wm$2.get(this);
3438
3451
  db.table("$logins").put(this);
3439
3452
  });
3440
3453
  }
@@ -3472,8 +3485,8 @@ function logout(db) {
3472
3485
  }
3473
3486
  });
3474
3487
  }
3475
- function _logout(db, { deleteUnsyncedData = false } = {}) {
3476
- return __awaiter(this, void 0, void 0, function* () {
3488
+ function _logout(db_1) {
3489
+ return __awaiter(this, arguments, void 0, function* (db, { deleteUnsyncedData = false } = {}) {
3477
3490
  // Clear the database without emptying configuration options.
3478
3491
  const [numUnsynced, loggedOut] = yield db.dx.transaction('rw', db.dx.tables, (tx) => __awaiter(this, void 0, void 0, function* () {
3479
3492
  // @ts-ignore
@@ -3521,11 +3534,11 @@ class HttpError extends Error {
3521
3534
 
3522
3535
  function otpFetchTokenCallback(db) {
3523
3536
  const { userInteraction } = db.cloud;
3524
- return function otpAuthenticate({ public_key, hints }) {
3525
- var _a;
3526
- return __awaiter(this, void 0, void 0, function* () {
3537
+ return function otpAuthenticate(_a) {
3538
+ return __awaiter(this, arguments, void 0, function* ({ public_key, hints }) {
3539
+ var _b;
3527
3540
  let tokenRequest;
3528
- const url = (_a = db.cloud.options) === null || _a === void 0 ? void 0 : _a.databaseUrl;
3541
+ const url = (_b = db.cloud.options) === null || _b === void 0 ? void 0 : _b.databaseUrl;
3529
3542
  if (!url)
3530
3543
  throw new Error(`No database URL given.`);
3531
3544
  if ((hints === null || hints === void 0 ? void 0 : hints.grant_type) === 'demo') {
@@ -3691,8 +3704,8 @@ function setCurrentUser(db, user) {
3691
3704
  }
3692
3705
 
3693
3706
  function login(db, hints) {
3694
- var _a;
3695
3707
  return __awaiter(this, void 0, void 0, function* () {
3708
+ var _a;
3696
3709
  const currentUser = yield db.getCurrentUser();
3697
3710
  const origUserId = currentUser.userId;
3698
3711
  if (currentUser.isLoggedIn && (!hints || (!hints.email && !hints.userId))) {
@@ -3880,8 +3893,8 @@ class BroadcastedAndLocalEvent extends Observable$1 {
3880
3893
  }
3881
3894
  }
3882
3895
 
3883
- function computeRealmSetHash({ realms, inviteRealms, }) {
3884
- return __awaiter(this, void 0, void 0, function* () {
3896
+ function computeRealmSetHash(_a) {
3897
+ return __awaiter(this, arguments, void 0, function* ({ realms, inviteRealms, }) {
3885
3898
  const data = JSON.stringify([
3886
3899
  ...realms.map((realmId) => ({ realmId, accepted: true })),
3887
3900
  ...inviteRealms.map((realmId) => ({ realmId, accepted: false })),
@@ -3917,8 +3930,8 @@ function flatten(a) {
3917
3930
  return concat.apply([], a);
3918
3931
  }
3919
3932
 
3920
- function listClientChanges(mutationTables, db, { since = {}, limit = Infinity } = {}) {
3921
- return __awaiter(this, void 0, void 0, function* () {
3933
+ function listClientChanges(mutationTables_1, db_1) {
3934
+ return __awaiter(this, arguments, void 0, function* (mutationTables, db, { since = {}, limit = Infinity } = {}) {
3922
3935
  const allMutsOnTables = yield Promise.all(mutationTables.map((mutationTable) => __awaiter(this, void 0, void 0, function* () {
3923
3936
  const tableName = getTableFromMutationTable(mutationTable.name);
3924
3937
  const lastRevision = since[tableName];
@@ -4653,8 +4666,8 @@ function cloneChange(change, rewriteValues) {
4653
4666
  // seconds (given that there is a Ratelimit-Reset header).
4654
4667
  let syncRatelimitDelays = new WeakMap();
4655
4668
  function checkSyncRateLimitDelay(db) {
4656
- var _a, _b;
4657
4669
  return __awaiter(this, void 0, void 0, function* () {
4670
+ var _a, _b;
4658
4671
  const delatMilliseconds = ((_b = (_a = syncRatelimitDelays.get(db)) === null || _a === void 0 ? void 0 : _a.getTime()) !== null && _b !== void 0 ? _b : 0) - Date.now();
4659
4672
  if (delatMilliseconds > 0) {
4660
4673
  console.debug(`Stalling sync request ${delatMilliseconds} ms to spare ratelimits`);
@@ -4715,6 +4728,7 @@ function syncWithServer(changes, y, syncState, baseRevs, db, databaseUrl, schema
4715
4728
  lastPull: syncState
4716
4729
  ? {
4717
4730
  serverRevision: syncState.serverRevision,
4731
+ yServerRevision: syncState.yServerRevision,
4718
4732
  realms: syncState.realms,
4719
4733
  inviteRealms: syncState.inviteRealms,
4720
4734
  }
@@ -4944,7 +4958,7 @@ function listUpdatesSince(yTable, sinceIncluding) {
4944
4958
  .toArray();
4945
4959
  }
4946
4960
 
4947
- function $Y(db) {
4961
+ function $Y$1(db) {
4948
4962
  const $Y = db.dx._options.Y;
4949
4963
  if (!$Y)
4950
4964
  throw new Error('Y library not supplied to Dexie constructor');
@@ -4974,7 +4988,7 @@ function listYClientMessagesAndStateVector(db, tablesToSync) {
4974
4988
  for (const table of tablesToSync) {
4975
4989
  if (table.schema.yProps) {
4976
4990
  for (const yProp of table.schema.yProps) {
4977
- const Y = $Y(db); // This is how we retrieve the user-provided Y library
4991
+ const Y = $Y$1(db); // This is how we retrieve the user-provided Y library
4978
4992
  const yTable = db.table(yProp.updatesTable); // the updates-table for this combo of table+propName
4979
4993
  const syncState = (yield yTable.get(DEXIE_CLOUD_SYNCER_ID));
4980
4994
  // unsentFrom = the `i` value of updates that aren't yet sent to server (or at least not acked by the server yet)
@@ -5059,12 +5073,15 @@ function getUpdatesTable(db, table, ydocProp) {
5059
5073
 
5060
5074
  function applyYServerMessages(yMessages, db) {
5061
5075
  return __awaiter(this, void 0, void 0, function* () {
5062
- const result = {};
5076
+ var _a;
5077
+ const receivedUntils = {};
5078
+ let resyncNeeded = false;
5079
+ let yServerRevision;
5063
5080
  for (const m of yMessages) {
5064
5081
  switch (m.type) {
5065
5082
  case 'u-s': {
5066
5083
  const utbl = getUpdatesTable(db, m.table, m.prop);
5067
- result[utbl.name] = yield utbl.add({
5084
+ receivedUntils[utbl.name] = yield utbl.add({
5068
5085
  k: m.k,
5069
5086
  u: m.u,
5070
5087
  });
@@ -5089,7 +5106,24 @@ function applyYServerMessages(yMessages, db) {
5089
5106
  // See my question in https://discuss.yjs.dev/t/generate-an-inverse-update/2765
5090
5107
  console.debug(`Y update rejected. Deleting it.`);
5091
5108
  const utbl = getUpdatesTable(db, m.table, m.prop);
5092
- yield utbl.delete(m.i);
5109
+ // Delete the rejected update and all local updates since (avoid holes in the CRDT)
5110
+ // and destroy it's open document if there is one.
5111
+ const primaryKey = (_a = (yield utbl.get(m.i))) === null || _a === void 0 ? void 0 : _a.k;
5112
+ if (primaryKey != null) {
5113
+ yield db.transaction('rw', utbl, (tx) => {
5114
+ // @ts-ignore
5115
+ tx.idbtrans._rejecting_y_ypdate = true; // Inform ydoc triggers that we delete because of a rejection and not GC
5116
+ return utbl
5117
+ .where('i')
5118
+ .aboveOrEqual(m.i)
5119
+ .filter((u) => cmp(u.k, primaryKey) === 0 && ((u.f || 0) & 1) === 1)
5120
+ .delete();
5121
+ });
5122
+ // Destroy active doc
5123
+ const activeDoc = DexieYProvider.getDocCache(db.dx).find(m.table, primaryKey, m.prop);
5124
+ if (activeDoc)
5125
+ activeDoc.destroy(); // Destroy the document so that editors don't continue to work on it
5126
+ }
5093
5127
  break;
5094
5128
  }
5095
5129
  case 'in-sync': {
@@ -5099,15 +5133,26 @@ function applyYServerMessages(yMessages, db) {
5099
5133
  }
5100
5134
  break;
5101
5135
  }
5136
+ case 'y-complete-sync-done': {
5137
+ yServerRevision = m.yServerRev;
5138
+ break;
5139
+ }
5140
+ case 'outdated-server-rev':
5141
+ resyncNeeded = true;
5142
+ break;
5102
5143
  }
5103
5144
  }
5104
- return result;
5145
+ return {
5146
+ receivedUntils,
5147
+ resyncNeeded,
5148
+ yServerRevision
5149
+ };
5105
5150
  });
5106
5151
  }
5107
5152
 
5108
- function updateYSyncStates(lastUpdateIdsBeforeSync, receivedUntilsAfterSync, db, serverRevision) {
5109
- var _a, _b, _c, _d, _e;
5153
+ function updateYSyncStates(lastUpdateIdsBeforeSync, receivedUntilsAfterSync, db) {
5110
5154
  return __awaiter(this, void 0, void 0, function* () {
5155
+ var _a, _b, _c, _d, _e;
5111
5156
  // We want to update unsentFrom for each yTable to the value specified in first argument
5112
5157
  // because we got those values before we synced with server and here we are back from server
5113
5158
  // that has successfully received all those messages - no matter if the last update was a client or server update,
@@ -5154,14 +5199,12 @@ function updateYSyncStates(lastUpdateIdsBeforeSync, receivedUntilsAfterSync, db,
5154
5199
  yield db.table(yTable).add({
5155
5200
  i: DEXIE_CLOUD_SYNCER_ID,
5156
5201
  unsentFrom,
5157
- receivedUntil,
5158
- serverRev: serverRevision,
5202
+ receivedUntil
5159
5203
  });
5160
5204
  }
5161
5205
  else {
5162
5206
  state.unsentFrom = Math.max(unsentFrom, state.unsentFrom || 1);
5163
5207
  state.receivedUntil = Math.max(receivedUntil, state.receivedUntil || 0);
5164
- state.serverRev = serverRevision;
5165
5208
  yield db.table(yTable).put(state);
5166
5209
  }
5167
5210
  }));
@@ -5172,8 +5215,8 @@ function updateYSyncStates(lastUpdateIdsBeforeSync, receivedUntilsAfterSync, db,
5172
5215
  const BINSTREAM_TYPE_REALMID = 1;
5173
5216
  const BINSTREAM_TYPE_TABLE_AND_PROP = 2;
5174
5217
  const BINSTREAM_TYPE_DOCUMENT = 3;
5175
- function downloadYDocsFromServer(db, databaseUrl, { yDownloadedRealms, realms }) {
5176
- return __awaiter(this, void 0, void 0, function* () {
5218
+ function downloadYDocsFromServer(db_1, databaseUrl_1, _a) {
5219
+ return __awaiter(this, arguments, void 0, function* (db, databaseUrl, { yDownloadedRealms, realms }) {
5177
5220
  if (yDownloadedRealms &&
5178
5221
  realms &&
5179
5222
  realms.every((realmId) => yDownloadedRealms[realmId] === '*')) {
@@ -5288,8 +5331,7 @@ function downloadYDocsFromServer(db, databaseUrl, { yDownloadedRealms, realms })
5288
5331
 
5289
5332
  const CURRENT_SYNC_WORKER = 'currentSyncWorker';
5290
5333
  function sync(db, options, schema, syncOptions) {
5291
- return _sync
5292
- .apply(this, arguments)
5334
+ return _sync(db, options, schema, syncOptions)
5293
5335
  .then((result) => {
5294
5336
  if (!(syncOptions === null || syncOptions === void 0 ? void 0 : syncOptions.justCheckIfNeeded)) { // && syncOptions?.purpose !== 'push') {
5295
5337
  db.syncStateChangedEvent.next({
@@ -5331,11 +5373,11 @@ function sync(db, options, schema, syncOptions) {
5331
5373
  return Promise.reject(error);
5332
5374
  }));
5333
5375
  }
5334
- function _sync(db, options, schema, { isInitialSync, cancelToken, justCheckIfNeeded, purpose } = {
5335
- isInitialSync: false,
5336
- }) {
5337
- var _a;
5338
- return __awaiter(this, void 0, void 0, function* () {
5376
+ function _sync(db_1, options_1, schema_1) {
5377
+ return __awaiter(this, arguments, void 0, function* (db, options, schema, { isInitialSync, cancelToken, justCheckIfNeeded, purpose } = {
5378
+ isInitialSync: false,
5379
+ }) {
5380
+ var _a;
5339
5381
  if (!justCheckIfNeeded) {
5340
5382
  console.debug('SYNC STARTED', { isInitialSync, purpose });
5341
5383
  }
@@ -5352,7 +5394,7 @@ function _sync(db, options, schema, { isInitialSync, cancelToken, justCheckIfNee
5352
5394
  // Prepare for syncification by modifying locally unauthorized objects:
5353
5395
  //
5354
5396
  const persistedSyncState = yield db.getPersistedSyncState();
5355
- const readyForSyncification = !isInitialSync && currentUser.isLoggedIn;
5397
+ const readyForSyncification = currentUser.isLoggedIn;
5356
5398
  const tablesToSyncify = readyForSyncification
5357
5399
  ? getTablesToSyncify(db, persistedSyncState)
5358
5400
  : [];
@@ -5377,7 +5419,7 @@ function _sync(db, options, schema, { isInitialSync, cancelToken, justCheckIfNee
5377
5419
  const [clientChangeSet, syncState, baseRevs, { yMessages, lastUpdateIds }] = yield db.transaction('r', db.tables, () => __awaiter(this, void 0, void 0, function* () {
5378
5420
  const syncState = yield db.getPersistedSyncState();
5379
5421
  const baseRevs = yield db.$baseRevs.toArray();
5380
- let clientChanges = yield listClientChanges(mutationTables);
5422
+ let clientChanges = yield listClientChanges(mutationTables, db);
5381
5423
  const yResults = yield listYClientMessagesAndStateVector(db, tablesToSync);
5382
5424
  throwIfCancelled(cancelToken);
5383
5425
  if (doSyncify) {
@@ -5497,6 +5539,7 @@ function _sync(db, options, schema, { isInitialSync, cancelToken, justCheckIfNee
5497
5539
  newSyncState.realms = res.realms;
5498
5540
  newSyncState.inviteRealms = res.inviteRealms;
5499
5541
  newSyncState.serverRevision = res.serverRevision;
5542
+ newSyncState.yServerRevision = res.serverRevision;
5500
5543
  newSyncState.timestamp = new Date();
5501
5544
  delete newSyncState.error;
5502
5545
  const filteredChanges = filterServerChangesThroughAddedClientChanges(res.changes, addedClientChanges);
@@ -5508,11 +5551,17 @@ function _sync(db, options, schema, { isInitialSync, cancelToken, justCheckIfNee
5508
5551
  //
5509
5552
  // apply yMessages
5510
5553
  //
5511
- const receivedUntils = yield applyYServerMessages(res.yMessages, db);
5554
+ const { receivedUntils, resyncNeeded, yServerRevision } = yield applyYServerMessages(res.yMessages, db);
5555
+ if (yServerRevision) {
5556
+ newSyncState.yServerRevision = yServerRevision;
5557
+ }
5512
5558
  //
5513
5559
  // update Y SyncStates
5514
5560
  //
5515
- yield updateYSyncStates(lastUpdateIds, receivedUntils, db, res.serverRevision);
5561
+ yield updateYSyncStates(lastUpdateIds, receivedUntils, db);
5562
+ if (resyncNeeded) {
5563
+ newSyncState.yDownloadedRealms = {}; // Will trigger a full download of Y-documents below...
5564
+ }
5516
5565
  }
5517
5566
  //
5518
5567
  // Update regular syncState
@@ -5646,8 +5695,8 @@ function MessagesFromServerConsumer(db) {
5646
5695
  event.next(null);
5647
5696
  }
5648
5697
  function consumeQueue() {
5649
- var _a, _b, _c, _d, _e, _f;
5650
5698
  return __awaiter(this, void 0, void 0, function* () {
5699
+ var _a, _b, _c, _d, _e, _f;
5651
5700
  while (queue.length > 0) {
5652
5701
  const msg = queue.shift();
5653
5702
  try {
@@ -5805,7 +5854,7 @@ function MessagesFromServerConsumer(db) {
5805
5854
  };
5806
5855
  }
5807
5856
 
5808
- const wm = new WeakMap();
5857
+ const wm$1 = new WeakMap();
5809
5858
  const DEXIE_CLOUD_SCHEMA = {
5810
5859
  members: '@id, [userId+realmId], [email+realmId], realmId',
5811
5860
  roles: '[realmId+name]',
@@ -5819,7 +5868,7 @@ let static_counter = 0;
5819
5868
  function DexieCloudDB(dx) {
5820
5869
  if ('vip' in dx)
5821
5870
  dx = dx['vip']; // Avoid race condition. Always map to a vipped dexie that don't block during db.on.ready().
5822
- let db = wm.get(dx.cloud);
5871
+ let db = wm$1.get(dx.cloud);
5823
5872
  if (!db) {
5824
5873
  const localSyncEvent = new Subject();
5825
5874
  let syncStateChangedEvent = new BroadcastedAndLocalEvent(`syncstatechanged-${dx.name}`);
@@ -5907,7 +5956,7 @@ function DexieCloudDB(dx) {
5907
5956
  Object.assign(db, helperMethods);
5908
5957
  db.messageConsumer = MessagesFromServerConsumer(db);
5909
5958
  db.messageProducer = new Subject();
5910
- wm.set(dx.cloud, db);
5959
+ wm$1.set(dx.cloud, db);
5911
5960
  }
5912
5961
  return db;
5913
5962
  }
@@ -6303,8 +6352,7 @@ function createMutationTrackingMiddleware({ currentUserObservable, db, }) {
6303
6352
  outstandingTransactions.next(outstandingTransactions.value);
6304
6353
  };
6305
6354
  const txComplete = () => {
6306
- if (tx.mutationsAdded &&
6307
- !isEagerSyncDisabled(db)) {
6355
+ if (tx.mutationsAdded && !isEagerSyncDisabled(db)) {
6308
6356
  triggerSync(db, 'push');
6309
6357
  }
6310
6358
  removeTransaction();
@@ -6386,26 +6434,107 @@ function createMutationTrackingMiddleware({ currentUserObservable, db, }) {
6386
6434
  : mutateAndLog(req);
6387
6435
  } }));
6388
6436
  function mutateAndLog(req) {
6437
+ var _a, _b;
6389
6438
  const trans = req.trans;
6390
- trans.mutationsAdded = true;
6439
+ const unsyncedProps = (_b = (_a = db.cloud.options) === null || _a === void 0 ? void 0 : _a.unsyncedProperties) === null || _b === void 0 ? void 0 : _b[tableName];
6391
6440
  const { txid, currentUser: { userId }, } = trans;
6392
6441
  const { type } = req;
6393
6442
  const opNo = ++trans.opCount;
6443
+ function stripChangeSpec(changeSpec) {
6444
+ if (!unsyncedProps)
6445
+ return changeSpec;
6446
+ let rv = changeSpec;
6447
+ for (const keyPath of Object.keys(changeSpec)) {
6448
+ if (unsyncedProps.some((p) => keyPath === p || keyPath.startsWith(p + '.'))) {
6449
+ if (rv === changeSpec)
6450
+ rv = Object.assign({}, changeSpec); // clone on demand
6451
+ delete rv[keyPath];
6452
+ }
6453
+ }
6454
+ return rv;
6455
+ }
6394
6456
  return table.mutate(req).then((res) => {
6457
+ var _a;
6395
6458
  const { numFailures: hasFailures, failures } = res;
6396
6459
  let keys = type === 'delete' ? req.keys : res.results;
6397
6460
  let values = 'values' in req ? req.values : [];
6398
- let updates = 'updates' in req && req.updates;
6461
+ let changeSpec = 'changeSpec' in req ? req.changeSpec : undefined;
6462
+ let updates = 'updates' in req ? req.updates : undefined;
6399
6463
  if (hasFailures) {
6400
6464
  keys = keys.filter((_, idx) => !failures[idx]);
6401
6465
  values = values.filter((_, idx) => !failures[idx]);
6402
6466
  }
6467
+ if (unsyncedProps) {
6468
+ // Filter out unsynced properties
6469
+ values = values.map((value) => {
6470
+ const newValue = Object.assign({}, value);
6471
+ for (const prop of unsyncedProps) {
6472
+ delete newValue[prop];
6473
+ }
6474
+ return newValue;
6475
+ });
6476
+ if (changeSpec) {
6477
+ // modify operation with criteria and changeSpec.
6478
+ // We must strip out unsynced properties from changeSpec.
6479
+ // We deal with criteria later.
6480
+ changeSpec = stripChangeSpec(changeSpec);
6481
+ if (Object.keys(changeSpec).length === 0) {
6482
+ // Nothing to change on server
6483
+ return res;
6484
+ }
6485
+ }
6486
+ if (updates) {
6487
+ let strippedChangeSpecs = updates.changeSpecs.map(stripChangeSpec);
6488
+ let newUpdates = {
6489
+ keys: [],
6490
+ changeSpecs: [],
6491
+ };
6492
+ const validKeys = new RangeSet();
6493
+ let anyChangeSpecBecameEmpty = false;
6494
+ for (let i = 0, l = strippedChangeSpecs.length; i < l; ++i) {
6495
+ if (Object.keys(strippedChangeSpecs[i]).length > 0) {
6496
+ newUpdates.keys.push(updates.keys[i]);
6497
+ newUpdates.changeSpecs.push(strippedChangeSpecs[i]);
6498
+ validKeys.addKey(updates.keys[i]);
6499
+ }
6500
+ else {
6501
+ anyChangeSpecBecameEmpty = true;
6502
+ }
6503
+ }
6504
+ updates = newUpdates;
6505
+ if (anyChangeSpecBecameEmpty) {
6506
+ // Some keys were stripped. We must also strip them from keys and values
6507
+ let newKeys = [];
6508
+ let newValues = [];
6509
+ for (let i = 0, l = keys.length; i < l; ++i) {
6510
+ if (validKeys.hasKey(keys[i])) {
6511
+ newKeys.push(keys[i]);
6512
+ newValues.push(values[i]);
6513
+ }
6514
+ }
6515
+ keys = newKeys;
6516
+ values = newValues;
6517
+ }
6518
+ }
6519
+ }
6403
6520
  const ts = Date.now();
6404
6521
  // Canonicalize req.criteria.index to null if it's on the primary key.
6405
- const criteria = 'criteria' in req && req.criteria
6522
+ let criteria = 'criteria' in req && req.criteria
6406
6523
  ? Object.assign(Object.assign({}, req.criteria), { index: req.criteria.index === schema.primaryKey.keyPath // Use null to inform server that criteria is on primary key
6407
6524
  ? null // This will disable the server from trying to log consistent operations where it shouldnt.
6408
6525
  : req.criteria.index }) : undefined;
6526
+ if (unsyncedProps && (criteria === null || criteria === void 0 ? void 0 : criteria.index)) {
6527
+ const keyPaths = (_a = schema.indexes.find((idx) => idx.name === criteria.index)) === null || _a === void 0 ? void 0 : _a.keyPath;
6528
+ const involvedProps = keyPaths
6529
+ ? typeof keyPaths === 'string'
6530
+ ? [keyPaths]
6531
+ : keyPaths
6532
+ : [];
6533
+ if (involvedProps.some((p) => unsyncedProps === null || unsyncedProps === void 0 ? void 0 : unsyncedProps.includes(p))) {
6534
+ // Don't log criteria on unsynced properties as the server could not test them.
6535
+ criteria = undefined;
6536
+ }
6537
+ }
6409
6538
  const mut = req.type === 'delete'
6410
6539
  ? {
6411
6540
  type: 'delete',
@@ -6426,7 +6555,7 @@ function createMutationTrackingMiddleware({ currentUserObservable, db, }) {
6426
6555
  userId,
6427
6556
  values,
6428
6557
  }
6429
- : criteria && req.changeSpec
6558
+ : criteria && changeSpec
6430
6559
  ? {
6431
6560
  // Common changeSpec for all keys
6432
6561
  type: 'modify',
@@ -6434,37 +6563,51 @@ function createMutationTrackingMiddleware({ currentUserObservable, db, }) {
6434
6563
  opNo,
6435
6564
  keys,
6436
6565
  criteria,
6437
- changeSpec: req.changeSpec,
6566
+ changeSpec,
6438
6567
  txid,
6439
6568
  userId,
6440
6569
  }
6441
- : updates
6570
+ : changeSpec
6442
6571
  ? {
6443
- // One changeSpec per key
6572
+ // In case criteria involved an unsynced property, we go for keys instead.
6444
6573
  type: 'update',
6445
6574
  ts,
6446
6575
  opNo,
6447
- keys: updates.keys,
6448
- changeSpecs: updates.changeSpecs,
6449
- txid,
6450
- userId,
6451
- }
6452
- : {
6453
- type: 'upsert',
6454
- ts,
6455
- opNo,
6456
6576
  keys,
6457
- values,
6577
+ changeSpecs: keys.map(() => changeSpec),
6458
6578
  txid,
6459
6579
  userId,
6460
- };
6580
+ }
6581
+ : updates
6582
+ ? {
6583
+ // One changeSpec per key
6584
+ type: 'update',
6585
+ ts,
6586
+ opNo,
6587
+ keys: updates.keys,
6588
+ changeSpecs: updates.changeSpecs,
6589
+ txid,
6590
+ userId,
6591
+ }
6592
+ : {
6593
+ type: 'upsert',
6594
+ ts,
6595
+ opNo,
6596
+ keys,
6597
+ values,
6598
+ txid,
6599
+ userId,
6600
+ };
6461
6601
  if ('isAdditionalChunk' in req && req.isAdditionalChunk) {
6462
6602
  mut.isAdditionalChunk = true;
6463
6603
  }
6464
6604
  return keys.length > 0 || criteria
6465
6605
  ? mutsTable
6466
6606
  .mutate({ type: 'add', trans, values: [mut] }) // Log entry
6467
- .then(() => res) // Return original response
6607
+ .then(() => {
6608
+ trans.mutationsAdded = true; // Mark transaction as having added mutations to trigger eager sync
6609
+ return res; // Return original response
6610
+ })
6468
6611
  : res;
6469
6612
  });
6470
6613
  }
@@ -6548,10 +6691,18 @@ function overrideParseStoresSpec(origFunc, dexie) {
6548
6691
  };
6549
6692
  }
6550
6693
 
6694
+ function performGuardedJob(db, jobName, job) {
6695
+ if (typeof navigator === 'undefined' || !navigator.locks) {
6696
+ // No support for guarding jobs. IE11, node.js, etc.
6697
+ return job();
6698
+ }
6699
+ return navigator.locks.request(db.name + '|' + jobName, () => job());
6700
+ }
6701
+
6551
6702
  function performInitialSync(db, cloudOptions, cloudSchema) {
6552
6703
  return __awaiter(this, void 0, void 0, function* () {
6553
6704
  console.debug('Performing initial sync');
6554
- yield sync(db, cloudOptions, cloudSchema, { isInitialSync: true });
6705
+ yield performGuardedJob(db, CURRENT_SYNC_WORKER, () => sync(db, cloudOptions, cloudSchema, { isInitialSync: true }));
6555
6706
  console.debug('Done initial sync');
6556
6707
  });
6557
6708
  }
@@ -6638,29 +6789,35 @@ function createYClientUpdateObservable(db) {
6638
6789
  updatesTable: p.updatesTable,
6639
6790
  }))));
6640
6791
  return merge(...yTableRecords.map(({ table, ydocProp, updatesTable }) => {
6641
- let currentUnsentFrom = 1;
6642
- return liveQuery(() => __awaiter(this, void 0, void 0, function* () {
6643
- const yTbl = db.table(updatesTable);
6644
- const unsentFrom = yield yTbl
6645
- .where({ i: DEXIE_CLOUD_SYNCER_ID })
6646
- .first()
6647
- .then((syncer) => (syncer === null || syncer === void 0 ? void 0 : syncer.unsentFrom) || 1);
6648
- currentUnsentFrom = Math.max(currentUnsentFrom, unsentFrom);
6649
- const addedUpdates = yield listUpdatesSince(yTbl, currentUnsentFrom);
6650
- // Update currentUnsentFrom to only listen for updates that will be newer than the ones we emitted.
6651
- currentUnsentFrom = Math.max(currentUnsentFrom, ...addedUpdates.map((update) => update.i + 1));
6652
- return addedUpdates
6653
- .filter((update) => update.f && update.f & 1) // Only include local updates
6654
- .map((update) => {
6655
- return {
6656
- type: 'u-c',
6657
- table,
6658
- prop: ydocProp,
6659
- k: update.k,
6660
- u: update.u,
6661
- i: update.i,
6662
- };
6663
- });
6792
+ // Per updates table (table+prop combo), we first read syncer.unsentFrom,
6793
+ // and then start listening for updates since that number.
6794
+ const yTbl = db.table(updatesTable);
6795
+ return from$1(yTbl.get(DEXIE_CLOUD_SYNCER_ID)).pipe(switchMap$1((syncer) => {
6796
+ let currentUnsentFrom = (syncer === null || syncer === void 0 ? void 0 : syncer.unsentFrom) || 1;
6797
+ return from$1(liveQuery(() => __awaiter(this, void 0, void 0, function* () {
6798
+ const addedUpdates = yield listUpdatesSince(yTbl, currentUnsentFrom);
6799
+ return addedUpdates
6800
+ .filter((update) => update.f && update.f & 1) // Only include local updates
6801
+ .map((update) => {
6802
+ return {
6803
+ type: 'u-c',
6804
+ table,
6805
+ prop: ydocProp,
6806
+ k: update.k,
6807
+ u: update.u,
6808
+ i: update.i,
6809
+ };
6810
+ });
6811
+ }))).pipe(tap$1((addedUpdates) => {
6812
+ // Update currentUnsentFrom to only listen for updates that will be newer than the ones we emitted.
6813
+ // (Before, we did this within the liveQuery, but that caused a bug because
6814
+ // a cancelled emittion of a liveQuery would update the currentUnsentFrom without
6815
+ // emitting anything, leading to that we jumped over some updates. Here we update it
6816
+ // after the liveQuery has emitted its updates)
6817
+ if (addedUpdates.length > 0) {
6818
+ currentUnsentFrom = addedUpdates.at(-1).i + 1;
6819
+ }
6820
+ }));
6664
6821
  }));
6665
6822
  })).pipe(
6666
6823
  // Flatten the array of messages.
@@ -6680,6 +6837,23 @@ function getAwarenessLibrary(db) {
6680
6837
  const awarenessWeakMap = new WeakMap();
6681
6838
  const getDocAwareness = (doc) => awarenessWeakMap.get(doc);
6682
6839
 
6840
+ const wm = new WeakMap();
6841
+ /** A property (package-private) on Y.Doc that is used
6842
+ * to signal that the server wants us to send a 'doc-open' message
6843
+ * to the server for this document.
6844
+ *
6845
+ * @param doc
6846
+ * @returns
6847
+ */
6848
+ function getOpenDocSignal(doc) {
6849
+ let signal = wm.get(doc);
6850
+ if (!signal) {
6851
+ signal = new Subject();
6852
+ wm.set(doc, signal);
6853
+ }
6854
+ return signal;
6855
+ }
6856
+
6683
6857
  const SERVER_PING_TIMEOUT = 20000;
6684
6858
  const CLIENT_PING_INTERVAL = 30000;
6685
6859
  const FAIL_RETRY_WAIT_TIME = 60000;
@@ -6842,9 +7016,6 @@ class WSConnection extends Subscription$1 {
6842
7016
  if (msg.type === 'error') {
6843
7017
  throw new Error(`Error message from dexie-cloud: ${msg.error}`);
6844
7018
  }
6845
- else if (msg.type === 'rev') {
6846
- this.rev = msg.rev; // No meaning but seems reasonable.
6847
- }
6848
7019
  else if (msg.type === 'aware') {
6849
7020
  const docCache = DexieYProvider.getDocCache(this.db.dx);
6850
7021
  const doc = docCache.find(msg.table, msg.k, msg.prop);
@@ -6859,7 +7030,19 @@ class WSConnection extends Subscription$1 {
6859
7030
  else if (msg.type === 'u-ack' || msg.type === 'u-reject' || msg.type === 'u-s' || msg.type === 'in-sync') {
6860
7031
  applyYServerMessages([msg], this.db);
6861
7032
  }
7033
+ else if (msg.type === 'doc-open') {
7034
+ const docCache = DexieYProvider.getDocCache(this.db.dx);
7035
+ const doc = docCache.find(msg.table, msg.k, msg.prop);
7036
+ if (doc) {
7037
+ getOpenDocSignal(doc).next(); // Make yHandler reopen the document on server.
7038
+ }
7039
+ }
7040
+ else if (msg.type === 'outdated-server-rev' || msg.type === 'y-complete-sync-done') {
7041
+ // Won't happen but need this for typing.
7042
+ throw new Error('Outdated server revision or y-complete-sync-done not expected over WebSocket - only in sync using fetch()');
7043
+ }
6862
7044
  else if (msg.type !== 'pong') {
7045
+ // Forward the request to our subscriber, wich is in messageFromServerQueue.ts (via connectWebSocket's subscribe() at the end!)
6863
7046
  this.subscriber.next(msg);
6864
7047
  }
6865
7048
  }
@@ -6896,6 +7079,10 @@ class WSConnection extends Subscription$1 {
6896
7079
  }
6897
7080
  console.debug('dexie-cloud WebSocket send', msg.type, msg);
6898
7081
  if (msg.type === 'ready') {
7082
+ // Ok, we are certain to have stored everything up until revision msg.rev.
7083
+ // Update this.rev in case of reconnect - remember where we were and don't just start over!
7084
+ this.rev = msg.rev;
7085
+ // ... and then send along the request to the server so it would also be updated!
6899
7086
  (_a = this.ws) === null || _a === void 0 ? void 0 : _a.send(TSON.stringify(msg));
6900
7087
  }
6901
7088
  else {
@@ -6977,7 +7164,7 @@ function connectWebSocket(db) {
6977
7164
  return db.cloud.persistedSyncState.pipe(filter((syncState) => (syncState === null || syncState === void 0 ? void 0 : syncState.realms.includes(userLogin.userId)) || false), take(1), map((syncState) => [userLogin, syncState]));
6978
7165
  }
6979
7166
  return new BehaviorSubject([userLogin, syncState]);
6980
- }), switchMap(([userLogin, syncState]) => __awaiter(this, void 0, void 0, function* () { return [userLogin, yield computeRealmSetHash(syncState)]; })), distinctUntilChanged(([prevUser, prevHash], [currUser, currHash]) => prevUser === currUser && prevHash === currHash), switchMap(([userLogin, realmSetHash]) => {
7167
+ }), switchMap((_a) => __awaiter(this, [_a], void 0, function* ([userLogin, syncState]) { return [userLogin, yield computeRealmSetHash(syncState)]; })), distinctUntilChanged(([prevUser, prevHash], [currUser, currHash]) => prevUser === currUser && prevHash === currHash), switchMap(([userLogin, realmSetHash]) => {
6981
7168
  var _a;
6982
7169
  if (!((_a = db.cloud.persistedSyncState) === null || _a === void 0 ? void 0 : _a.value)) {
6983
7170
  // Restart the flow if persistedSyncState is not yet available.
@@ -7038,22 +7225,14 @@ function connectWebSocket(db) {
7038
7225
  }
7039
7226
 
7040
7227
  function isSyncNeeded(db) {
7041
- var _a;
7042
7228
  return __awaiter(this, void 0, void 0, function* () {
7229
+ var _a;
7043
7230
  return ((_a = db.cloud.options) === null || _a === void 0 ? void 0 : _a.databaseUrl) && db.cloud.schema
7044
7231
  ? yield sync(db, db.cloud.options, db.cloud.schema, { justCheckIfNeeded: true })
7045
7232
  : false;
7046
7233
  });
7047
7234
  }
7048
7235
 
7049
- function performGuardedJob(db, jobName, job) {
7050
- if (typeof navigator === 'undefined' || !navigator.locks) {
7051
- // No support for guarding jobs. IE11, node.js, etc.
7052
- return job();
7053
- }
7054
- return navigator.locks.request(db.name + '|' + jobName, () => job());
7055
- }
7056
-
7057
7236
  const ongoingSyncs = new WeakMap();
7058
7237
  function syncIfPossible(db, cloudOptions, cloudSchema, options) {
7059
7238
  const ongoing = ongoingSyncs.get(db);
@@ -7107,20 +7286,7 @@ function syncIfPossible(db, cloudOptions, cloudSchema, options) {
7107
7286
  try {
7108
7287
  // Check if should delay sync due to ratelimit:
7109
7288
  yield checkSyncRateLimitDelay(db);
7110
- // Check if we need to lock the sync job. Not needed if we are the service worker.
7111
- if (db.cloud.isServiceWorkerDB) {
7112
- // We are the dedicated sync SW:
7113
- yield sync(db, cloudOptions, cloudSchema, options);
7114
- }
7115
- else if (!db.cloud.usingServiceWorker) {
7116
- // We use a flow that is better suited for the case when multiple workers want to
7117
- // do the same thing.
7118
- yield performGuardedJob(db, CURRENT_SYNC_WORKER, () => sync(db, cloudOptions, cloudSchema, options));
7119
- }
7120
- else {
7121
- assert(false);
7122
- throw new Error('Internal _syncIfPossible() - invalid precondition - should not have been called.');
7123
- }
7289
+ yield performGuardedJob(db, CURRENT_SYNC_WORKER, () => sync(db, cloudOptions, cloudSchema, options));
7124
7290
  ongoingSyncs.delete(db);
7125
7291
  console.debug('Done sync');
7126
7292
  }
@@ -7723,6 +7889,7 @@ class PermissionChecker {
7723
7889
  // If user can update any prop in any table in this realm, return true unless
7724
7890
  // it regards to ownership change:
7725
7891
  if (this.permissions.update === '*') {
7892
+ // @ts-ignore
7726
7893
  return props.every((prop) => prop !== 'owner');
7727
7894
  }
7728
7895
  const tablePermissions = (_b = this.permissions.update) === null || _b === void 0 ? void 0 : _b[this.tableName];
@@ -7796,125 +7963,151 @@ const getInvitesObservable = associate((db) => {
7796
7963
  function createYHandler(db) {
7797
7964
  return (provider) => {
7798
7965
  var _a;
7799
- const awap = getAwarenessLibrary(db);
7800
7966
  const doc = provider.doc;
7801
- const { parentTable, parentId, parentProp, updatesTable } = doc.meta;
7967
+ const { parentTable } = doc.meta || {};
7802
7968
  if (!((_a = db.cloud.schema) === null || _a === void 0 ? void 0 : _a[parentTable].markedForSync)) {
7803
7969
  return; // The table that holds the doc is not marked for sync - leave it to dexie. No syncing, no awareness.
7804
7970
  }
7805
- let awareness = new awap.Awareness(doc);
7806
- awarenessWeakMap.set(doc, awareness);
7807
- provider.awareness = awareness;
7808
- awareness.on('update', ({ added, updated, removed }, origin) => {
7809
- // Send the update
7810
- const changedClients = added.concat(updated).concat(removed);
7811
- const user = db.cloud.currentUser.value;
7812
- if (origin !== 'server' && user.isLoggedIn && !isEagerSyncDisabled(db)) {
7813
- const update = awap.encodeAwarenessUpdate(awareness, changedClients);
7971
+ let awareness;
7972
+ Object.defineProperty(provider, 'awareness', {
7973
+ get() {
7974
+ if (awareness)
7975
+ return awareness;
7976
+ awareness = createAwareness(db, doc, provider);
7977
+ awarenessWeakMap.set(doc, awareness);
7978
+ return awareness;
7979
+ },
7980
+ });
7981
+ };
7982
+ }
7983
+ function createAwareness(db, doc, provider) {
7984
+ const { parentTable, parentId, parentProp, updatesTable } = doc.meta;
7985
+ const awap = getAwarenessLibrary(db);
7986
+ const awareness = new awap.Awareness(doc);
7987
+ const reopenDocSignal = getOpenDocSignal(doc);
7988
+ awareness.on('update', ({ added, updated, removed }, origin) => {
7989
+ // Send the update
7990
+ const changedClients = added.concat(updated).concat(removed);
7991
+ const user = db.cloud.currentUser.value;
7992
+ if (origin !== 'server' && user.isLoggedIn && !isEagerSyncDisabled(db)) {
7993
+ const update = awap.encodeAwarenessUpdate(awareness, changedClients);
7994
+ db.messageProducer.next({
7995
+ type: 'aware',
7996
+ table: parentTable,
7997
+ prop: parentProp,
7998
+ k: doc.meta.parentId,
7999
+ u: update,
8000
+ });
8001
+ if (provider.destroyed) {
8002
+ // We're called from awareness.on('destroy') that did
8003
+ // removeAwarenessStates.
8004
+ // It's time to also send the doc-close message that dexie-cloud understands
8005
+ // and uses to stop subscribing for updates and awareness updates and brings
8006
+ // down the cached information in memory on the WS connection for this.
7814
8007
  db.messageProducer.next({
7815
- type: 'aware',
8008
+ type: 'doc-close',
7816
8009
  table: parentTable,
7817
8010
  prop: parentProp,
7818
8011
  k: doc.meta.parentId,
7819
- u: update,
7820
8012
  });
7821
- if (provider.destroyed) {
7822
- // We're called from awareness.on('destroy') that did
7823
- // removeAwarenessStates.
7824
- // It's time to also send the doc-close message that dexie-cloud understands
7825
- // and uses to stop subscribing for updates and awareness updates and brings
7826
- // down the cached information in memory on the WS connection for this.
7827
- db.messageProducer.next({
7828
- type: 'doc-close',
7829
- table: parentTable,
7830
- prop: parentProp,
7831
- k: doc.meta.parentId
7832
- });
7833
- }
7834
8013
  }
7835
- });
7836
- awareness.on('destroy', () => {
7837
- // Signal to server that this provider is destroyed (the update event will be triggered, which
7838
- // in turn will trigger db.messageProducer that will send the message to the server if WS is connected)
7839
- awap.removeAwarenessStates(awareness, [doc.clientID], 'provider destroyed');
7840
- });
7841
- // Now wait til document is loaded and then open the document on the server
7842
- provider.on('load', () => __awaiter(this, void 0, void 0, function* () {
8014
+ }
8015
+ });
8016
+ awareness.on('destroy', () => {
8017
+ // Signal to server that this provider is destroyed (the update event will be triggered, which
8018
+ // in turn will trigger db.messageProducer that will send the message to the server if WS is connected)
8019
+ awap.removeAwarenessStates(awareness, [doc.clientID], 'provider destroyed');
8020
+ });
8021
+ // Open the document on the server
8022
+ (() => __awaiter(this, void 0, void 0, function* () {
8023
+ if (provider.destroyed)
8024
+ return;
8025
+ let connected = false;
8026
+ let currentFlowId = 1;
8027
+ const subscription = combineLatest([
8028
+ db.cloud.webSocketStatus, // Wake up when webSocket status changes
8029
+ reopenDocSignal.pipe(startWith$1(null)), // Wake up when reopenDocSignal emits
8030
+ ]).subscribe(([wsStatus]) => {
7843
8031
  if (provider.destroyed)
7844
8032
  return;
7845
- let connected = false;
7846
- let currentFlowId = 1;
7847
- const subscription = db.cloud.webSocketStatus.subscribe((wsStatus) => {
7848
- if (provider.destroyed)
8033
+ // Keep "connected" state in a variable so we can check it after async operations
8034
+ connected = wsStatus === 'connected';
8035
+ // We are or got connected. Open the document on the server.
8036
+ const user = db.cloud.currentUser.value;
8037
+ if (wsStatus === 'connected' &&
8038
+ user.isLoggedIn &&
8039
+ !isEagerSyncDisabled(db)) {
8040
+ ++currentFlowId;
8041
+ openDocumentOnServer().catch((error) => {
8042
+ console.warn(`Error catched in createYHandler.ts: ${error}`);
8043
+ });
8044
+ }
8045
+ });
8046
+ // Wait until WebSocket is connected
8047
+ provider.addCleanupHandler(subscription);
8048
+ /** Sends an 'doc-open' message to server whenever websocket becomes
8049
+ * connected, or if it is already connected.
8050
+ * The flow is aborted in case websocket is disconnected while querying
8051
+ * information required to compute the state vector. Flow is also
8052
+ * aborted in case document or provider has been destroyed during
8053
+ * the async parts of the task.
8054
+ *
8055
+ * The state vector is only computed from the updates that have occured
8056
+ * after the last full sync - which could very often be zero - in which
8057
+ * case no state vector is sent (then the server already knows us by
8058
+ * revision)
8059
+ *
8060
+ * When server gets the doc-open message, it will authorize us for
8061
+ * whether we are allowed to read / write to this document, and then
8062
+ * keep the cached information in memory on the WS connection for this
8063
+ * particular document, as well as subscribe to updates and awareness updates
8064
+ * from other clients on the document.
8065
+ */
8066
+ function openDocumentOnServer() {
8067
+ return __awaiter(this, void 0, void 0, function* () {
8068
+ const myFlow = currentFlowId; // So we can abort when a new flow is started
8069
+ const yTbl = db.table(updatesTable);
8070
+ const syncStateTbl = db.$syncState;
8071
+ const [receivedUntil, yServerRev] = yield db.transaction('r', syncStateTbl, yTbl, () => __awaiter(this, void 0, void 0, function* () {
8072
+ const syncState = yield yTbl.get(DEXIE_CLOUD_SYNCER_ID);
8073
+ const persistedSyncState = yield syncStateTbl.get('syncState');
8074
+ return [
8075
+ (syncState === null || syncState === void 0 ? void 0 : syncState.receivedUntil) || 0,
8076
+ (persistedSyncState === null || persistedSyncState === void 0 ? void 0 : persistedSyncState.yServerRevision) ||
8077
+ (persistedSyncState === null || persistedSyncState === void 0 ? void 0 : persistedSyncState.serverRevision),
8078
+ ];
8079
+ }));
8080
+ // After every await, check if we still should be working on this task.
8081
+ if (provider.destroyed || currentFlowId !== myFlow || !connected)
7849
8082
  return;
7850
- // Keep "connected" state in a variable so we can check it after async operations
7851
- connected = wsStatus === 'connected';
7852
- // We are or got connected. Open the document on the server.
7853
- const user = db.cloud.currentUser.value;
7854
- if (wsStatus === "connected" && user.isLoggedIn && !isEagerSyncDisabled(db)) {
7855
- ++currentFlowId;
7856
- openDocumentOnServer().catch(error => {
7857
- console.warn(`Error catched in createYHandler.ts: ${error}`);
7858
- });
8083
+ const docOpenMsg = {
8084
+ type: 'doc-open',
8085
+ table: parentTable,
8086
+ prop: parentProp,
8087
+ k: parentId,
8088
+ serverRev: yServerRev,
8089
+ };
8090
+ const serverUpdatesSinceLastSync = yield yTbl
8091
+ .where('i')
8092
+ .between(receivedUntil, Infinity, false)
8093
+ .filter((update) => cmp(update.k, parentId) === 0 && // Only updates for this document
8094
+ ((update.f || 0) & 1) === 0 // Don't include local changes
8095
+ )
8096
+ .toArray();
8097
+ // After every await, check if we still should be working on this task.
8098
+ if (provider.destroyed || currentFlowId !== myFlow || !connected)
8099
+ return;
8100
+ if (serverUpdatesSinceLastSync.length > 0) {
8101
+ const Y = $Y$1(db); // Get the Yjs library from Dexie constructor options
8102
+ const mergedUpdate = Y.mergeUpdatesV2(serverUpdatesSinceLastSync.map((update) => update.u));
8103
+ const stateVector = Y.encodeStateVectorFromUpdateV2(mergedUpdate);
8104
+ docOpenMsg.sv = stateVector;
7859
8105
  }
8106
+ db.messageProducer.next(docOpenMsg);
7860
8107
  });
7861
- // Wait until WebSocket is connected
7862
- provider.addCleanupHandler(subscription);
7863
- /** Sends an 'doc-open' message to server whenever websocket becomes
7864
- * connected, or if it is already connected.
7865
- * The flow is aborted in case websocket is disconnected while querying
7866
- * information required to compute the state vector. Flow is also
7867
- * aborted in case document or provider has been destroyed during
7868
- * the async parts of the task.
7869
- *
7870
- * The state vector is only computed from the updates that have occured
7871
- * after the last full sync - which could very often be zero - in which
7872
- * case no state vector is sent (then the server already knows us by
7873
- * revision)
7874
- *
7875
- * When server gets the doc-open message, it will authorized us for
7876
- * whether we are allowed to read / write to this document, and then
7877
- * keep the cached information in memory on the WS connection for this
7878
- * particular document, as well as subscribe to updates and awareness updates
7879
- * from other clients on the document.
7880
- */
7881
- function openDocumentOnServer(wsStatus) {
7882
- return __awaiter(this, void 0, void 0, function* () {
7883
- const myFlow = currentFlowId; // So we can abort when a new flow is started
7884
- const yTbl = db.table(updatesTable);
7885
- const syncState = yield yTbl.get(DEXIE_CLOUD_SYNCER_ID);
7886
- // After every await, check if we still should be working on this task.
7887
- if (provider.destroyed || currentFlowId !== myFlow || !connected)
7888
- return;
7889
- const receivedUntil = (syncState === null || syncState === void 0 ? void 0 : syncState.receivedUntil) || 0;
7890
- const docOpenMsg = {
7891
- type: 'doc-open',
7892
- table: parentTable,
7893
- prop: parentProp,
7894
- k: parentId,
7895
- serverRev: syncState === null || syncState === void 0 ? void 0 : syncState.serverRev,
7896
- };
7897
- const serverUpdatesSinceLastSync = yield yTbl
7898
- .where('i')
7899
- .between(receivedUntil, Infinity, false)
7900
- .filter((update) => cmp(update.k, parentId) === 0 && // Only updates for this document
7901
- ((update.f || 0) & 1) === 0 // Don't include local changes
7902
- )
7903
- .toArray();
7904
- // After every await, check if we still should be working on this task.
7905
- if (provider.destroyed || currentFlowId !== myFlow || !connected)
7906
- return;
7907
- if (serverUpdatesSinceLastSync.length > 0) {
7908
- const Y = $Y(db); // Get the Yjs library from Dexie constructor options
7909
- const mergedUpdate = Y.mergeUpdatesV2(serverUpdatesSinceLastSync.map((update) => update.u));
7910
- const stateVector = Y.encodeStateVectorFromUpdateV2(mergedUpdate);
7911
- docOpenMsg.sv = stateVector;
7912
- }
7913
- db.messageProducer.next(docOpenMsg);
7914
- });
7915
- }
7916
- }));
7917
- };
8108
+ }
8109
+ }))();
8110
+ return awareness;
7918
8111
  }
7919
8112
 
7920
8113
  function getTiedRealmId(objectId) {
@@ -7964,7 +8157,7 @@ function dexieCloud(dexie) {
7964
8157
  const syncComplete = new Subject();
7965
8158
  dexie.cloud = {
7966
8159
  // @ts-ignore
7967
- version: "4.1.0-alpha.8",
8160
+ version: "4.1.0-beta.25",
7968
8161
  options: Object.assign({}, DEFAULT_OPTIONS),
7969
8162
  schema: null,
7970
8163
  get currentUserId() {
@@ -8000,16 +8193,16 @@ function dexieCloud(dexie) {
8000
8193
  }
8001
8194
  updateSchemaFromOptions(dexie.cloud.schema, dexie.cloud.options);
8002
8195
  },
8003
- logout({ force } = {}) {
8004
- return __awaiter(this, void 0, void 0, function* () {
8196
+ logout() {
8197
+ return __awaiter(this, arguments, void 0, function* ({ force } = {}) {
8005
8198
  force
8006
8199
  ? yield _logout(DexieCloudDB(dexie), { deleteUnsyncedData: true })
8007
8200
  : yield logout(DexieCloudDB(dexie));
8008
8201
  });
8009
8202
  },
8010
- sync({ wait, purpose } = { wait: true, purpose: 'push' }) {
8011
- var _a;
8012
- return __awaiter(this, void 0, void 0, function* () {
8203
+ sync() {
8204
+ return __awaiter(this, arguments, void 0, function* ({ wait, purpose } = { wait: true, purpose: 'push' }) {
8205
+ var _a;
8013
8206
  if (wait === undefined)
8014
8207
  wait = true;
8015
8208
  const db = DexieCloudDB(dexie);
@@ -8067,8 +8260,8 @@ function dexieCloud(dexie) {
8067
8260
  dexie.use(createImplicitPropSetterMiddleware(DexieCloudDB(dexie)));
8068
8261
  dexie.use(createIdGenerationMiddleware(DexieCloudDB(dexie)));
8069
8262
  function onDbReady(dexie) {
8070
- var _a, _b, _c, _d, _e, _f, _g;
8071
8263
  return __awaiter(this, void 0, void 0, function* () {
8264
+ var _a, _b, _c, _d, _e, _f, _g;
8072
8265
  closed = false; // As Dexie calls us, we are not closed anymore. Maybe reopened? Remember db.ready event is registered with sticky flag!
8073
8266
  const db = DexieCloudDB(dexie);
8074
8267
  // Setup default GUI:
@@ -8091,7 +8284,7 @@ function dexieCloud(dexie) {
8091
8284
  ? yield navigator.serviceWorker.getRegistrations()
8092
8285
  : [];
8093
8286
  const [initiallySynced, lastSyncedRealms] = yield db.transaction('rw', db.$syncState, () => __awaiter(this, void 0, void 0, function* () {
8094
- var _h, _j;
8287
+ var _a, _b;
8095
8288
  const { options, schema } = db.cloud;
8096
8289
  const [persistedOptions, persistedSchema, persistedSyncState] = yield Promise.all([
8097
8290
  db.getOptions(),
@@ -8113,7 +8306,7 @@ function dexieCloud(dexie) {
8113
8306
  delete newPersistedOptions.awarenessProtocol;
8114
8307
  yield db.$syncState.put(newPersistedOptions, 'options');
8115
8308
  }
8116
- if (((_h = db.cloud.options) === null || _h === void 0 ? void 0 : _h.tryUseServiceWorker) &&
8309
+ if (((_a = db.cloud.options) === null || _a === void 0 ? void 0 : _a.tryUseServiceWorker) &&
8117
8310
  'serviceWorker' in navigator &&
8118
8311
  swRegistrations.length > 0 &&
8119
8312
  !DISABLE_SERVICEWORKER_STRATEGY) {
@@ -8127,7 +8320,7 @@ function dexieCloud(dexie) {
8127
8320
  // Not configured for using service worker or no service worker
8128
8321
  // registration exists. Don't rely on service worker to do any job.
8129
8322
  // Use LocalSyncWorker instead.
8130
- if (((_j = db.cloud.options) === null || _j === void 0 ? void 0 : _j.tryUseServiceWorker) &&
8323
+ if (((_b = db.cloud.options) === null || _b === void 0 ? void 0 : _b.tryUseServiceWorker) &&
8131
8324
  !db.cloud.isServiceWorkerDB) {
8132
8325
  console.debug('dexie-cloud-addon: Not using service worker.', swRegistrations.length === 0
8133
8326
  ? 'No SW registrations found.'
@@ -8170,10 +8363,6 @@ function dexieCloud(dexie) {
8170
8363
  db.setInitiallySynced(true);
8171
8364
  }
8172
8365
  verifySchema(db);
8173
- if (((_b = db.cloud.options) === null || _b === void 0 ? void 0 : _b.databaseUrl) && !initiallySynced) {
8174
- yield performInitialSync(db, db.cloud.options, db.cloud.schema);
8175
- db.setInitiallySynced(true);
8176
- }
8177
8366
  // Manage CurrentUser observable:
8178
8367
  throwIfClosed();
8179
8368
  if (!db.cloud.isServiceWorkerDB) {
@@ -8198,20 +8387,29 @@ function dexieCloud(dexie) {
8198
8387
  // HERE: If requireAuth, do athentication now.
8199
8388
  let changedUser = false;
8200
8389
  const user = yield db.getCurrentUser();
8201
- const requireAuth = (_c = db.cloud.options) === null || _c === void 0 ? void 0 : _c.requireAuth;
8390
+ const requireAuth = (_b = db.cloud.options) === null || _b === void 0 ? void 0 : _b.requireAuth;
8202
8391
  if (requireAuth) {
8203
- if (typeof requireAuth === 'object') {
8204
- // requireAuth contains login hints. Check if we already fulfil it:
8205
- if (!user.isLoggedIn ||
8206
- (requireAuth.userId && user.userId !== requireAuth.userId) ||
8207
- (requireAuth.email && user.email !== requireAuth.email)) {
8208
- // If not, login the configured user:
8209
- changedUser = yield login(db, requireAuth);
8210
- }
8392
+ if (db.cloud.isServiceWorkerDB) {
8393
+ // If this is a service worker DB, we can't do authentication here,
8394
+ // we just wait until the application has done it.
8395
+ console.debug('Dexie Cloud Service worker. Waiting for application to authenticate.');
8396
+ yield firstValueFrom(currentUserEmitter.pipe(filter((user) => !!user.isLoggedIn), take(1)));
8397
+ console.debug('Dexie Cloud Service worker. Application has authenticated.');
8211
8398
  }
8212
- else if (!user.isLoggedIn) {
8213
- // requireAuth is true and user is not logged in
8214
- changedUser = yield login(db);
8399
+ else {
8400
+ if (typeof requireAuth === 'object') {
8401
+ // requireAuth contains login hints. Check if we already fulfil it:
8402
+ if (!user.isLoggedIn ||
8403
+ (requireAuth.userId && user.userId !== requireAuth.userId) ||
8404
+ (requireAuth.email && user.email !== requireAuth.email)) {
8405
+ // If not, login the configured user:
8406
+ changedUser = yield login(db, requireAuth);
8407
+ }
8408
+ }
8409
+ else if (!user.isLoggedIn) {
8410
+ // requireAuth is true and user is not logged in
8411
+ changedUser = yield login(db);
8412
+ }
8215
8413
  }
8216
8414
  }
8217
8415
  if (user.isLoggedIn && (!lastSyncedRealms || !lastSyncedRealms.includes(user.userId))) {
@@ -8226,8 +8424,17 @@ function dexieCloud(dexie) {
8226
8424
  localSyncWorker.stop();
8227
8425
  localSyncWorker = null;
8228
8426
  throwIfClosed();
8427
+ const doInitialSync = ((_c = db.cloud.options) === null || _c === void 0 ? void 0 : _c.databaseUrl) && (!initiallySynced || changedUser);
8428
+ if (doInitialSync) {
8429
+ // Do the initial sync directly in the browser thread no matter if we are using service worker or not.
8430
+ yield performInitialSync(db, db.cloud.options, db.cloud.schema);
8431
+ db.setInitiallySynced(true);
8432
+ }
8433
+ throwIfClosed();
8229
8434
  if (db.cloud.usingServiceWorker && ((_d = db.cloud.options) === null || _d === void 0 ? void 0 : _d.databaseUrl)) {
8230
- registerSyncEvent(db, changedUser ? 'pull' : 'push').catch(() => { });
8435
+ if (!doInitialSync) {
8436
+ registerSyncEvent(db, 'push').catch(() => { });
8437
+ }
8231
8438
  registerPeriodicSyncEvent(db).catch(() => { });
8232
8439
  }
8233
8440
  else if (((_e = db.cloud.options) === null || _e === void 0 ? void 0 : _e.databaseUrl) &&
@@ -8236,7 +8443,9 @@ function dexieCloud(dexie) {
8236
8443
  // There's no SW. Start SyncWorker instead.
8237
8444
  localSyncWorker = LocalSyncWorker(db, db.cloud.options, db.cloud.schema);
8238
8445
  localSyncWorker.start();
8239
- triggerSync(db, changedUser ? 'pull' : 'push');
8446
+ if (!doInitialSync) {
8447
+ triggerSync(db, 'push');
8448
+ }
8240
8449
  }
8241
8450
  // Listen to online event and do sync.
8242
8451
  throwIfClosed();
@@ -8256,7 +8465,7 @@ function dexieCloud(dexie) {
8256
8465
  });
8257
8466
  }));
8258
8467
  }
8259
- // Connect WebSocket unless we
8468
+ // Connect WebSocket unless we are in a service worker or websocket is disabled.
8260
8469
  if (((_f = db.cloud.options) === null || _f === void 0 ? void 0 : _f.databaseUrl) &&
8261
8470
  !((_g = db.cloud.options) === null || _g === void 0 ? void 0 : _g.disableWebSocket) &&
8262
8471
  !IS_SERVICE_WORKER) {
@@ -8266,8 +8475,164 @@ function dexieCloud(dexie) {
8266
8475
  }
8267
8476
  }
8268
8477
  // @ts-ignore
8269
- dexieCloud.version = "4.1.0-alpha.8";
8478
+ dexieCloud.version = "4.1.0-beta.25";
8270
8479
  Dexie.Cloud = dexieCloud;
8271
8480
 
8272
- export { dexieCloud as default, dexieCloud, getTiedObjectId, getTiedRealmId, resolveText };
8481
+ const ydocTriggers = {};
8482
+ const docIsAlreadyHooked = new WeakSet();
8483
+ const middlewares = new WeakMap();
8484
+ const createMiddleware = (db) => ({
8485
+ stack: 'dbcore',
8486
+ level: 10,
8487
+ name: 'yTriggerMiddleware',
8488
+ create: (down) => {
8489
+ return Object.assign(Object.assign({}, down), { transaction: (stores, mode, options) => {
8490
+ const idbtrans = down.transaction(stores, mode, options);
8491
+ idbtrans.addEventListener('complete', onTransactionCommitted);
8492
+ return idbtrans;
8493
+ }, table: (tblName) => {
8494
+ const coreTable = down.table(tblName);
8495
+ const triggerSpec = ydocTriggers[tblName];
8496
+ if (!triggerSpec)
8497
+ return coreTable;
8498
+ const { trigger, parentTable, prop } = triggerSpec;
8499
+ return Object.assign(Object.assign({}, coreTable), { mutate(req) {
8500
+ switch (req.type) {
8501
+ case 'add': {
8502
+ for (const yUpdateRow of req.values) {
8503
+ if (yUpdateRow.k == undefined)
8504
+ continue; // A syncer or garbage collection state does not point to a key
8505
+ const primaryKey = yUpdateRow.k;
8506
+ const doc = DexieYProvider.getDocCache(db).find(parentTable, primaryKey, prop);
8507
+ if (doc) {
8508
+ if (!docIsAlreadyHooked.has(doc)) {
8509
+ hookToDoc(doc, primaryKey, trigger);
8510
+ docIsAlreadyHooked.add(doc);
8511
+ }
8512
+ }
8513
+ else {
8514
+ enqueueTrigger(db, tblName, primaryKey, trigger);
8515
+ }
8516
+ }
8517
+ break;
8518
+ }
8519
+ case 'delete':
8520
+ // @ts-ignore
8521
+ if (req.trans._rejecting_y_ypdate) {
8522
+ // The deletion came from a rejection, not garbage collection.
8523
+ // When that happens, let the triggers run to compute new values
8524
+ // based on the deleted updates.
8525
+ coreTable
8526
+ .getMany({
8527
+ keys: req.keys,
8528
+ trans: req.trans,
8529
+ cache: 'immutable',
8530
+ })
8531
+ .then((updates) => {
8532
+ const keySet = new RangeSet();
8533
+ for (const { k } of updates) {
8534
+ if (k != undefined)
8535
+ keySet.addKey(k);
8536
+ }
8537
+ for (const interval of keySet) {
8538
+ enqueueTrigger(db, tblName, interval.from, trigger);
8539
+ }
8540
+ });
8541
+ }
8542
+ break;
8543
+ }
8544
+ return coreTable.mutate(req);
8545
+ } });
8546
+ } });
8547
+ },
8548
+ });
8549
+ let triggerExecPromise = null;
8550
+ let triggerScheduled = false;
8551
+ let scheduledTriggers = [];
8552
+ function $Y(db) {
8553
+ const $Y = db._options.Y;
8554
+ if (!$Y)
8555
+ throw new Error('Y library not supplied to Dexie constructor');
8556
+ return $Y;
8557
+ }
8558
+ function executeTriggers(triggersToRun) {
8559
+ return __awaiter(this, void 0, void 0, function* () {
8560
+ for (const { db, parentId, trigger, updatesTable } of triggersToRun) {
8561
+ // Load entire document into an Y.Doc instance:
8562
+ const updates = yield db
8563
+ .table(updatesTable)
8564
+ .where({ k: parentId })
8565
+ .toArray();
8566
+ const Y = $Y(db);
8567
+ const yDoc = new Y.Doc();
8568
+ for (const update of updates) {
8569
+ Y.applyUpdateV2(yDoc, update.u);
8570
+ }
8571
+ try {
8572
+ yield trigger(yDoc, parentId);
8573
+ }
8574
+ catch (error) {
8575
+ console.error(`Error in YDocTrigger ${error}`);
8576
+ }
8577
+ }
8578
+ });
8579
+ }
8580
+ function enqueueTrigger(db, updatesTable, parentId, trigger) {
8581
+ scheduledTriggers.push({
8582
+ db,
8583
+ updatesTable,
8584
+ parentId,
8585
+ trigger,
8586
+ });
8587
+ }
8588
+ function onTransactionCommitted() {
8589
+ return __awaiter(this, void 0, void 0, function* () {
8590
+ if (!triggerScheduled && scheduledTriggers.length > 0) {
8591
+ triggerScheduled = true;
8592
+ if (triggerExecPromise)
8593
+ yield triggerExecPromise.catch(() => { });
8594
+ setTimeout(() => {
8595
+ // setTimeout() is to escape from Promise.PSD zones and never run within liveQueries or transaction scopes
8596
+ triggerScheduled = false;
8597
+ const triggersToRun = scheduledTriggers;
8598
+ scheduledTriggers = [];
8599
+ triggerExecPromise = executeTriggers(triggersToRun).finally(() => (triggerExecPromise = null));
8600
+ }, 0);
8601
+ }
8602
+ });
8603
+ }
8604
+ function hookToDoc(doc, parentId, trigger) {
8605
+ // From now on, keep listening to doc updates and execute the trigger when it happens there instead
8606
+ doc.on('updateV2', (update, origin) => {
8607
+ //Dexie.ignoreTransaction(()=>{
8608
+ trigger(doc, parentId);
8609
+ //});
8610
+ });
8611
+ /*
8612
+ NOT NEEDED because DexieYProvider's docCache will also listen to destroy and remove it from its cache:
8613
+ doc.on('destroy', ()=>{
8614
+ docIsAlreadyHooked.delete(doc);
8615
+ })
8616
+ */
8617
+ }
8618
+ function defineYDocTrigger(table, prop, trigger) {
8619
+ var _a, _b;
8620
+ 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;
8621
+ if (!updatesTable)
8622
+ throw new Error(`Table ${table.name} does not have a Yjs property named ${prop}`);
8623
+ ydocTriggers[updatesTable] = {
8624
+ trigger,
8625
+ parentTable: table.name,
8626
+ prop,
8627
+ };
8628
+ const db = table.db._novip;
8629
+ let mw = middlewares.get(db);
8630
+ if (!mw) {
8631
+ mw = createMiddleware(db);
8632
+ middlewares.set(db, mw);
8633
+ }
8634
+ db.use(mw);
8635
+ }
8636
+
8637
+ export { dexieCloud as default, defineYDocTrigger, dexieCloud, getTiedObjectId, getTiedRealmId, resolveText };
8273
8638
  //# sourceMappingURL=dexie-cloud-addon.js.map