dexie-cloud-addon 4.1.0-alpha.10 → 4.1.0-alpha.13

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.
@@ -8,7 +8,7 @@
8
8
  *
9
9
  * ==========================================================================
10
10
  *
11
- * Version 4.1.0-alpha.10, Wed Oct 16 2024
11
+ * Version 4.1.0-alpha.13, Thu Oct 17 2024
12
12
  *
13
13
  * https://dexie.org
14
14
  *
@@ -16,7 +16,7 @@
16
16
  *
17
17
  */
18
18
 
19
- import Dexie, { PropModification, cmp, DexieYProvider, liveQuery } from 'dexie';
19
+ import Dexie, { PropModification, cmp, DexieYProvider, RangeSet, liveQuery } from 'dexie';
20
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
  /******************************************************************************
@@ -4944,7 +4944,7 @@ function listUpdatesSince(yTable, sinceIncluding) {
4944
4944
  .toArray();
4945
4945
  }
4946
4946
 
4947
- function $Y(db) {
4947
+ function $Y$1(db) {
4948
4948
  const $Y = db.dx._options.Y;
4949
4949
  if (!$Y)
4950
4950
  throw new Error('Y library not supplied to Dexie constructor');
@@ -4974,7 +4974,7 @@ function listYClientMessagesAndStateVector(db, tablesToSync) {
4974
4974
  for (const table of tablesToSync) {
4975
4975
  if (table.schema.yProps) {
4976
4976
  for (const yProp of table.schema.yProps) {
4977
- const Y = $Y(db); // This is how we retrieve the user-provided Y library
4977
+ const Y = $Y$1(db); // This is how we retrieve the user-provided Y library
4978
4978
  const yTable = db.table(yProp.updatesTable); // the updates-table for this combo of table+propName
4979
4979
  const syncState = (yield yTable.get(DEXIE_CLOUD_SYNCER_ID));
4980
4980
  // unsentFrom = the `i` value of updates that aren't yet sent to server (or at least not acked by the server yet)
@@ -5058,6 +5058,7 @@ function getUpdatesTable(db, table, ydocProp) {
5058
5058
  }
5059
5059
 
5060
5060
  function applyYServerMessages(yMessages, db) {
5061
+ var _a;
5061
5062
  return __awaiter(this, void 0, void 0, function* () {
5062
5063
  const result = {};
5063
5064
  for (const m of yMessages) {
@@ -5089,7 +5090,24 @@ function applyYServerMessages(yMessages, db) {
5089
5090
  // See my question in https://discuss.yjs.dev/t/generate-an-inverse-update/2765
5090
5091
  console.debug(`Y update rejected. Deleting it.`);
5091
5092
  const utbl = getUpdatesTable(db, m.table, m.prop);
5092
- yield utbl.delete(m.i);
5093
+ // Delete the rejected update and all local updates since (avoid holes in the CRDT)
5094
+ // and destroy it's open document if there is one.
5095
+ const primaryKey = (_a = (yield utbl.get(m.i))) === null || _a === void 0 ? void 0 : _a.k;
5096
+ if (primaryKey != null) {
5097
+ yield db.transaction('rw', utbl, tx => {
5098
+ // @ts-ignore
5099
+ tx.idbtrans._rejecting_y_ypdate = true; // Inform ydoc triggers that we delete because of a rejection and not GC
5100
+ return utbl
5101
+ .where('i')
5102
+ .aboveOrEqual(m.i)
5103
+ .filter(u => cmp(u.k, primaryKey) === 0 && ((u.f || 0) & 1) === 1)
5104
+ .delete();
5105
+ });
5106
+ // Destroy active doc
5107
+ const activeDoc = DexieYProvider.getDocCache(db.dx).find(m.table, primaryKey, m.prop);
5108
+ if (activeDoc)
5109
+ activeDoc.destroy(); // Destroy the document so that editors don't continue to work on it
5110
+ }
5093
5111
  break;
5094
5112
  }
5095
5113
  case 'in-sync': {
@@ -6303,8 +6321,7 @@ function createMutationTrackingMiddleware({ currentUserObservable, db, }) {
6303
6321
  outstandingTransactions.next(outstandingTransactions.value);
6304
6322
  };
6305
6323
  const txComplete = () => {
6306
- if (tx.mutationsAdded &&
6307
- !isEagerSyncDisabled(db)) {
6324
+ if (tx.mutationsAdded && !isEagerSyncDisabled(db)) {
6308
6325
  triggerSync(db, 'push');
6309
6326
  }
6310
6327
  removeTransaction();
@@ -6386,26 +6403,107 @@ function createMutationTrackingMiddleware({ currentUserObservable, db, }) {
6386
6403
  : mutateAndLog(req);
6387
6404
  } }));
6388
6405
  function mutateAndLog(req) {
6406
+ var _a, _b;
6389
6407
  const trans = req.trans;
6390
- trans.mutationsAdded = true;
6408
+ const unsyncedProps = (_b = (_a = db.cloud.options) === null || _a === void 0 ? void 0 : _a.unsyncedProperties) === null || _b === void 0 ? void 0 : _b[tableName];
6391
6409
  const { txid, currentUser: { userId }, } = trans;
6392
6410
  const { type } = req;
6393
6411
  const opNo = ++trans.opCount;
6412
+ function stripChangeSpec(changeSpec) {
6413
+ if (!unsyncedProps)
6414
+ return changeSpec;
6415
+ let rv = changeSpec;
6416
+ for (const keyPath of Object.keys(changeSpec)) {
6417
+ if (unsyncedProps.some((p) => keyPath === p || keyPath.startsWith(p + '.'))) {
6418
+ if (rv === changeSpec)
6419
+ rv = Object.assign({}, changeSpec); // clone on demand
6420
+ delete rv[keyPath];
6421
+ }
6422
+ }
6423
+ return rv;
6424
+ }
6394
6425
  return table.mutate(req).then((res) => {
6426
+ var _a;
6395
6427
  const { numFailures: hasFailures, failures } = res;
6396
6428
  let keys = type === 'delete' ? req.keys : res.results;
6397
6429
  let values = 'values' in req ? req.values : [];
6398
- let updates = 'updates' in req && req.updates;
6430
+ let changeSpec = 'changeSpec' in req ? req.changeSpec : undefined;
6431
+ let updates = 'updates' in req ? req.updates : undefined;
6399
6432
  if (hasFailures) {
6400
6433
  keys = keys.filter((_, idx) => !failures[idx]);
6401
6434
  values = values.filter((_, idx) => !failures[idx]);
6402
6435
  }
6436
+ if (unsyncedProps) {
6437
+ // Filter out unsynced properties
6438
+ values = values.map((value) => {
6439
+ const newValue = Object.assign({}, value);
6440
+ for (const prop of unsyncedProps) {
6441
+ delete newValue[prop];
6442
+ }
6443
+ return newValue;
6444
+ });
6445
+ if (changeSpec) {
6446
+ // modify operation with criteria and changeSpec.
6447
+ // We must strip out unsynced properties from changeSpec.
6448
+ // We deal with criteria later.
6449
+ changeSpec = stripChangeSpec(changeSpec);
6450
+ if (Object.keys(changeSpec).length === 0) {
6451
+ // Nothing to change on server
6452
+ return res;
6453
+ }
6454
+ }
6455
+ if (updates) {
6456
+ let strippedChangeSpecs = updates.changeSpecs.map(stripChangeSpec);
6457
+ let newUpdates = {
6458
+ keys: [],
6459
+ changeSpecs: [],
6460
+ };
6461
+ const validKeys = new RangeSet();
6462
+ let anyChangeSpecBecameEmpty = false;
6463
+ for (let i = 0, l = strippedChangeSpecs.length; i < l; ++i) {
6464
+ if (Object.keys(strippedChangeSpecs[i]).length > 0) {
6465
+ newUpdates.keys.push(updates.keys[i]);
6466
+ newUpdates.changeSpecs.push(strippedChangeSpecs[i]);
6467
+ validKeys.addKey(updates.keys[i]);
6468
+ }
6469
+ else {
6470
+ anyChangeSpecBecameEmpty = true;
6471
+ }
6472
+ }
6473
+ updates = newUpdates;
6474
+ if (anyChangeSpecBecameEmpty) {
6475
+ // Some keys were stripped. We must also strip them from keys and values
6476
+ let newKeys = [];
6477
+ let newValues = [];
6478
+ for (let i = 0, l = keys.length; i < l; ++i) {
6479
+ if (validKeys.hasKey(keys[i])) {
6480
+ newKeys.push(keys[i]);
6481
+ newValues.push(values[i]);
6482
+ }
6483
+ }
6484
+ keys = newKeys;
6485
+ values = newValues;
6486
+ }
6487
+ }
6488
+ }
6403
6489
  const ts = Date.now();
6404
6490
  // Canonicalize req.criteria.index to null if it's on the primary key.
6405
- const criteria = 'criteria' in req && req.criteria
6491
+ let criteria = 'criteria' in req && req.criteria
6406
6492
  ? Object.assign(Object.assign({}, req.criteria), { index: req.criteria.index === schema.primaryKey.keyPath // Use null to inform server that criteria is on primary key
6407
6493
  ? null // This will disable the server from trying to log consistent operations where it shouldnt.
6408
6494
  : req.criteria.index }) : undefined;
6495
+ if (unsyncedProps && (criteria === null || criteria === void 0 ? void 0 : criteria.index)) {
6496
+ const keyPaths = (_a = schema.indexes.find((idx) => idx.name === criteria.index)) === null || _a === void 0 ? void 0 : _a.keyPath;
6497
+ const involvedProps = keyPaths
6498
+ ? typeof keyPaths === 'string'
6499
+ ? [keyPaths]
6500
+ : keyPaths
6501
+ : [];
6502
+ if (involvedProps.some((p) => unsyncedProps === null || unsyncedProps === void 0 ? void 0 : unsyncedProps.includes(p))) {
6503
+ // Don't log criteria on unsynced properties as the server could not test them.
6504
+ criteria = undefined;
6505
+ }
6506
+ }
6409
6507
  const mut = req.type === 'delete'
6410
6508
  ? {
6411
6509
  type: 'delete',
@@ -6426,7 +6524,7 @@ function createMutationTrackingMiddleware({ currentUserObservable, db, }) {
6426
6524
  userId,
6427
6525
  values,
6428
6526
  }
6429
- : criteria && req.changeSpec
6527
+ : criteria && changeSpec
6430
6528
  ? {
6431
6529
  // Common changeSpec for all keys
6432
6530
  type: 'modify',
@@ -6434,30 +6532,41 @@ function createMutationTrackingMiddleware({ currentUserObservable, db, }) {
6434
6532
  opNo,
6435
6533
  keys,
6436
6534
  criteria,
6437
- changeSpec: req.changeSpec,
6535
+ changeSpec,
6438
6536
  txid,
6439
6537
  userId,
6440
6538
  }
6441
- : updates
6539
+ : changeSpec
6442
6540
  ? {
6443
- // One changeSpec per key
6541
+ // In case criteria involved an unsynced property, we go for keys instead.
6444
6542
  type: 'update',
6445
6543
  ts,
6446
6544
  opNo,
6447
- keys: updates.keys,
6448
- changeSpecs: updates.changeSpecs,
6449
- txid,
6450
- userId,
6451
- }
6452
- : {
6453
- type: 'upsert',
6454
- ts,
6455
- opNo,
6456
6545
  keys,
6457
- values,
6546
+ changeSpecs: keys.map(() => changeSpec),
6458
6547
  txid,
6459
6548
  userId,
6460
- };
6549
+ }
6550
+ : updates
6551
+ ? {
6552
+ // One changeSpec per key
6553
+ type: 'update',
6554
+ ts,
6555
+ opNo,
6556
+ keys: updates.keys,
6557
+ changeSpecs: updates.changeSpecs,
6558
+ txid,
6559
+ userId,
6560
+ }
6561
+ : {
6562
+ type: 'upsert',
6563
+ ts,
6564
+ opNo,
6565
+ keys,
6566
+ values,
6567
+ txid,
6568
+ userId,
6569
+ };
6461
6570
  if ('isAdditionalChunk' in req && req.isAdditionalChunk) {
6462
6571
  mut.isAdditionalChunk = true;
6463
6572
  }
@@ -7804,125 +7913,137 @@ const getInvitesObservable = associate((db) => {
7804
7913
  function createYHandler(db) {
7805
7914
  return (provider) => {
7806
7915
  var _a;
7807
- const awap = getAwarenessLibrary(db);
7808
7916
  const doc = provider.doc;
7809
- const { parentTable, parentId, parentProp, updatesTable } = doc.meta;
7917
+ const { parentTable } = doc.meta || {};
7810
7918
  if (!((_a = db.cloud.schema) === null || _a === void 0 ? void 0 : _a[parentTable].markedForSync)) {
7811
7919
  return; // The table that holds the doc is not marked for sync - leave it to dexie. No syncing, no awareness.
7812
7920
  }
7813
- let awareness = new awap.Awareness(doc);
7814
- awarenessWeakMap.set(doc, awareness);
7815
- provider.awareness = awareness;
7816
- awareness.on('update', ({ added, updated, removed }, origin) => {
7817
- // Send the update
7818
- const changedClients = added.concat(updated).concat(removed);
7819
- const user = db.cloud.currentUser.value;
7820
- if (origin !== 'server' && user.isLoggedIn && !isEagerSyncDisabled(db)) {
7821
- const update = awap.encodeAwarenessUpdate(awareness, changedClients);
7921
+ let awareness;
7922
+ Object.defineProperty(provider, "awareness", {
7923
+ get() {
7924
+ if (awareness)
7925
+ return awareness;
7926
+ awarenessWeakMap.set(doc, awareness);
7927
+ awareness = createAwareness(db, doc, provider);
7928
+ return awareness;
7929
+ }
7930
+ });
7931
+ };
7932
+ }
7933
+ function createAwareness(db, doc, provider) {
7934
+ const { parentTable, parentId, parentProp, updatesTable } = doc.meta;
7935
+ const awap = getAwarenessLibrary(db);
7936
+ const awareness = new awap.Awareness(doc);
7937
+ awareness.on('update', ({ added, updated, removed }, origin) => {
7938
+ // Send the update
7939
+ const changedClients = added.concat(updated).concat(removed);
7940
+ const user = db.cloud.currentUser.value;
7941
+ if (origin !== 'server' && user.isLoggedIn && !isEagerSyncDisabled(db)) {
7942
+ const update = awap.encodeAwarenessUpdate(awareness, changedClients);
7943
+ db.messageProducer.next({
7944
+ type: 'aware',
7945
+ table: parentTable,
7946
+ prop: parentProp,
7947
+ k: doc.meta.parentId,
7948
+ u: update,
7949
+ });
7950
+ if (provider.destroyed) {
7951
+ // We're called from awareness.on('destroy') that did
7952
+ // removeAwarenessStates.
7953
+ // It's time to also send the doc-close message that dexie-cloud understands
7954
+ // and uses to stop subscribing for updates and awareness updates and brings
7955
+ // down the cached information in memory on the WS connection for this.
7822
7956
  db.messageProducer.next({
7823
- type: 'aware',
7957
+ type: 'doc-close',
7824
7958
  table: parentTable,
7825
7959
  prop: parentProp,
7826
- k: doc.meta.parentId,
7827
- u: update,
7960
+ k: doc.meta.parentId
7828
7961
  });
7829
- if (provider.destroyed) {
7830
- // We're called from awareness.on('destroy') that did
7831
- // removeAwarenessStates.
7832
- // It's time to also send the doc-close message that dexie-cloud understands
7833
- // and uses to stop subscribing for updates and awareness updates and brings
7834
- // down the cached information in memory on the WS connection for this.
7835
- db.messageProducer.next({
7836
- type: 'doc-close',
7837
- table: parentTable,
7838
- prop: parentProp,
7839
- k: doc.meta.parentId
7840
- });
7841
- }
7842
7962
  }
7843
- });
7844
- awareness.on('destroy', () => {
7845
- // Signal to server that this provider is destroyed (the update event will be triggered, which
7846
- // in turn will trigger db.messageProducer that will send the message to the server if WS is connected)
7847
- awap.removeAwarenessStates(awareness, [doc.clientID], 'provider destroyed');
7848
- });
7849
- // Now wait til document is loaded and then open the document on the server
7850
- provider.on('load', () => __awaiter(this, void 0, void 0, function* () {
7963
+ }
7964
+ });
7965
+ awareness.on('destroy', () => {
7966
+ // Signal to server that this provider is destroyed (the update event will be triggered, which
7967
+ // in turn will trigger db.messageProducer that will send the message to the server if WS is connected)
7968
+ awap.removeAwarenessStates(awareness, [doc.clientID], 'provider destroyed');
7969
+ });
7970
+ // Open the document on the server
7971
+ (() => __awaiter(this, void 0, void 0, function* () {
7972
+ if (provider.destroyed)
7973
+ return;
7974
+ let connected = false;
7975
+ let currentFlowId = 1;
7976
+ const subscription = db.cloud.webSocketStatus.subscribe((wsStatus) => {
7851
7977
  if (provider.destroyed)
7852
7978
  return;
7853
- let connected = false;
7854
- let currentFlowId = 1;
7855
- const subscription = db.cloud.webSocketStatus.subscribe((wsStatus) => {
7856
- if (provider.destroyed)
7979
+ // Keep "connected" state in a variable so we can check it after async operations
7980
+ connected = wsStatus === 'connected';
7981
+ // We are or got connected. Open the document on the server.
7982
+ const user = db.cloud.currentUser.value;
7983
+ if (wsStatus === "connected" && user.isLoggedIn && !isEagerSyncDisabled(db)) {
7984
+ ++currentFlowId;
7985
+ openDocumentOnServer().catch(error => {
7986
+ console.warn(`Error catched in createYHandler.ts: ${error}`);
7987
+ });
7988
+ }
7989
+ });
7990
+ // Wait until WebSocket is connected
7991
+ provider.addCleanupHandler(subscription);
7992
+ /** Sends an 'doc-open' message to server whenever websocket becomes
7993
+ * connected, or if it is already connected.
7994
+ * The flow is aborted in case websocket is disconnected while querying
7995
+ * information required to compute the state vector. Flow is also
7996
+ * aborted in case document or provider has been destroyed during
7997
+ * the async parts of the task.
7998
+ *
7999
+ * The state vector is only computed from the updates that have occured
8000
+ * after the last full sync - which could very often be zero - in which
8001
+ * case no state vector is sent (then the server already knows us by
8002
+ * revision)
8003
+ *
8004
+ * When server gets the doc-open message, it will authorized us for
8005
+ * whether we are allowed to read / write to this document, and then
8006
+ * keep the cached information in memory on the WS connection for this
8007
+ * particular document, as well as subscribe to updates and awareness updates
8008
+ * from other clients on the document.
8009
+ */
8010
+ function openDocumentOnServer(wsStatus) {
8011
+ return __awaiter(this, void 0, void 0, function* () {
8012
+ const myFlow = currentFlowId; // So we can abort when a new flow is started
8013
+ const yTbl = db.table(updatesTable);
8014
+ const syncState = yield yTbl.get(DEXIE_CLOUD_SYNCER_ID);
8015
+ // After every await, check if we still should be working on this task.
8016
+ if (provider.destroyed || currentFlowId !== myFlow || !connected)
7857
8017
  return;
7858
- // Keep "connected" state in a variable so we can check it after async operations
7859
- connected = wsStatus === 'connected';
7860
- // We are or got connected. Open the document on the server.
7861
- const user = db.cloud.currentUser.value;
7862
- if (wsStatus === "connected" && user.isLoggedIn && !isEagerSyncDisabled(db)) {
7863
- ++currentFlowId;
7864
- openDocumentOnServer().catch(error => {
7865
- console.warn(`Error catched in createYHandler.ts: ${error}`);
7866
- });
8018
+ const receivedUntil = (syncState === null || syncState === void 0 ? void 0 : syncState.receivedUntil) || 0;
8019
+ const docOpenMsg = {
8020
+ type: 'doc-open',
8021
+ table: parentTable,
8022
+ prop: parentProp,
8023
+ k: parentId,
8024
+ serverRev: syncState === null || syncState === void 0 ? void 0 : syncState.serverRev,
8025
+ };
8026
+ const serverUpdatesSinceLastSync = yield yTbl
8027
+ .where('i')
8028
+ .between(receivedUntil, Infinity, false)
8029
+ .filter((update) => cmp(update.k, parentId) === 0 && // Only updates for this document
8030
+ ((update.f || 0) & 1) === 0 // Don't include local changes
8031
+ )
8032
+ .toArray();
8033
+ // After every await, check if we still should be working on this task.
8034
+ if (provider.destroyed || currentFlowId !== myFlow || !connected)
8035
+ return;
8036
+ if (serverUpdatesSinceLastSync.length > 0) {
8037
+ const Y = $Y$1(db); // Get the Yjs library from Dexie constructor options
8038
+ const mergedUpdate = Y.mergeUpdatesV2(serverUpdatesSinceLastSync.map((update) => update.u));
8039
+ const stateVector = Y.encodeStateVectorFromUpdateV2(mergedUpdate);
8040
+ docOpenMsg.sv = stateVector;
7867
8041
  }
8042
+ db.messageProducer.next(docOpenMsg);
7868
8043
  });
7869
- // Wait until WebSocket is connected
7870
- provider.addCleanupHandler(subscription);
7871
- /** Sends an 'doc-open' message to server whenever websocket becomes
7872
- * connected, or if it is already connected.
7873
- * The flow is aborted in case websocket is disconnected while querying
7874
- * information required to compute the state vector. Flow is also
7875
- * aborted in case document or provider has been destroyed during
7876
- * the async parts of the task.
7877
- *
7878
- * The state vector is only computed from the updates that have occured
7879
- * after the last full sync - which could very often be zero - in which
7880
- * case no state vector is sent (then the server already knows us by
7881
- * revision)
7882
- *
7883
- * When server gets the doc-open message, it will authorized us for
7884
- * whether we are allowed to read / write to this document, and then
7885
- * keep the cached information in memory on the WS connection for this
7886
- * particular document, as well as subscribe to updates and awareness updates
7887
- * from other clients on the document.
7888
- */
7889
- function openDocumentOnServer(wsStatus) {
7890
- return __awaiter(this, void 0, void 0, function* () {
7891
- const myFlow = currentFlowId; // So we can abort when a new flow is started
7892
- const yTbl = db.table(updatesTable);
7893
- const syncState = yield yTbl.get(DEXIE_CLOUD_SYNCER_ID);
7894
- // After every await, check if we still should be working on this task.
7895
- if (provider.destroyed || currentFlowId !== myFlow || !connected)
7896
- return;
7897
- const receivedUntil = (syncState === null || syncState === void 0 ? void 0 : syncState.receivedUntil) || 0;
7898
- const docOpenMsg = {
7899
- type: 'doc-open',
7900
- table: parentTable,
7901
- prop: parentProp,
7902
- k: parentId,
7903
- serverRev: syncState === null || syncState === void 0 ? void 0 : syncState.serverRev,
7904
- };
7905
- const serverUpdatesSinceLastSync = yield yTbl
7906
- .where('i')
7907
- .between(receivedUntil, Infinity, false)
7908
- .filter((update) => cmp(update.k, parentId) === 0 && // Only updates for this document
7909
- ((update.f || 0) & 1) === 0 // Don't include local changes
7910
- )
7911
- .toArray();
7912
- // After every await, check if we still should be working on this task.
7913
- if (provider.destroyed || currentFlowId !== myFlow || !connected)
7914
- return;
7915
- if (serverUpdatesSinceLastSync.length > 0) {
7916
- const Y = $Y(db); // Get the Yjs library from Dexie constructor options
7917
- const mergedUpdate = Y.mergeUpdatesV2(serverUpdatesSinceLastSync.map((update) => update.u));
7918
- const stateVector = Y.encodeStateVectorFromUpdateV2(mergedUpdate);
7919
- docOpenMsg.sv = stateVector;
7920
- }
7921
- db.messageProducer.next(docOpenMsg);
7922
- });
7923
- }
7924
- }));
7925
- };
8044
+ }
8045
+ }))();
8046
+ return awareness;
7926
8047
  }
7927
8048
 
7928
8049
  function getTiedRealmId(objectId) {
@@ -7972,7 +8093,7 @@ function dexieCloud(dexie) {
7972
8093
  const syncComplete = new Subject();
7973
8094
  dexie.cloud = {
7974
8095
  // @ts-ignore
7975
- version: "4.1.0-alpha.10",
8096
+ version: "4.1.0-alpha.13",
7976
8097
  options: Object.assign({}, DEFAULT_OPTIONS),
7977
8098
  schema: null,
7978
8099
  get currentUserId() {
@@ -8274,8 +8395,160 @@ function dexieCloud(dexie) {
8274
8395
  }
8275
8396
  }
8276
8397
  // @ts-ignore
8277
- dexieCloud.version = "4.1.0-alpha.10";
8398
+ dexieCloud.version = "4.1.0-alpha.13";
8278
8399
  Dexie.Cloud = dexieCloud;
8279
8400
 
8280
- export { dexieCloud as default, dexieCloud, getTiedObjectId, getTiedRealmId, resolveText };
8401
+ const ydocTriggers = {};
8402
+ const docIsAlreadyHooked = new WeakSet();
8403
+ const middlewares = new WeakMap();
8404
+ const createMiddleware = (db) => ({
8405
+ stack: 'dbcore',
8406
+ level: 10,
8407
+ name: 'yTriggerMiddleware',
8408
+ create: (down) => {
8409
+ return Object.assign(Object.assign({}, down), { transaction: (stores, mode, options) => {
8410
+ const idbtrans = down.transaction(stores, mode, options);
8411
+ idbtrans.addEventListener('complete', onTransactionCommitted);
8412
+ return idbtrans;
8413
+ }, table: (tblName) => {
8414
+ const coreTable = down.table(tblName);
8415
+ const triggerSpec = ydocTriggers[tblName];
8416
+ if (!triggerSpec)
8417
+ return coreTable;
8418
+ const { trigger, parentTable, prop } = triggerSpec;
8419
+ return Object.assign(Object.assign({}, coreTable), { mutate(req) {
8420
+ switch (req.type) {
8421
+ case 'add': {
8422
+ for (const yUpdateRow of req.values) {
8423
+ const primaryKey = yUpdateRow.k;
8424
+ const doc = DexieYProvider.getDocCache(db).find(parentTable, primaryKey, prop);
8425
+ if (doc) {
8426
+ if (!docIsAlreadyHooked.has(doc)) {
8427
+ hookToDoc(doc, primaryKey, trigger);
8428
+ docIsAlreadyHooked.add(doc);
8429
+ }
8430
+ }
8431
+ else {
8432
+ enqueueTrigger(tblName, primaryKey, trigger);
8433
+ }
8434
+ }
8435
+ break;
8436
+ }
8437
+ case 'delete':
8438
+ // @ts-ignore
8439
+ if (req.trans._rejecting_y_ypdate) {
8440
+ // The deletion came from a rejection, not garbage collection.
8441
+ // When that happens, let the triggers run to compute new values
8442
+ // based on the deleted updates.
8443
+ coreTable
8444
+ .getMany({
8445
+ keys: req.keys,
8446
+ trans: req.trans,
8447
+ cache: 'immutable',
8448
+ })
8449
+ .then((updates) => {
8450
+ const keySet = new RangeSet();
8451
+ for (const { k } of updates) {
8452
+ keySet.addKey(k);
8453
+ }
8454
+ for (const key of keySet) {
8455
+ enqueueTrigger(tblName, key, trigger);
8456
+ }
8457
+ });
8458
+ }
8459
+ break;
8460
+ }
8461
+ return coreTable.mutate(req);
8462
+ } });
8463
+ } });
8464
+ },
8465
+ });
8466
+ let triggerExecPromise = null;
8467
+ let triggerScheduled = false;
8468
+ let scheduledTriggers = [];
8469
+ function $Y(db) {
8470
+ const $Y = db._options.Y;
8471
+ if (!$Y)
8472
+ throw new Error('Y library not supplied to Dexie constructor');
8473
+ return $Y;
8474
+ }
8475
+ function executeTriggers(triggersToRun) {
8476
+ return __awaiter(this, void 0, void 0, function* () {
8477
+ for (const { db, parentId, trigger, updatesTable } of triggersToRun) {
8478
+ // Load entire document into an Y.Doc instance:
8479
+ const updates = yield db
8480
+ .table(updatesTable)
8481
+ .where({ k: parentId })
8482
+ .toArray();
8483
+ const Y = $Y(db);
8484
+ const yDoc = new Y.Doc();
8485
+ for (const update of updates) {
8486
+ Y.applyUpdateV2(yDoc, update);
8487
+ }
8488
+ try {
8489
+ yield trigger(yDoc, parentId);
8490
+ }
8491
+ catch (error) {
8492
+ console.error(`Error in YDocTrigger ${error}`);
8493
+ }
8494
+ }
8495
+ });
8496
+ }
8497
+ function enqueueTrigger(updatesTable, parentId, trigger) {
8498
+ var _a;
8499
+ ((_a = scheduledTriggers[updatesTable]) !== null && _a !== void 0 ? _a : (scheduledTriggers[updatesTable] = [])).push({
8500
+ parentId,
8501
+ trigger,
8502
+ });
8503
+ }
8504
+ function onTransactionCommitted() {
8505
+ return __awaiter(this, void 0, void 0, function* () {
8506
+ if (!triggerScheduled && scheduledTriggers.length > 0) {
8507
+ triggerScheduled = true;
8508
+ if (triggerExecPromise)
8509
+ yield triggerExecPromise.catch(() => { });
8510
+ setTimeout(() => {
8511
+ // setTimeout() is to escape from Promise.PSD zones and never run within liveQueries or transaction scopes
8512
+ triggerScheduled = false;
8513
+ const triggersToRun = scheduledTriggers;
8514
+ scheduledTriggers = [];
8515
+ triggerExecPromise = executeTriggers(triggersToRun).finally(() => (triggerExecPromise = null));
8516
+ }, 0);
8517
+ }
8518
+ });
8519
+ }
8520
+ function hookToDoc(doc, parentId, trigger) {
8521
+ // From now on, keep listening to doc updates and execute the trigger when it happens there instead
8522
+ doc.on('updateV2', (update, origin) => {
8523
+ //Dexie.ignoreTransaction(()=>{
8524
+ trigger(doc, parentId);
8525
+ //});
8526
+ });
8527
+ /*
8528
+ NOT NEEDED because DexieYProvider's docCache will also listen to destroy and remove it from its cache:
8529
+ doc.on('destroy', ()=>{
8530
+ docIsAlreadyHooked.delete(doc);
8531
+ })
8532
+ */
8533
+ }
8534
+ function defineYDocTrigger(table, prop, trigger) {
8535
+ var _a, _b;
8536
+ 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;
8537
+ if (!updatesTable)
8538
+ throw new Error(`Table ${table.name} does not have a Yjs property named ${prop}`);
8539
+ ydocTriggers[updatesTable] = {
8540
+ trigger,
8541
+ parentTable: table.name,
8542
+ prop,
8543
+ };
8544
+ const db = table.db._novip;
8545
+ let mw = middlewares.get(db);
8546
+ if (!mw) {
8547
+ mw = createMiddleware(db);
8548
+ middlewares.set(db, mw);
8549
+ }
8550
+ db.use(mw);
8551
+ }
8552
+
8553
+ export { dexieCloud as default, defineYDocTrigger, dexieCloud, getTiedObjectId, getTiedRealmId, resolveText };
8281
8554
  //# sourceMappingURL=dexie-cloud-addon.js.map