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

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.20, Wed Oct 23 2024
11
+ * Version 4.1.0-alpha.23, Wed Nov 27 2024
12
12
  *
13
13
  * https://dexie.org
14
14
  *
@@ -2757,13 +2757,20 @@
2757
2757
  function encodeYMessage(msg) {
2758
2758
  const encoder = new Encoder();
2759
2759
  writeVarString(encoder, msg.type);
2760
- writeVarString(encoder, msg.table);
2761
- writeVarString(encoder, msg.prop);
2760
+ if ('table' in msg)
2761
+ writeVarString(encoder, msg.table);
2762
+ if ('prop' in msg)
2763
+ writeVarString(encoder, msg.prop);
2762
2764
  switch (msg.type) {
2763
2765
  case 'u-ack':
2764
2766
  case 'u-reject':
2765
2767
  writeBigUint64(encoder, BigInt(msg.i));
2766
2768
  break;
2769
+ case 'outdated-server-rev':
2770
+ break;
2771
+ case 'y-complete-sync-done':
2772
+ writeVarString(encoder, msg.yServerRev);
2773
+ break;
2767
2774
  default:
2768
2775
  writeAny(encoder, msg.k);
2769
2776
  switch (msg.type) {
@@ -3107,6 +3114,12 @@
3107
3114
  function decodeYMessage(a) {
3108
3115
  const decoder = new Decoder(a);
3109
3116
  const type = readVarString(decoder);
3117
+ if (type === 'outdated-server-rev') {
3118
+ return { type };
3119
+ }
3120
+ if (type === 'y-complete-sync-done') {
3121
+ return { type, yServerRev: readVarString(decoder) };
3122
+ }
3110
3123
  const table = readVarString(decoder);
3111
3124
  const prop = readVarString(decoder);
3112
3125
  switch (type) {
@@ -4412,6 +4425,7 @@
4412
4425
  lastPull: syncState
4413
4426
  ? {
4414
4427
  serverRevision: syncState.serverRevision,
4428
+ yServerRevision: syncState.yServerRevision,
4415
4429
  realms: syncState.realms,
4416
4430
  inviteRealms: syncState.inviteRealms,
4417
4431
  }
@@ -4757,12 +4771,14 @@
4757
4771
  function applyYServerMessages(yMessages, db) {
4758
4772
  return __awaiter(this, void 0, void 0, function* () {
4759
4773
  var _a;
4760
- const result = {};
4774
+ const receivedUntils = {};
4775
+ let resyncNeeded = false;
4776
+ let yServerRevision;
4761
4777
  for (const m of yMessages) {
4762
4778
  switch (m.type) {
4763
4779
  case 'u-s': {
4764
4780
  const utbl = getUpdatesTable(db, m.table, m.prop);
4765
- result[utbl.name] = yield utbl.add({
4781
+ receivedUntils[utbl.name] = yield utbl.add({
4766
4782
  k: m.k,
4767
4783
  u: m.u,
4768
4784
  });
@@ -4791,13 +4807,13 @@
4791
4807
  // and destroy it's open document if there is one.
4792
4808
  const primaryKey = (_a = (yield utbl.get(m.i))) === null || _a === void 0 ? void 0 : _a.k;
4793
4809
  if (primaryKey != null) {
4794
- yield db.transaction('rw', utbl, tx => {
4810
+ yield db.transaction('rw', utbl, (tx) => {
4795
4811
  // @ts-ignore
4796
4812
  tx.idbtrans._rejecting_y_ypdate = true; // Inform ydoc triggers that we delete because of a rejection and not GC
4797
4813
  return utbl
4798
4814
  .where('i')
4799
4815
  .aboveOrEqual(m.i)
4800
- .filter(u => Dexie.cmp(u.k, primaryKey) === 0 && ((u.f || 0) & 1) === 1)
4816
+ .filter((u) => Dexie.cmp(u.k, primaryKey) === 0 && ((u.f || 0) & 1) === 1)
4801
4817
  .delete();
4802
4818
  });
4803
4819
  // Destroy active doc
@@ -4814,13 +4830,24 @@
4814
4830
  }
4815
4831
  break;
4816
4832
  }
4833
+ case 'y-complete-sync-done': {
4834
+ yServerRevision = m.yServerRev;
4835
+ break;
4836
+ }
4837
+ case 'outdated-server-rev':
4838
+ resyncNeeded = true;
4839
+ break;
4817
4840
  }
4818
4841
  }
4819
- return result;
4842
+ return {
4843
+ receivedUntils,
4844
+ resyncNeeded,
4845
+ yServerRevision
4846
+ };
4820
4847
  });
4821
4848
  }
4822
4849
 
4823
- function updateYSyncStates(lastUpdateIdsBeforeSync, receivedUntilsAfterSync, db, serverRevision) {
4850
+ function updateYSyncStates(lastUpdateIdsBeforeSync, receivedUntilsAfterSync, db) {
4824
4851
  return __awaiter(this, void 0, void 0, function* () {
4825
4852
  var _a, _b, _c, _d, _e;
4826
4853
  // We want to update unsentFrom for each yTable to the value specified in first argument
@@ -4869,14 +4896,12 @@
4869
4896
  yield db.table(yTable).add({
4870
4897
  i: DEXIE_CLOUD_SYNCER_ID,
4871
4898
  unsentFrom,
4872
- receivedUntil,
4873
- serverRev: serverRevision,
4899
+ receivedUntil
4874
4900
  });
4875
4901
  }
4876
4902
  else {
4877
4903
  state.unsentFrom = Math.max(unsentFrom, state.unsentFrom || 1);
4878
4904
  state.receivedUntil = Math.max(receivedUntil, state.receivedUntil || 0);
4879
- state.serverRev = serverRevision;
4880
4905
  yield db.table(yTable).put(state);
4881
4906
  }
4882
4907
  }));
@@ -5212,6 +5237,7 @@
5212
5237
  newSyncState.realms = res.realms;
5213
5238
  newSyncState.inviteRealms = res.inviteRealms;
5214
5239
  newSyncState.serverRevision = res.serverRevision;
5240
+ newSyncState.yServerRevision = res.serverRevision;
5215
5241
  newSyncState.timestamp = new Date();
5216
5242
  delete newSyncState.error;
5217
5243
  const filteredChanges = filterServerChangesThroughAddedClientChanges(res.changes, addedClientChanges);
@@ -5223,11 +5249,17 @@
5223
5249
  //
5224
5250
  // apply yMessages
5225
5251
  //
5226
- const receivedUntils = yield applyYServerMessages(res.yMessages, db);
5252
+ const { receivedUntils, resyncNeeded, yServerRevision } = yield applyYServerMessages(res.yMessages, db);
5253
+ if (yServerRevision) {
5254
+ newSyncState.yServerRevision = yServerRevision;
5255
+ }
5227
5256
  //
5228
5257
  // update Y SyncStates
5229
5258
  //
5230
- yield updateYSyncStates(lastUpdateIds, receivedUntils, db, res.serverRevision);
5259
+ yield updateYSyncStates(lastUpdateIds, receivedUntils, db);
5260
+ if (resyncNeeded) {
5261
+ newSyncState.yDownloadedRealms = {}; // Will trigger a full download of Y-documents below...
5262
+ }
5231
5263
  }
5232
5264
  //
5233
5265
  // Update regular syncState
@@ -5520,7 +5552,7 @@
5520
5552
  };
5521
5553
  }
5522
5554
 
5523
- const wm$1 = new WeakMap();
5555
+ const wm$2 = new WeakMap();
5524
5556
  const DEXIE_CLOUD_SCHEMA = {
5525
5557
  members: '@id, [userId+realmId], [email+realmId], realmId',
5526
5558
  roles: '[realmId+name]',
@@ -5534,7 +5566,7 @@
5534
5566
  function DexieCloudDB(dx) {
5535
5567
  if ('vip' in dx)
5536
5568
  dx = dx['vip']; // Avoid race condition. Always map to a vipped dexie that don't block during db.on.ready().
5537
- let db = wm$1.get(dx.cloud);
5569
+ let db = wm$2.get(dx.cloud);
5538
5570
  if (!db) {
5539
5571
  const localSyncEvent = new rxjs.Subject();
5540
5572
  let syncStateChangedEvent = new BroadcastedAndLocalEvent(`syncstatechanged-${dx.name}`);
@@ -5622,7 +5654,7 @@
5622
5654
  Object.assign(db, helperMethods);
5623
5655
  db.messageConsumer = MessagesFromServerConsumer(db);
5624
5656
  db.messageProducer = new rxjs.Subject();
5625
- wm$1.set(dx.cloud, db);
5657
+ wm$2.set(dx.cloud, db);
5626
5658
  }
5627
5659
  return db;
5628
5660
  }
@@ -5633,10 +5665,10 @@
5633
5665
  }
5634
5666
 
5635
5667
  // Emulate true-private property db. Why? So it's not stored in DB.
5636
- const wm = new WeakMap();
5668
+ const wm$1 = new WeakMap();
5637
5669
  class AuthPersistedContext {
5638
5670
  constructor(db, userLogin) {
5639
- wm.set(this, db);
5671
+ wm$1.set(this, db);
5640
5672
  Object.assign(this, userLogin);
5641
5673
  }
5642
5674
  static load(db, userId) {
@@ -5653,7 +5685,7 @@
5653
5685
  }
5654
5686
  save() {
5655
5687
  return __awaiter(this, void 0, void 0, function* () {
5656
- const db = wm.get(this);
5688
+ const db = wm$1.get(this);
5657
5689
  db.table("$logins").put(this);
5658
5690
  });
5659
5691
  }
@@ -6788,9 +6820,7 @@
6788
6820
  // If messageProducer emits empty array, nothing is emitted
6789
6821
  // but if messageProducer emits array of messages, they are
6790
6822
  // emitted one by one.
6791
- rxjs.mergeMap((messages) => messages), rxjs.tap((message) => {
6792
- console.debug('dexie-cloud emitting y-c', message);
6793
- }));
6823
+ rxjs.mergeMap((messages) => messages));
6794
6824
  }
6795
6825
 
6796
6826
  function getAwarenessLibrary(db) {
@@ -6803,6 +6833,23 @@
6803
6833
  const awarenessWeakMap = new WeakMap();
6804
6834
  const getDocAwareness = (doc) => awarenessWeakMap.get(doc);
6805
6835
 
6836
+ const wm = new WeakMap();
6837
+ /** A property (package-private) on Y.Doc that is used
6838
+ * to signal that the server wants us to send a 'doc-open' message
6839
+ * to the server for this document.
6840
+ *
6841
+ * @param doc
6842
+ * @returns
6843
+ */
6844
+ function getOpenDocSignal(doc) {
6845
+ let signal = wm.get(doc);
6846
+ if (!signal) {
6847
+ signal = new rxjs.Subject();
6848
+ wm.set(doc, signal);
6849
+ }
6850
+ return signal;
6851
+ }
6852
+
6806
6853
  const SERVER_PING_TIMEOUT = 20000;
6807
6854
  const CLIENT_PING_INTERVAL = 30000;
6808
6855
  const FAIL_RETRY_WAIT_TIME = 60000;
@@ -6965,9 +7012,6 @@
6965
7012
  if (msg.type === 'error') {
6966
7013
  throw new Error(`Error message from dexie-cloud: ${msg.error}`);
6967
7014
  }
6968
- else if (msg.type === 'rev') {
6969
- this.rev = msg.rev; // No meaning but seems reasonable.
6970
- }
6971
7015
  else if (msg.type === 'aware') {
6972
7016
  const docCache = Dexie.DexieYProvider.getDocCache(this.db.dx);
6973
7017
  const doc = docCache.find(msg.table, msg.k, msg.prop);
@@ -6982,7 +7026,19 @@
6982
7026
  else if (msg.type === 'u-ack' || msg.type === 'u-reject' || msg.type === 'u-s' || msg.type === 'in-sync') {
6983
7027
  applyYServerMessages([msg], this.db);
6984
7028
  }
7029
+ else if (msg.type === 'doc-open') {
7030
+ const docCache = Dexie.DexieYProvider.getDocCache(this.db.dx);
7031
+ const doc = docCache.find(msg.table, msg.k, msg.prop);
7032
+ if (doc) {
7033
+ getOpenDocSignal(doc).next(); // Make yHandler reopen the document on server.
7034
+ }
7035
+ }
7036
+ else if (msg.type === 'outdated-server-rev' || msg.type === 'y-complete-sync-done') {
7037
+ // Won't happen but need this for typing.
7038
+ throw new Error('Outdated server revision or y-complete-sync-done not expected over WebSocket - only in sync using fetch()');
7039
+ }
6985
7040
  else if (msg.type !== 'pong') {
7041
+ // Forward the request to our subscriber, wich is in messageFromServerQueue.ts (via connectWebSocket's subscribe() at the end!)
6986
7042
  this.subscriber.next(msg);
6987
7043
  }
6988
7044
  }
@@ -7019,6 +7075,10 @@
7019
7075
  }
7020
7076
  console.debug('dexie-cloud WebSocket send', msg.type, msg);
7021
7077
  if (msg.type === 'ready') {
7078
+ // Ok, we are certain to have stored everything up until revision msg.rev.
7079
+ // Update this.rev in case of reconnect - remember where we were and don't just start over!
7080
+ this.rev = msg.rev;
7081
+ // ... and then send along the request to the server so it would also be updated!
7022
7082
  (_a = this.ws) === null || _a === void 0 ? void 0 : _a.send(TSON.stringify(msg));
7023
7083
  }
7024
7084
  else {
@@ -7926,14 +7986,14 @@
7926
7986
  return; // The table that holds the doc is not marked for sync - leave it to dexie. No syncing, no awareness.
7927
7987
  }
7928
7988
  let awareness;
7929
- Object.defineProperty(provider, "awareness", {
7989
+ Object.defineProperty(provider, 'awareness', {
7930
7990
  get() {
7931
7991
  if (awareness)
7932
7992
  return awareness;
7933
7993
  awareness = createAwareness(db, doc, provider);
7934
7994
  awarenessWeakMap.set(doc, awareness);
7935
7995
  return awareness;
7936
- }
7996
+ },
7937
7997
  });
7938
7998
  };
7939
7999
  }
@@ -7941,6 +8001,7 @@
7941
8001
  const { parentTable, parentId, parentProp, updatesTable } = doc.meta;
7942
8002
  const awap = getAwarenessLibrary(db);
7943
8003
  const awareness = new awap.Awareness(doc);
8004
+ const reopenDocSignal = getOpenDocSignal(doc);
7944
8005
  awareness.on('update', ({ added, updated, removed }, origin) => {
7945
8006
  // Send the update
7946
8007
  const changedClients = added.concat(updated).concat(removed);
@@ -7956,7 +8017,7 @@
7956
8017
  });
7957
8018
  if (provider.destroyed) {
7958
8019
  // We're called from awareness.on('destroy') that did
7959
- // removeAwarenessStates.
8020
+ // removeAwarenessStates.
7960
8021
  // It's time to also send the doc-close message that dexie-cloud understands
7961
8022
  // and uses to stop subscribing for updates and awareness updates and brings
7962
8023
  // down the cached information in memory on the WS connection for this.
@@ -7964,7 +8025,7 @@
7964
8025
  type: 'doc-close',
7965
8026
  table: parentTable,
7966
8027
  prop: parentProp,
7967
- k: doc.meta.parentId
8028
+ k: doc.meta.parentId,
7968
8029
  });
7969
8030
  }
7970
8031
  }
@@ -7980,16 +8041,21 @@
7980
8041
  return;
7981
8042
  let connected = false;
7982
8043
  let currentFlowId = 1;
7983
- const subscription = db.cloud.webSocketStatus.subscribe((wsStatus) => {
8044
+ const subscription = rxjs.combineLatest([
8045
+ db.cloud.webSocketStatus, // Wake up when webSocket status changes
8046
+ reopenDocSignal.pipe(rxjs.startWith(null)), // Wake up when reopenDocSignal emits
8047
+ ]).subscribe(([wsStatus]) => {
7984
8048
  if (provider.destroyed)
7985
8049
  return;
7986
8050
  // Keep "connected" state in a variable so we can check it after async operations
7987
8051
  connected = wsStatus === 'connected';
7988
8052
  // We are or got connected. Open the document on the server.
7989
8053
  const user = db.cloud.currentUser.value;
7990
- if (wsStatus === "connected" && user.isLoggedIn && !isEagerSyncDisabled(db)) {
8054
+ if (wsStatus === 'connected' &&
8055
+ user.isLoggedIn &&
8056
+ !isEagerSyncDisabled(db)) {
7991
8057
  ++currentFlowId;
7992
- openDocumentOnServer().catch(error => {
8058
+ openDocumentOnServer().catch((error) => {
7993
8059
  console.warn(`Error catched in createYHandler.ts: ${error}`);
7994
8060
  });
7995
8061
  }
@@ -8008,27 +8074,35 @@
8008
8074
  * case no state vector is sent (then the server already knows us by
8009
8075
  * revision)
8010
8076
  *
8011
- * When server gets the doc-open message, it will authorized us for
8077
+ * When server gets the doc-open message, it will authorize us for
8012
8078
  * whether we are allowed to read / write to this document, and then
8013
8079
  * keep the cached information in memory on the WS connection for this
8014
8080
  * particular document, as well as subscribe to updates and awareness updates
8015
8081
  * from other clients on the document.
8016
8082
  */
8017
- function openDocumentOnServer(wsStatus) {
8083
+ function openDocumentOnServer() {
8018
8084
  return __awaiter(this, void 0, void 0, function* () {
8019
8085
  const myFlow = currentFlowId; // So we can abort when a new flow is started
8020
8086
  const yTbl = db.table(updatesTable);
8021
- const syncState = yield yTbl.get(DEXIE_CLOUD_SYNCER_ID);
8087
+ const syncStateTbl = db.$syncState;
8088
+ const [receivedUntil, yServerRev] = yield db.transaction('r', syncStateTbl, yTbl, () => __awaiter(this, void 0, void 0, function* () {
8089
+ const syncState = yield yTbl.get(DEXIE_CLOUD_SYNCER_ID);
8090
+ const persistedSyncState = yield syncStateTbl.get('syncState');
8091
+ return [
8092
+ (syncState === null || syncState === void 0 ? void 0 : syncState.receivedUntil) || 0,
8093
+ (persistedSyncState === null || persistedSyncState === void 0 ? void 0 : persistedSyncState.yServerRevision) ||
8094
+ (persistedSyncState === null || persistedSyncState === void 0 ? void 0 : persistedSyncState.serverRevision),
8095
+ ];
8096
+ }));
8022
8097
  // After every await, check if we still should be working on this task.
8023
8098
  if (provider.destroyed || currentFlowId !== myFlow || !connected)
8024
8099
  return;
8025
- const receivedUntil = (syncState === null || syncState === void 0 ? void 0 : syncState.receivedUntil) || 0;
8026
8100
  const docOpenMsg = {
8027
8101
  type: 'doc-open',
8028
8102
  table: parentTable,
8029
8103
  prop: parentProp,
8030
8104
  k: parentId,
8031
- serverRev: syncState === null || syncState === void 0 ? void 0 : syncState.serverRev,
8105
+ serverRev: yServerRev,
8032
8106
  };
8033
8107
  const serverUpdatesSinceLastSync = yield yTbl
8034
8108
  .where('i')
@@ -8093,7 +8167,7 @@
8093
8167
  const syncComplete = new rxjs.Subject();
8094
8168
  dexie.cloud = {
8095
8169
  // @ts-ignore
8096
- version: "4.1.0-alpha.20",
8170
+ version: "4.1.0-alpha.23",
8097
8171
  options: Object.assign({}, DEFAULT_OPTIONS),
8098
8172
  schema: null,
8099
8173
  get currentUserId() {
@@ -8395,7 +8469,7 @@
8395
8469
  }
8396
8470
  }
8397
8471
  // @ts-ignore
8398
- dexieCloud.version = "4.1.0-alpha.20";
8472
+ dexieCloud.version = "4.1.0-alpha.23";
8399
8473
  Dexie.Cloud = dexieCloud;
8400
8474
 
8401
8475
  // In case the SW lives for a while, let it reuse already opened connections: