dexie-cloud-addon 4.1.0-alpha.21 → 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.21, Mon Nov 18 2024
11
+ * Version 4.1.0-alpha.23, Wed Nov 27 2024
12
12
  *
13
13
  * https://dexie.org
14
14
  *
@@ -2768,6 +2768,9 @@
2768
2768
  break;
2769
2769
  case 'outdated-server-rev':
2770
2770
  break;
2771
+ case 'y-complete-sync-done':
2772
+ writeVarString(encoder, msg.yServerRev);
2773
+ break;
2771
2774
  default:
2772
2775
  writeAny(encoder, msg.k);
2773
2776
  switch (msg.type) {
@@ -3114,6 +3117,9 @@
3114
3117
  if (type === 'outdated-server-rev') {
3115
3118
  return { type };
3116
3119
  }
3120
+ if (type === 'y-complete-sync-done') {
3121
+ return { type, yServerRev: readVarString(decoder) };
3122
+ }
3117
3123
  const table = readVarString(decoder);
3118
3124
  const prop = readVarString(decoder);
3119
3125
  switch (type) {
@@ -4419,6 +4425,7 @@
4419
4425
  lastPull: syncState
4420
4426
  ? {
4421
4427
  serverRevision: syncState.serverRevision,
4428
+ yServerRevision: syncState.yServerRevision,
4422
4429
  realms: syncState.realms,
4423
4430
  inviteRealms: syncState.inviteRealms,
4424
4431
  }
@@ -4766,6 +4773,7 @@
4766
4773
  var _a;
4767
4774
  const receivedUntils = {};
4768
4775
  let resyncNeeded = false;
4776
+ let yServerRevision;
4769
4777
  for (const m of yMessages) {
4770
4778
  switch (m.type) {
4771
4779
  case 'u-s': {
@@ -4822,6 +4830,10 @@
4822
4830
  }
4823
4831
  break;
4824
4832
  }
4833
+ case 'y-complete-sync-done': {
4834
+ yServerRevision = m.yServerRev;
4835
+ break;
4836
+ }
4825
4837
  case 'outdated-server-rev':
4826
4838
  resyncNeeded = true;
4827
4839
  break;
@@ -4830,11 +4842,12 @@
4830
4842
  return {
4831
4843
  receivedUntils,
4832
4844
  resyncNeeded,
4845
+ yServerRevision
4833
4846
  };
4834
4847
  });
4835
4848
  }
4836
4849
 
4837
- function updateYSyncStates(lastUpdateIdsBeforeSync, receivedUntilsAfterSync, db, serverRevision) {
4850
+ function updateYSyncStates(lastUpdateIdsBeforeSync, receivedUntilsAfterSync, db) {
4838
4851
  return __awaiter(this, void 0, void 0, function* () {
4839
4852
  var _a, _b, _c, _d, _e;
4840
4853
  // We want to update unsentFrom for each yTable to the value specified in first argument
@@ -4883,14 +4896,12 @@
4883
4896
  yield db.table(yTable).add({
4884
4897
  i: DEXIE_CLOUD_SYNCER_ID,
4885
4898
  unsentFrom,
4886
- receivedUntil,
4887
- serverRev: serverRevision,
4899
+ receivedUntil
4888
4900
  });
4889
4901
  }
4890
4902
  else {
4891
4903
  state.unsentFrom = Math.max(unsentFrom, state.unsentFrom || 1);
4892
4904
  state.receivedUntil = Math.max(receivedUntil, state.receivedUntil || 0);
4893
- state.serverRev = serverRevision;
4894
4905
  yield db.table(yTable).put(state);
4895
4906
  }
4896
4907
  }));
@@ -5226,6 +5237,7 @@
5226
5237
  newSyncState.realms = res.realms;
5227
5238
  newSyncState.inviteRealms = res.inviteRealms;
5228
5239
  newSyncState.serverRevision = res.serverRevision;
5240
+ newSyncState.yServerRevision = res.serverRevision;
5229
5241
  newSyncState.timestamp = new Date();
5230
5242
  delete newSyncState.error;
5231
5243
  const filteredChanges = filterServerChangesThroughAddedClientChanges(res.changes, addedClientChanges);
@@ -5237,11 +5249,14 @@
5237
5249
  //
5238
5250
  // apply yMessages
5239
5251
  //
5240
- const { receivedUntils, resyncNeeded } = yield applyYServerMessages(res.yMessages, db);
5252
+ const { receivedUntils, resyncNeeded, yServerRevision } = yield applyYServerMessages(res.yMessages, db);
5253
+ if (yServerRevision) {
5254
+ newSyncState.yServerRevision = yServerRevision;
5255
+ }
5241
5256
  //
5242
5257
  // update Y SyncStates
5243
5258
  //
5244
- yield updateYSyncStates(lastUpdateIds, receivedUntils, db, res.serverRevision);
5259
+ yield updateYSyncStates(lastUpdateIds, receivedUntils, db);
5245
5260
  if (resyncNeeded) {
5246
5261
  newSyncState.yDownloadedRealms = {}; // Will trigger a full download of Y-documents below...
5247
5262
  }
@@ -5537,7 +5552,7 @@
5537
5552
  };
5538
5553
  }
5539
5554
 
5540
- const wm$1 = new WeakMap();
5555
+ const wm$2 = new WeakMap();
5541
5556
  const DEXIE_CLOUD_SCHEMA = {
5542
5557
  members: '@id, [userId+realmId], [email+realmId], realmId',
5543
5558
  roles: '[realmId+name]',
@@ -5551,7 +5566,7 @@
5551
5566
  function DexieCloudDB(dx) {
5552
5567
  if ('vip' in dx)
5553
5568
  dx = dx['vip']; // Avoid race condition. Always map to a vipped dexie that don't block during db.on.ready().
5554
- let db = wm$1.get(dx.cloud);
5569
+ let db = wm$2.get(dx.cloud);
5555
5570
  if (!db) {
5556
5571
  const localSyncEvent = new rxjs.Subject();
5557
5572
  let syncStateChangedEvent = new BroadcastedAndLocalEvent(`syncstatechanged-${dx.name}`);
@@ -5639,7 +5654,7 @@
5639
5654
  Object.assign(db, helperMethods);
5640
5655
  db.messageConsumer = MessagesFromServerConsumer(db);
5641
5656
  db.messageProducer = new rxjs.Subject();
5642
- wm$1.set(dx.cloud, db);
5657
+ wm$2.set(dx.cloud, db);
5643
5658
  }
5644
5659
  return db;
5645
5660
  }
@@ -5650,10 +5665,10 @@
5650
5665
  }
5651
5666
 
5652
5667
  // Emulate true-private property db. Why? So it's not stored in DB.
5653
- const wm = new WeakMap();
5668
+ const wm$1 = new WeakMap();
5654
5669
  class AuthPersistedContext {
5655
5670
  constructor(db, userLogin) {
5656
- wm.set(this, db);
5671
+ wm$1.set(this, db);
5657
5672
  Object.assign(this, userLogin);
5658
5673
  }
5659
5674
  static load(db, userId) {
@@ -5670,7 +5685,7 @@
5670
5685
  }
5671
5686
  save() {
5672
5687
  return __awaiter(this, void 0, void 0, function* () {
5673
- const db = wm.get(this);
5688
+ const db = wm$1.get(this);
5674
5689
  db.table("$logins").put(this);
5675
5690
  });
5676
5691
  }
@@ -6805,9 +6820,7 @@
6805
6820
  // If messageProducer emits empty array, nothing is emitted
6806
6821
  // but if messageProducer emits array of messages, they are
6807
6822
  // emitted one by one.
6808
- rxjs.mergeMap((messages) => messages), rxjs.tap((message) => {
6809
- console.debug('dexie-cloud emitting y-c', message);
6810
- }));
6823
+ rxjs.mergeMap((messages) => messages));
6811
6824
  }
6812
6825
 
6813
6826
  function getAwarenessLibrary(db) {
@@ -6820,6 +6833,23 @@
6820
6833
  const awarenessWeakMap = new WeakMap();
6821
6834
  const getDocAwareness = (doc) => awarenessWeakMap.get(doc);
6822
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
+
6823
6853
  const SERVER_PING_TIMEOUT = 20000;
6824
6854
  const CLIENT_PING_INTERVAL = 30000;
6825
6855
  const FAIL_RETRY_WAIT_TIME = 60000;
@@ -6982,9 +7012,6 @@
6982
7012
  if (msg.type === 'error') {
6983
7013
  throw new Error(`Error message from dexie-cloud: ${msg.error}`);
6984
7014
  }
6985
- else if (msg.type === 'rev') {
6986
- this.rev = msg.rev; // No meaning but seems reasonable.
6987
- }
6988
7015
  else if (msg.type === 'aware') {
6989
7016
  const docCache = Dexie.DexieYProvider.getDocCache(this.db.dx);
6990
7017
  const doc = docCache.find(msg.table, msg.k, msg.prop);
@@ -6999,11 +7026,19 @@
6999
7026
  else if (msg.type === 'u-ack' || msg.type === 'u-reject' || msg.type === 'u-s' || msg.type === 'in-sync') {
7000
7027
  applyYServerMessages([msg], this.db);
7001
7028
  }
7002
- else if (msg.type === 'outdated-server-rev') {
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') {
7003
7037
  // Won't happen but need this for typing.
7004
- throw new Error('Outdated server revision not expected over WebSocket - only in sync using fetch()');
7038
+ throw new Error('Outdated server revision or y-complete-sync-done not expected over WebSocket - only in sync using fetch()');
7005
7039
  }
7006
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!)
7007
7042
  this.subscriber.next(msg);
7008
7043
  }
7009
7044
  }
@@ -7040,24 +7075,21 @@
7040
7075
  }
7041
7076
  console.debug('dexie-cloud WebSocket send', msg.type, msg);
7042
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!
7043
7082
  (_a = this.ws) === null || _a === void 0 ? void 0 : _a.send(TSON.stringify(msg));
7044
7083
  }
7045
7084
  else {
7046
7085
  // If it's not a "ready" message, it's an YMessage.
7047
7086
  // YMessages can be sent binary encoded.
7048
- if (msg.type === 'u-c') {
7049
- console.log("u-c:B", ++gotClientUpdateB);
7050
- }
7051
7087
  (_b = this.ws) === null || _b === void 0 ? void 0 : _b.send(encodeYMessage(msg));
7052
7088
  }
7053
7089
  }
7054
7090
  }));
7055
7091
  if (this.user.isLoggedIn && !isEagerSyncDisabled(this.db)) {
7056
- this.subscriptions.add(createYClientUpdateObservable(this.db).pipe(rxjs.tap((msg) => {
7057
- if (msg.type === 'u-c') {
7058
- console.log("u-c:A", ++gotClientUpdateA, msg.i);
7059
- }
7060
- })).subscribe(this.db.messageProducer));
7092
+ this.subscriptions.add(createYClientUpdateObservable(this.db).subscribe(this.db.messageProducer));
7061
7093
  }
7062
7094
  }
7063
7095
  catch (error) {
@@ -7066,8 +7098,6 @@
7066
7098
  });
7067
7099
  }
7068
7100
  }
7069
- let gotClientUpdateA = 0;
7070
- let gotClientUpdateB = 0;
7071
7101
 
7072
7102
  class InvalidLicenseError extends Error {
7073
7103
  constructor(license) {
@@ -7956,14 +7986,14 @@
7956
7986
  return; // The table that holds the doc is not marked for sync - leave it to dexie. No syncing, no awareness.
7957
7987
  }
7958
7988
  let awareness;
7959
- Object.defineProperty(provider, "awareness", {
7989
+ Object.defineProperty(provider, 'awareness', {
7960
7990
  get() {
7961
7991
  if (awareness)
7962
7992
  return awareness;
7963
7993
  awareness = createAwareness(db, doc, provider);
7964
7994
  awarenessWeakMap.set(doc, awareness);
7965
7995
  return awareness;
7966
- }
7996
+ },
7967
7997
  });
7968
7998
  };
7969
7999
  }
@@ -7971,6 +8001,7 @@
7971
8001
  const { parentTable, parentId, parentProp, updatesTable } = doc.meta;
7972
8002
  const awap = getAwarenessLibrary(db);
7973
8003
  const awareness = new awap.Awareness(doc);
8004
+ const reopenDocSignal = getOpenDocSignal(doc);
7974
8005
  awareness.on('update', ({ added, updated, removed }, origin) => {
7975
8006
  // Send the update
7976
8007
  const changedClients = added.concat(updated).concat(removed);
@@ -7986,7 +8017,7 @@
7986
8017
  });
7987
8018
  if (provider.destroyed) {
7988
8019
  // We're called from awareness.on('destroy') that did
7989
- // removeAwarenessStates.
8020
+ // removeAwarenessStates.
7990
8021
  // It's time to also send the doc-close message that dexie-cloud understands
7991
8022
  // and uses to stop subscribing for updates and awareness updates and brings
7992
8023
  // down the cached information in memory on the WS connection for this.
@@ -7994,7 +8025,7 @@
7994
8025
  type: 'doc-close',
7995
8026
  table: parentTable,
7996
8027
  prop: parentProp,
7997
- k: doc.meta.parentId
8028
+ k: doc.meta.parentId,
7998
8029
  });
7999
8030
  }
8000
8031
  }
@@ -8010,16 +8041,21 @@
8010
8041
  return;
8011
8042
  let connected = false;
8012
8043
  let currentFlowId = 1;
8013
- 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]) => {
8014
8048
  if (provider.destroyed)
8015
8049
  return;
8016
8050
  // Keep "connected" state in a variable so we can check it after async operations
8017
8051
  connected = wsStatus === 'connected';
8018
8052
  // We are or got connected. Open the document on the server.
8019
8053
  const user = db.cloud.currentUser.value;
8020
- if (wsStatus === "connected" && user.isLoggedIn && !isEagerSyncDisabled(db)) {
8054
+ if (wsStatus === 'connected' &&
8055
+ user.isLoggedIn &&
8056
+ !isEagerSyncDisabled(db)) {
8021
8057
  ++currentFlowId;
8022
- openDocumentOnServer().catch(error => {
8058
+ openDocumentOnServer().catch((error) => {
8023
8059
  console.warn(`Error catched in createYHandler.ts: ${error}`);
8024
8060
  });
8025
8061
  }
@@ -8038,27 +8074,35 @@
8038
8074
  * case no state vector is sent (then the server already knows us by
8039
8075
  * revision)
8040
8076
  *
8041
- * 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
8042
8078
  * whether we are allowed to read / write to this document, and then
8043
8079
  * keep the cached information in memory on the WS connection for this
8044
8080
  * particular document, as well as subscribe to updates and awareness updates
8045
8081
  * from other clients on the document.
8046
8082
  */
8047
- function openDocumentOnServer(wsStatus) {
8083
+ function openDocumentOnServer() {
8048
8084
  return __awaiter(this, void 0, void 0, function* () {
8049
8085
  const myFlow = currentFlowId; // So we can abort when a new flow is started
8050
8086
  const yTbl = db.table(updatesTable);
8051
- 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
+ }));
8052
8097
  // After every await, check if we still should be working on this task.
8053
8098
  if (provider.destroyed || currentFlowId !== myFlow || !connected)
8054
8099
  return;
8055
- const receivedUntil = (syncState === null || syncState === void 0 ? void 0 : syncState.receivedUntil) || 0;
8056
8100
  const docOpenMsg = {
8057
8101
  type: 'doc-open',
8058
8102
  table: parentTable,
8059
8103
  prop: parentProp,
8060
8104
  k: parentId,
8061
- serverRev: syncState === null || syncState === void 0 ? void 0 : syncState.serverRev,
8105
+ serverRev: yServerRev,
8062
8106
  };
8063
8107
  const serverUpdatesSinceLastSync = yield yTbl
8064
8108
  .where('i')
@@ -8123,7 +8167,7 @@
8123
8167
  const syncComplete = new rxjs.Subject();
8124
8168
  dexie.cloud = {
8125
8169
  // @ts-ignore
8126
- version: "4.1.0-alpha.21",
8170
+ version: "4.1.0-alpha.23",
8127
8171
  options: Object.assign({}, DEFAULT_OPTIONS),
8128
8172
  schema: null,
8129
8173
  get currentUserId() {
@@ -8425,7 +8469,7 @@
8425
8469
  }
8426
8470
  }
8427
8471
  // @ts-ignore
8428
- dexieCloud.version = "4.1.0-alpha.21";
8472
+ dexieCloud.version = "4.1.0-alpha.23";
8429
8473
  Dexie.Cloud = dexieCloud;
8430
8474
 
8431
8475
  // In case the SW lives for a while, let it reuse already opened connections: