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

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.12, Wed Oct 16 2024
12
12
  *
13
13
  * https://dexie.org
14
14
  *
@@ -4752,6 +4752,7 @@ function getUpdatesTable(db, table, ydocProp) {
4752
4752
  }
4753
4753
 
4754
4754
  function applyYServerMessages(yMessages, db) {
4755
+ var _a;
4755
4756
  return __awaiter(this, void 0, void 0, function* () {
4756
4757
  const result = {};
4757
4758
  for (const m of yMessages) {
@@ -4783,7 +4784,24 @@ function applyYServerMessages(yMessages, db) {
4783
4784
  // See my question in https://discuss.yjs.dev/t/generate-an-inverse-update/2765
4784
4785
  console.debug(`Y update rejected. Deleting it.`);
4785
4786
  const utbl = getUpdatesTable(db, m.table, m.prop);
4786
- yield utbl.delete(m.i);
4787
+ // Delete the rejected update and all local updates since (avoid holes in the CRDT)
4788
+ // and destroy it's open document if there is one.
4789
+ const primaryKey = (_a = (yield utbl.get(m.i))) === null || _a === void 0 ? void 0 : _a.k;
4790
+ if (primaryKey != null) {
4791
+ yield db.transaction('rw', utbl, tx => {
4792
+ // @ts-ignore
4793
+ tx.idbtrans._rejecting_y_ypdate = true; // Inform ydoc triggers that we delete because of a rejection and not GC
4794
+ return utbl
4795
+ .where('i')
4796
+ .aboveOrEqual(m.i)
4797
+ .filter(u => cmp(u.k, primaryKey) === 0 && ((u.f || 0) & 1) === 1)
4798
+ .delete();
4799
+ });
4800
+ // Destroy active doc
4801
+ const activeDoc = DexieYProvider.getDocCache(db.dx).find(m.table, primaryKey, m.prop);
4802
+ if (activeDoc)
4803
+ activeDoc.destroy(); // Destroy the document so that editors don't continue to work on it
4804
+ }
4787
4805
  break;
4788
4806
  }
4789
4807
  case 'in-sync': {
@@ -7804,125 +7822,137 @@ const getInvitesObservable = associate((db) => {
7804
7822
  function createYHandler(db) {
7805
7823
  return (provider) => {
7806
7824
  var _a;
7807
- const awap = getAwarenessLibrary(db);
7808
7825
  const doc = provider.doc;
7809
- const { parentTable, parentId, parentProp, updatesTable } = doc.meta;
7826
+ const { parentTable } = doc.meta || {};
7810
7827
  if (!((_a = db.cloud.schema) === null || _a === void 0 ? void 0 : _a[parentTable].markedForSync)) {
7811
7828
  return; // The table that holds the doc is not marked for sync - leave it to dexie. No syncing, no awareness.
7812
7829
  }
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);
7830
+ let awareness;
7831
+ Object.defineProperty(provider, "awareness", {
7832
+ get() {
7833
+ if (awareness)
7834
+ return awareness;
7835
+ awarenessWeakMap.set(doc, awareness);
7836
+ awareness = createAwareness(db, doc, provider);
7837
+ return awareness;
7838
+ }
7839
+ });
7840
+ };
7841
+ }
7842
+ function createAwareness(db, doc, provider) {
7843
+ const { parentTable, parentId, parentProp, updatesTable } = doc.meta;
7844
+ const awap = getAwarenessLibrary(db);
7845
+ const awareness = new awap.Awareness(doc);
7846
+ awareness.on('update', ({ added, updated, removed }, origin) => {
7847
+ // Send the update
7848
+ const changedClients = added.concat(updated).concat(removed);
7849
+ const user = db.cloud.currentUser.value;
7850
+ if (origin !== 'server' && user.isLoggedIn && !isEagerSyncDisabled(db)) {
7851
+ const update = awap.encodeAwarenessUpdate(awareness, changedClients);
7852
+ db.messageProducer.next({
7853
+ type: 'aware',
7854
+ table: parentTable,
7855
+ prop: parentProp,
7856
+ k: doc.meta.parentId,
7857
+ u: update,
7858
+ });
7859
+ if (provider.destroyed) {
7860
+ // We're called from awareness.on('destroy') that did
7861
+ // removeAwarenessStates.
7862
+ // It's time to also send the doc-close message that dexie-cloud understands
7863
+ // and uses to stop subscribing for updates and awareness updates and brings
7864
+ // down the cached information in memory on the WS connection for this.
7822
7865
  db.messageProducer.next({
7823
- type: 'aware',
7866
+ type: 'doc-close',
7824
7867
  table: parentTable,
7825
7868
  prop: parentProp,
7826
- k: doc.meta.parentId,
7827
- u: update,
7869
+ k: doc.meta.parentId
7828
7870
  });
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
7871
  }
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* () {
7872
+ }
7873
+ });
7874
+ awareness.on('destroy', () => {
7875
+ // Signal to server that this provider is destroyed (the update event will be triggered, which
7876
+ // in turn will trigger db.messageProducer that will send the message to the server if WS is connected)
7877
+ awap.removeAwarenessStates(awareness, [doc.clientID], 'provider destroyed');
7878
+ });
7879
+ // Open the document on the server
7880
+ (() => __awaiter(this, void 0, void 0, function* () {
7881
+ if (provider.destroyed)
7882
+ return;
7883
+ let connected = false;
7884
+ let currentFlowId = 1;
7885
+ const subscription = db.cloud.webSocketStatus.subscribe((wsStatus) => {
7851
7886
  if (provider.destroyed)
7852
7887
  return;
7853
- let connected = false;
7854
- let currentFlowId = 1;
7855
- const subscription = db.cloud.webSocketStatus.subscribe((wsStatus) => {
7856
- if (provider.destroyed)
7888
+ // Keep "connected" state in a variable so we can check it after async operations
7889
+ connected = wsStatus === 'connected';
7890
+ // We are or got connected. Open the document on the server.
7891
+ const user = db.cloud.currentUser.value;
7892
+ if (wsStatus === "connected" && user.isLoggedIn && !isEagerSyncDisabled(db)) {
7893
+ ++currentFlowId;
7894
+ openDocumentOnServer().catch(error => {
7895
+ console.warn(`Error catched in createYHandler.ts: ${error}`);
7896
+ });
7897
+ }
7898
+ });
7899
+ // Wait until WebSocket is connected
7900
+ provider.addCleanupHandler(subscription);
7901
+ /** Sends an 'doc-open' message to server whenever websocket becomes
7902
+ * connected, or if it is already connected.
7903
+ * The flow is aborted in case websocket is disconnected while querying
7904
+ * information required to compute the state vector. Flow is also
7905
+ * aborted in case document or provider has been destroyed during
7906
+ * the async parts of the task.
7907
+ *
7908
+ * The state vector is only computed from the updates that have occured
7909
+ * after the last full sync - which could very often be zero - in which
7910
+ * case no state vector is sent (then the server already knows us by
7911
+ * revision)
7912
+ *
7913
+ * When server gets the doc-open message, it will authorized us for
7914
+ * whether we are allowed to read / write to this document, and then
7915
+ * keep the cached information in memory on the WS connection for this
7916
+ * particular document, as well as subscribe to updates and awareness updates
7917
+ * from other clients on the document.
7918
+ */
7919
+ function openDocumentOnServer(wsStatus) {
7920
+ return __awaiter(this, void 0, void 0, function* () {
7921
+ const myFlow = currentFlowId; // So we can abort when a new flow is started
7922
+ const yTbl = db.table(updatesTable);
7923
+ const syncState = yield yTbl.get(DEXIE_CLOUD_SYNCER_ID);
7924
+ // After every await, check if we still should be working on this task.
7925
+ if (provider.destroyed || currentFlowId !== myFlow || !connected)
7857
7926
  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
- });
7927
+ const receivedUntil = (syncState === null || syncState === void 0 ? void 0 : syncState.receivedUntil) || 0;
7928
+ const docOpenMsg = {
7929
+ type: 'doc-open',
7930
+ table: parentTable,
7931
+ prop: parentProp,
7932
+ k: parentId,
7933
+ serverRev: syncState === null || syncState === void 0 ? void 0 : syncState.serverRev,
7934
+ };
7935
+ const serverUpdatesSinceLastSync = yield yTbl
7936
+ .where('i')
7937
+ .between(receivedUntil, Infinity, false)
7938
+ .filter((update) => cmp(update.k, parentId) === 0 && // Only updates for this document
7939
+ ((update.f || 0) & 1) === 0 // Don't include local changes
7940
+ )
7941
+ .toArray();
7942
+ // After every await, check if we still should be working on this task.
7943
+ if (provider.destroyed || currentFlowId !== myFlow || !connected)
7944
+ return;
7945
+ if (serverUpdatesSinceLastSync.length > 0) {
7946
+ const Y = $Y(db); // Get the Yjs library from Dexie constructor options
7947
+ const mergedUpdate = Y.mergeUpdatesV2(serverUpdatesSinceLastSync.map((update) => update.u));
7948
+ const stateVector = Y.encodeStateVectorFromUpdateV2(mergedUpdate);
7949
+ docOpenMsg.sv = stateVector;
7867
7950
  }
7951
+ db.messageProducer.next(docOpenMsg);
7868
7952
  });
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
- };
7953
+ }
7954
+ }))();
7955
+ return awareness;
7926
7956
  }
7927
7957
 
7928
7958
  const DEFAULT_OPTIONS = {
@@ -7965,7 +7995,7 @@ function dexieCloud(dexie) {
7965
7995
  const syncComplete = new Subject();
7966
7996
  dexie.cloud = {
7967
7997
  // @ts-ignore
7968
- version: "4.1.0-alpha.10",
7998
+ version: "4.1.0-alpha.12",
7969
7999
  options: Object.assign({}, DEFAULT_OPTIONS),
7970
8000
  schema: null,
7971
8001
  get currentUserId() {
@@ -8267,7 +8297,7 @@ function dexieCloud(dexie) {
8267
8297
  }
8268
8298
  }
8269
8299
  // @ts-ignore
8270
- dexieCloud.version = "4.1.0-alpha.10";
8300
+ dexieCloud.version = "4.1.0-alpha.12";
8271
8301
  Dexie.Cloud = dexieCloud;
8272
8302
 
8273
8303
  // In case the SW lives for a while, let it reuse already opened connections: