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
  *
@@ -17,7 +17,7 @@
17
17
  */
18
18
 
19
19
  import Dexie, { PropModification, cmp, DexieYProvider, RangeSet, liveQuery } from 'dexie';
20
- import { Observable as Observable$1, BehaviorSubject, firstValueFrom, Subject, from as from$1, filter as filter$1, 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';
20
+ import { Observable as Observable$1, BehaviorSubject, firstValueFrom, Subject, from as from$1, filter as filter$1, 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.
@@ -2754,13 +2754,20 @@ const writeAny = (encoder, data) => {
2754
2754
  function encodeYMessage(msg) {
2755
2755
  const encoder = new Encoder();
2756
2756
  writeVarString(encoder, msg.type);
2757
- writeVarString(encoder, msg.table);
2758
- writeVarString(encoder, msg.prop);
2757
+ if ('table' in msg)
2758
+ writeVarString(encoder, msg.table);
2759
+ if ('prop' in msg)
2760
+ writeVarString(encoder, msg.prop);
2759
2761
  switch (msg.type) {
2760
2762
  case 'u-ack':
2761
2763
  case 'u-reject':
2762
2764
  writeBigUint64(encoder, BigInt(msg.i));
2763
2765
  break;
2766
+ case 'outdated-server-rev':
2767
+ break;
2768
+ case 'y-complete-sync-done':
2769
+ writeVarString(encoder, msg.yServerRev);
2770
+ break;
2764
2771
  default:
2765
2772
  writeAny(encoder, msg.k);
2766
2773
  switch (msg.type) {
@@ -3104,6 +3111,12 @@ const readAny = decoder => readAnyLookupTable[127 - readUint8(decoder)](decoder)
3104
3111
  function decodeYMessage(a) {
3105
3112
  const decoder = new Decoder(a);
3106
3113
  const type = readVarString(decoder);
3114
+ if (type === 'outdated-server-rev') {
3115
+ return { type };
3116
+ }
3117
+ if (type === 'y-complete-sync-done') {
3118
+ return { type, yServerRev: readVarString(decoder) };
3119
+ }
3107
3120
  const table = readVarString(decoder);
3108
3121
  const prop = readVarString(decoder);
3109
3122
  switch (type) {
@@ -4409,6 +4422,7 @@ function syncWithServer(changes, y, syncState, baseRevs, db, databaseUrl, schema
4409
4422
  lastPull: syncState
4410
4423
  ? {
4411
4424
  serverRevision: syncState.serverRevision,
4425
+ yServerRevision: syncState.yServerRevision,
4412
4426
  realms: syncState.realms,
4413
4427
  inviteRealms: syncState.inviteRealms,
4414
4428
  }
@@ -4754,12 +4768,14 @@ function getUpdatesTable(db, table, ydocProp) {
4754
4768
  function applyYServerMessages(yMessages, db) {
4755
4769
  return __awaiter(this, void 0, void 0, function* () {
4756
4770
  var _a;
4757
- const result = {};
4771
+ const receivedUntils = {};
4772
+ let resyncNeeded = false;
4773
+ let yServerRevision;
4758
4774
  for (const m of yMessages) {
4759
4775
  switch (m.type) {
4760
4776
  case 'u-s': {
4761
4777
  const utbl = getUpdatesTable(db, m.table, m.prop);
4762
- result[utbl.name] = yield utbl.add({
4778
+ receivedUntils[utbl.name] = yield utbl.add({
4763
4779
  k: m.k,
4764
4780
  u: m.u,
4765
4781
  });
@@ -4788,13 +4804,13 @@ function applyYServerMessages(yMessages, db) {
4788
4804
  // and destroy it's open document if there is one.
4789
4805
  const primaryKey = (_a = (yield utbl.get(m.i))) === null || _a === void 0 ? void 0 : _a.k;
4790
4806
  if (primaryKey != null) {
4791
- yield db.transaction('rw', utbl, tx => {
4807
+ yield db.transaction('rw', utbl, (tx) => {
4792
4808
  // @ts-ignore
4793
4809
  tx.idbtrans._rejecting_y_ypdate = true; // Inform ydoc triggers that we delete because of a rejection and not GC
4794
4810
  return utbl
4795
4811
  .where('i')
4796
4812
  .aboveOrEqual(m.i)
4797
- .filter(u => cmp(u.k, primaryKey) === 0 && ((u.f || 0) & 1) === 1)
4813
+ .filter((u) => cmp(u.k, primaryKey) === 0 && ((u.f || 0) & 1) === 1)
4798
4814
  .delete();
4799
4815
  });
4800
4816
  // Destroy active doc
@@ -4811,13 +4827,24 @@ function applyYServerMessages(yMessages, db) {
4811
4827
  }
4812
4828
  break;
4813
4829
  }
4830
+ case 'y-complete-sync-done': {
4831
+ yServerRevision = m.yServerRev;
4832
+ break;
4833
+ }
4834
+ case 'outdated-server-rev':
4835
+ resyncNeeded = true;
4836
+ break;
4814
4837
  }
4815
4838
  }
4816
- return result;
4839
+ return {
4840
+ receivedUntils,
4841
+ resyncNeeded,
4842
+ yServerRevision
4843
+ };
4817
4844
  });
4818
4845
  }
4819
4846
 
4820
- function updateYSyncStates(lastUpdateIdsBeforeSync, receivedUntilsAfterSync, db, serverRevision) {
4847
+ function updateYSyncStates(lastUpdateIdsBeforeSync, receivedUntilsAfterSync, db) {
4821
4848
  return __awaiter(this, void 0, void 0, function* () {
4822
4849
  var _a, _b, _c, _d, _e;
4823
4850
  // We want to update unsentFrom for each yTable to the value specified in first argument
@@ -4866,14 +4893,12 @@ function updateYSyncStates(lastUpdateIdsBeforeSync, receivedUntilsAfterSync, db,
4866
4893
  yield db.table(yTable).add({
4867
4894
  i: DEXIE_CLOUD_SYNCER_ID,
4868
4895
  unsentFrom,
4869
- receivedUntil,
4870
- serverRev: serverRevision,
4896
+ receivedUntil
4871
4897
  });
4872
4898
  }
4873
4899
  else {
4874
4900
  state.unsentFrom = Math.max(unsentFrom, state.unsentFrom || 1);
4875
4901
  state.receivedUntil = Math.max(receivedUntil, state.receivedUntil || 0);
4876
- state.serverRev = serverRevision;
4877
4902
  yield db.table(yTable).put(state);
4878
4903
  }
4879
4904
  }));
@@ -5209,6 +5234,7 @@ function _sync(db_1, options_1, schema_1) {
5209
5234
  newSyncState.realms = res.realms;
5210
5235
  newSyncState.inviteRealms = res.inviteRealms;
5211
5236
  newSyncState.serverRevision = res.serverRevision;
5237
+ newSyncState.yServerRevision = res.serverRevision;
5212
5238
  newSyncState.timestamp = new Date();
5213
5239
  delete newSyncState.error;
5214
5240
  const filteredChanges = filterServerChangesThroughAddedClientChanges(res.changes, addedClientChanges);
@@ -5220,11 +5246,17 @@ function _sync(db_1, options_1, schema_1) {
5220
5246
  //
5221
5247
  // apply yMessages
5222
5248
  //
5223
- const receivedUntils = yield applyYServerMessages(res.yMessages, db);
5249
+ const { receivedUntils, resyncNeeded, yServerRevision } = yield applyYServerMessages(res.yMessages, db);
5250
+ if (yServerRevision) {
5251
+ newSyncState.yServerRevision = yServerRevision;
5252
+ }
5224
5253
  //
5225
5254
  // update Y SyncStates
5226
5255
  //
5227
- yield updateYSyncStates(lastUpdateIds, receivedUntils, db, res.serverRevision);
5256
+ yield updateYSyncStates(lastUpdateIds, receivedUntils, db);
5257
+ if (resyncNeeded) {
5258
+ newSyncState.yDownloadedRealms = {}; // Will trigger a full download of Y-documents below...
5259
+ }
5228
5260
  }
5229
5261
  //
5230
5262
  // Update regular syncState
@@ -5517,7 +5549,7 @@ function MessagesFromServerConsumer(db) {
5517
5549
  };
5518
5550
  }
5519
5551
 
5520
- const wm$1 = new WeakMap();
5552
+ const wm$2 = new WeakMap();
5521
5553
  const DEXIE_CLOUD_SCHEMA = {
5522
5554
  members: '@id, [userId+realmId], [email+realmId], realmId',
5523
5555
  roles: '[realmId+name]',
@@ -5531,7 +5563,7 @@ let static_counter = 0;
5531
5563
  function DexieCloudDB(dx) {
5532
5564
  if ('vip' in dx)
5533
5565
  dx = dx['vip']; // Avoid race condition. Always map to a vipped dexie that don't block during db.on.ready().
5534
- let db = wm$1.get(dx.cloud);
5566
+ let db = wm$2.get(dx.cloud);
5535
5567
  if (!db) {
5536
5568
  const localSyncEvent = new Subject();
5537
5569
  let syncStateChangedEvent = new BroadcastedAndLocalEvent(`syncstatechanged-${dx.name}`);
@@ -5619,7 +5651,7 @@ function DexieCloudDB(dx) {
5619
5651
  Object.assign(db, helperMethods);
5620
5652
  db.messageConsumer = MessagesFromServerConsumer(db);
5621
5653
  db.messageProducer = new Subject();
5622
- wm$1.set(dx.cloud, db);
5654
+ wm$2.set(dx.cloud, db);
5623
5655
  }
5624
5656
  return db;
5625
5657
  }
@@ -5630,10 +5662,10 @@ function nameFromKeyPath(keyPath) {
5630
5662
  }
5631
5663
 
5632
5664
  // Emulate true-private property db. Why? So it's not stored in DB.
5633
- const wm = new WeakMap();
5665
+ const wm$1 = new WeakMap();
5634
5666
  class AuthPersistedContext {
5635
5667
  constructor(db, userLogin) {
5636
- wm.set(this, db);
5668
+ wm$1.set(this, db);
5637
5669
  Object.assign(this, userLogin);
5638
5670
  }
5639
5671
  static load(db, userId) {
@@ -5650,7 +5682,7 @@ class AuthPersistedContext {
5650
5682
  }
5651
5683
  save() {
5652
5684
  return __awaiter(this, void 0, void 0, function* () {
5653
- const db = wm.get(this);
5685
+ const db = wm$1.get(this);
5654
5686
  db.table("$logins").put(this);
5655
5687
  });
5656
5688
  }
@@ -6785,9 +6817,7 @@ function createYClientUpdateObservable(db) {
6785
6817
  // If messageProducer emits empty array, nothing is emitted
6786
6818
  // but if messageProducer emits array of messages, they are
6787
6819
  // emitted one by one.
6788
- mergeMap$1((messages) => messages), tap$1((message) => {
6789
- console.debug('dexie-cloud emitting y-c', message);
6790
- }));
6820
+ mergeMap$1((messages) => messages));
6791
6821
  }
6792
6822
 
6793
6823
  function getAwarenessLibrary(db) {
@@ -6800,6 +6830,23 @@ function getAwarenessLibrary(db) {
6800
6830
  const awarenessWeakMap = new WeakMap();
6801
6831
  const getDocAwareness = (doc) => awarenessWeakMap.get(doc);
6802
6832
 
6833
+ const wm = new WeakMap();
6834
+ /** A property (package-private) on Y.Doc that is used
6835
+ * to signal that the server wants us to send a 'doc-open' message
6836
+ * to the server for this document.
6837
+ *
6838
+ * @param doc
6839
+ * @returns
6840
+ */
6841
+ function getOpenDocSignal(doc) {
6842
+ let signal = wm.get(doc);
6843
+ if (!signal) {
6844
+ signal = new Subject();
6845
+ wm.set(doc, signal);
6846
+ }
6847
+ return signal;
6848
+ }
6849
+
6803
6850
  const SERVER_PING_TIMEOUT = 20000;
6804
6851
  const CLIENT_PING_INTERVAL = 30000;
6805
6852
  const FAIL_RETRY_WAIT_TIME = 60000;
@@ -6962,9 +7009,6 @@ class WSConnection extends Subscription$1 {
6962
7009
  if (msg.type === 'error') {
6963
7010
  throw new Error(`Error message from dexie-cloud: ${msg.error}`);
6964
7011
  }
6965
- else if (msg.type === 'rev') {
6966
- this.rev = msg.rev; // No meaning but seems reasonable.
6967
- }
6968
7012
  else if (msg.type === 'aware') {
6969
7013
  const docCache = DexieYProvider.getDocCache(this.db.dx);
6970
7014
  const doc = docCache.find(msg.table, msg.k, msg.prop);
@@ -6979,7 +7023,19 @@ class WSConnection extends Subscription$1 {
6979
7023
  else if (msg.type === 'u-ack' || msg.type === 'u-reject' || msg.type === 'u-s' || msg.type === 'in-sync') {
6980
7024
  applyYServerMessages([msg], this.db);
6981
7025
  }
7026
+ else if (msg.type === 'doc-open') {
7027
+ const docCache = DexieYProvider.getDocCache(this.db.dx);
7028
+ const doc = docCache.find(msg.table, msg.k, msg.prop);
7029
+ if (doc) {
7030
+ getOpenDocSignal(doc).next(); // Make yHandler reopen the document on server.
7031
+ }
7032
+ }
7033
+ else if (msg.type === 'outdated-server-rev' || msg.type === 'y-complete-sync-done') {
7034
+ // Won't happen but need this for typing.
7035
+ throw new Error('Outdated server revision or y-complete-sync-done not expected over WebSocket - only in sync using fetch()');
7036
+ }
6982
7037
  else if (msg.type !== 'pong') {
7038
+ // Forward the request to our subscriber, wich is in messageFromServerQueue.ts (via connectWebSocket's subscribe() at the end!)
6983
7039
  this.subscriber.next(msg);
6984
7040
  }
6985
7041
  }
@@ -7016,6 +7072,10 @@ class WSConnection extends Subscription$1 {
7016
7072
  }
7017
7073
  console.debug('dexie-cloud WebSocket send', msg.type, msg);
7018
7074
  if (msg.type === 'ready') {
7075
+ // Ok, we are certain to have stored everything up until revision msg.rev.
7076
+ // Update this.rev in case of reconnect - remember where we were and don't just start over!
7077
+ this.rev = msg.rev;
7078
+ // ... and then send along the request to the server so it would also be updated!
7019
7079
  (_a = this.ws) === null || _a === void 0 ? void 0 : _a.send(TSON.stringify(msg));
7020
7080
  }
7021
7081
  else {
@@ -7923,14 +7983,14 @@ function createYHandler(db) {
7923
7983
  return; // The table that holds the doc is not marked for sync - leave it to dexie. No syncing, no awareness.
7924
7984
  }
7925
7985
  let awareness;
7926
- Object.defineProperty(provider, "awareness", {
7986
+ Object.defineProperty(provider, 'awareness', {
7927
7987
  get() {
7928
7988
  if (awareness)
7929
7989
  return awareness;
7930
7990
  awareness = createAwareness(db, doc, provider);
7931
7991
  awarenessWeakMap.set(doc, awareness);
7932
7992
  return awareness;
7933
- }
7993
+ },
7934
7994
  });
7935
7995
  };
7936
7996
  }
@@ -7938,6 +7998,7 @@ function createAwareness(db, doc, provider) {
7938
7998
  const { parentTable, parentId, parentProp, updatesTable } = doc.meta;
7939
7999
  const awap = getAwarenessLibrary(db);
7940
8000
  const awareness = new awap.Awareness(doc);
8001
+ const reopenDocSignal = getOpenDocSignal(doc);
7941
8002
  awareness.on('update', ({ added, updated, removed }, origin) => {
7942
8003
  // Send the update
7943
8004
  const changedClients = added.concat(updated).concat(removed);
@@ -7953,7 +8014,7 @@ function createAwareness(db, doc, provider) {
7953
8014
  });
7954
8015
  if (provider.destroyed) {
7955
8016
  // We're called from awareness.on('destroy') that did
7956
- // removeAwarenessStates.
8017
+ // removeAwarenessStates.
7957
8018
  // It's time to also send the doc-close message that dexie-cloud understands
7958
8019
  // and uses to stop subscribing for updates and awareness updates and brings
7959
8020
  // down the cached information in memory on the WS connection for this.
@@ -7961,7 +8022,7 @@ function createAwareness(db, doc, provider) {
7961
8022
  type: 'doc-close',
7962
8023
  table: parentTable,
7963
8024
  prop: parentProp,
7964
- k: doc.meta.parentId
8025
+ k: doc.meta.parentId,
7965
8026
  });
7966
8027
  }
7967
8028
  }
@@ -7977,16 +8038,21 @@ function createAwareness(db, doc, provider) {
7977
8038
  return;
7978
8039
  let connected = false;
7979
8040
  let currentFlowId = 1;
7980
- const subscription = db.cloud.webSocketStatus.subscribe((wsStatus) => {
8041
+ const subscription = combineLatest([
8042
+ db.cloud.webSocketStatus, // Wake up when webSocket status changes
8043
+ reopenDocSignal.pipe(startWith$1(null)), // Wake up when reopenDocSignal emits
8044
+ ]).subscribe(([wsStatus]) => {
7981
8045
  if (provider.destroyed)
7982
8046
  return;
7983
8047
  // Keep "connected" state in a variable so we can check it after async operations
7984
8048
  connected = wsStatus === 'connected';
7985
8049
  // We are or got connected. Open the document on the server.
7986
8050
  const user = db.cloud.currentUser.value;
7987
- if (wsStatus === "connected" && user.isLoggedIn && !isEagerSyncDisabled(db)) {
8051
+ if (wsStatus === 'connected' &&
8052
+ user.isLoggedIn &&
8053
+ !isEagerSyncDisabled(db)) {
7988
8054
  ++currentFlowId;
7989
- openDocumentOnServer().catch(error => {
8055
+ openDocumentOnServer().catch((error) => {
7990
8056
  console.warn(`Error catched in createYHandler.ts: ${error}`);
7991
8057
  });
7992
8058
  }
@@ -8005,27 +8071,35 @@ function createAwareness(db, doc, provider) {
8005
8071
  * case no state vector is sent (then the server already knows us by
8006
8072
  * revision)
8007
8073
  *
8008
- * When server gets the doc-open message, it will authorized us for
8074
+ * When server gets the doc-open message, it will authorize us for
8009
8075
  * whether we are allowed to read / write to this document, and then
8010
8076
  * keep the cached information in memory on the WS connection for this
8011
8077
  * particular document, as well as subscribe to updates and awareness updates
8012
8078
  * from other clients on the document.
8013
8079
  */
8014
- function openDocumentOnServer(wsStatus) {
8080
+ function openDocumentOnServer() {
8015
8081
  return __awaiter(this, void 0, void 0, function* () {
8016
8082
  const myFlow = currentFlowId; // So we can abort when a new flow is started
8017
8083
  const yTbl = db.table(updatesTable);
8018
- const syncState = yield yTbl.get(DEXIE_CLOUD_SYNCER_ID);
8084
+ const syncStateTbl = db.$syncState;
8085
+ const [receivedUntil, yServerRev] = yield db.transaction('r', syncStateTbl, yTbl, () => __awaiter(this, void 0, void 0, function* () {
8086
+ const syncState = yield yTbl.get(DEXIE_CLOUD_SYNCER_ID);
8087
+ const persistedSyncState = yield syncStateTbl.get('syncState');
8088
+ return [
8089
+ (syncState === null || syncState === void 0 ? void 0 : syncState.receivedUntil) || 0,
8090
+ (persistedSyncState === null || persistedSyncState === void 0 ? void 0 : persistedSyncState.yServerRevision) ||
8091
+ (persistedSyncState === null || persistedSyncState === void 0 ? void 0 : persistedSyncState.serverRevision),
8092
+ ];
8093
+ }));
8019
8094
  // After every await, check if we still should be working on this task.
8020
8095
  if (provider.destroyed || currentFlowId !== myFlow || !connected)
8021
8096
  return;
8022
- const receivedUntil = (syncState === null || syncState === void 0 ? void 0 : syncState.receivedUntil) || 0;
8023
8097
  const docOpenMsg = {
8024
8098
  type: 'doc-open',
8025
8099
  table: parentTable,
8026
8100
  prop: parentProp,
8027
8101
  k: parentId,
8028
- serverRev: syncState === null || syncState === void 0 ? void 0 : syncState.serverRev,
8102
+ serverRev: yServerRev,
8029
8103
  };
8030
8104
  const serverUpdatesSinceLastSync = yield yTbl
8031
8105
  .where('i')
@@ -8090,7 +8164,7 @@ function dexieCloud(dexie) {
8090
8164
  const syncComplete = new Subject();
8091
8165
  dexie.cloud = {
8092
8166
  // @ts-ignore
8093
- version: "4.1.0-alpha.20",
8167
+ version: "4.1.0-alpha.23",
8094
8168
  options: Object.assign({}, DEFAULT_OPTIONS),
8095
8169
  schema: null,
8096
8170
  get currentUserId() {
@@ -8392,7 +8466,7 @@ function dexieCloud(dexie) {
8392
8466
  }
8393
8467
  }
8394
8468
  // @ts-ignore
8395
- dexieCloud.version = "4.1.0-alpha.20";
8469
+ dexieCloud.version = "4.1.0-alpha.23";
8396
8470
  Dexie.Cloud = dexieCloud;
8397
8471
 
8398
8472
  // In case the SW lives for a while, let it reuse already opened connections: