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.
@@ -1,5 +1,6 @@
1
1
  export interface PersistedSyncState {
2
2
  serverRevision?: any;
3
+ yServerRevision?: string;
3
4
  latestRevisions: {
4
5
  [tableName: string]: number;
5
6
  };
@@ -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 { 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';
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, startWith as startWith$1 } from 'rxjs';
21
21
 
22
22
  /******************************************************************************
23
23
  Copyright (c) Microsoft Corporation.
@@ -979,13 +979,20 @@ const writeAny = (encoder, data) => {
979
979
  function encodeYMessage(msg) {
980
980
  const encoder = new Encoder();
981
981
  writeVarString(encoder, msg.type);
982
- writeVarString(encoder, msg.table);
983
- writeVarString(encoder, msg.prop);
982
+ if ('table' in msg)
983
+ writeVarString(encoder, msg.table);
984
+ if ('prop' in msg)
985
+ writeVarString(encoder, msg.prop);
984
986
  switch (msg.type) {
985
987
  case 'u-ack':
986
988
  case 'u-reject':
987
989
  writeBigUint64(encoder, BigInt(msg.i));
988
990
  break;
991
+ case 'outdated-server-rev':
992
+ break;
993
+ case 'y-complete-sync-done':
994
+ writeVarString(encoder, msg.yServerRev);
995
+ break;
989
996
  default:
990
997
  writeAny(encoder, msg.k);
991
998
  switch (msg.type) {
@@ -1329,6 +1336,12 @@ const readAny = decoder => readAnyLookupTable[127 - readUint8(decoder)](decoder)
1329
1336
  function decodeYMessage(a) {
1330
1337
  const decoder = new Decoder(a);
1331
1338
  const type = readVarString(decoder);
1339
+ if (type === 'outdated-server-rev') {
1340
+ return { type };
1341
+ }
1342
+ if (type === 'y-complete-sync-done') {
1343
+ return { type, yServerRev: readVarString(decoder) };
1344
+ }
1332
1345
  const table = readVarString(decoder);
1333
1346
  const prop = readVarString(decoder);
1334
1347
  switch (type) {
@@ -3414,10 +3427,10 @@ function formatAsPem(str) {
3414
3427
  }
3415
3428
 
3416
3429
  // Emulate true-private property db. Why? So it's not stored in DB.
3417
- const wm$1 = new WeakMap();
3430
+ const wm$2 = new WeakMap();
3418
3431
  class AuthPersistedContext {
3419
3432
  constructor(db, userLogin) {
3420
- wm$1.set(this, db);
3433
+ wm$2.set(this, db);
3421
3434
  Object.assign(this, userLogin);
3422
3435
  }
3423
3436
  static load(db, userId) {
@@ -3434,7 +3447,7 @@ class AuthPersistedContext {
3434
3447
  }
3435
3448
  save() {
3436
3449
  return __awaiter(this, void 0, void 0, function* () {
3437
- const db = wm$1.get(this);
3450
+ const db = wm$2.get(this);
3438
3451
  db.table("$logins").put(this);
3439
3452
  });
3440
3453
  }
@@ -4715,6 +4728,7 @@ function syncWithServer(changes, y, syncState, baseRevs, db, databaseUrl, schema
4715
4728
  lastPull: syncState
4716
4729
  ? {
4717
4730
  serverRevision: syncState.serverRevision,
4731
+ yServerRevision: syncState.yServerRevision,
4718
4732
  realms: syncState.realms,
4719
4733
  inviteRealms: syncState.inviteRealms,
4720
4734
  }
@@ -5060,12 +5074,14 @@ function getUpdatesTable(db, table, ydocProp) {
5060
5074
  function applyYServerMessages(yMessages, db) {
5061
5075
  return __awaiter(this, void 0, void 0, function* () {
5062
5076
  var _a;
5063
- const result = {};
5077
+ const receivedUntils = {};
5078
+ let resyncNeeded = false;
5079
+ let yServerRevision;
5064
5080
  for (const m of yMessages) {
5065
5081
  switch (m.type) {
5066
5082
  case 'u-s': {
5067
5083
  const utbl = getUpdatesTable(db, m.table, m.prop);
5068
- result[utbl.name] = yield utbl.add({
5084
+ receivedUntils[utbl.name] = yield utbl.add({
5069
5085
  k: m.k,
5070
5086
  u: m.u,
5071
5087
  });
@@ -5094,13 +5110,13 @@ function applyYServerMessages(yMessages, db) {
5094
5110
  // and destroy it's open document if there is one.
5095
5111
  const primaryKey = (_a = (yield utbl.get(m.i))) === null || _a === void 0 ? void 0 : _a.k;
5096
5112
  if (primaryKey != null) {
5097
- yield db.transaction('rw', utbl, tx => {
5113
+ yield db.transaction('rw', utbl, (tx) => {
5098
5114
  // @ts-ignore
5099
5115
  tx.idbtrans._rejecting_y_ypdate = true; // Inform ydoc triggers that we delete because of a rejection and not GC
5100
5116
  return utbl
5101
5117
  .where('i')
5102
5118
  .aboveOrEqual(m.i)
5103
- .filter(u => cmp(u.k, primaryKey) === 0 && ((u.f || 0) & 1) === 1)
5119
+ .filter((u) => cmp(u.k, primaryKey) === 0 && ((u.f || 0) & 1) === 1)
5104
5120
  .delete();
5105
5121
  });
5106
5122
  // Destroy active doc
@@ -5117,13 +5133,24 @@ function applyYServerMessages(yMessages, db) {
5117
5133
  }
5118
5134
  break;
5119
5135
  }
5136
+ case 'y-complete-sync-done': {
5137
+ yServerRevision = m.yServerRev;
5138
+ break;
5139
+ }
5140
+ case 'outdated-server-rev':
5141
+ resyncNeeded = true;
5142
+ break;
5120
5143
  }
5121
5144
  }
5122
- return result;
5145
+ return {
5146
+ receivedUntils,
5147
+ resyncNeeded,
5148
+ yServerRevision
5149
+ };
5123
5150
  });
5124
5151
  }
5125
5152
 
5126
- function updateYSyncStates(lastUpdateIdsBeforeSync, receivedUntilsAfterSync, db, serverRevision) {
5153
+ function updateYSyncStates(lastUpdateIdsBeforeSync, receivedUntilsAfterSync, db) {
5127
5154
  return __awaiter(this, void 0, void 0, function* () {
5128
5155
  var _a, _b, _c, _d, _e;
5129
5156
  // We want to update unsentFrom for each yTable to the value specified in first argument
@@ -5172,14 +5199,12 @@ function updateYSyncStates(lastUpdateIdsBeforeSync, receivedUntilsAfterSync, db,
5172
5199
  yield db.table(yTable).add({
5173
5200
  i: DEXIE_CLOUD_SYNCER_ID,
5174
5201
  unsentFrom,
5175
- receivedUntil,
5176
- serverRev: serverRevision,
5202
+ receivedUntil
5177
5203
  });
5178
5204
  }
5179
5205
  else {
5180
5206
  state.unsentFrom = Math.max(unsentFrom, state.unsentFrom || 1);
5181
5207
  state.receivedUntil = Math.max(receivedUntil, state.receivedUntil || 0);
5182
- state.serverRev = serverRevision;
5183
5208
  yield db.table(yTable).put(state);
5184
5209
  }
5185
5210
  }));
@@ -5515,6 +5540,7 @@ function _sync(db_1, options_1, schema_1) {
5515
5540
  newSyncState.realms = res.realms;
5516
5541
  newSyncState.inviteRealms = res.inviteRealms;
5517
5542
  newSyncState.serverRevision = res.serverRevision;
5543
+ newSyncState.yServerRevision = res.serverRevision;
5518
5544
  newSyncState.timestamp = new Date();
5519
5545
  delete newSyncState.error;
5520
5546
  const filteredChanges = filterServerChangesThroughAddedClientChanges(res.changes, addedClientChanges);
@@ -5526,11 +5552,17 @@ function _sync(db_1, options_1, schema_1) {
5526
5552
  //
5527
5553
  // apply yMessages
5528
5554
  //
5529
- const receivedUntils = yield applyYServerMessages(res.yMessages, db);
5555
+ const { receivedUntils, resyncNeeded, yServerRevision } = yield applyYServerMessages(res.yMessages, db);
5556
+ if (yServerRevision) {
5557
+ newSyncState.yServerRevision = yServerRevision;
5558
+ }
5530
5559
  //
5531
5560
  // update Y SyncStates
5532
5561
  //
5533
- yield updateYSyncStates(lastUpdateIds, receivedUntils, db, res.serverRevision);
5562
+ yield updateYSyncStates(lastUpdateIds, receivedUntils, db);
5563
+ if (resyncNeeded) {
5564
+ newSyncState.yDownloadedRealms = {}; // Will trigger a full download of Y-documents below...
5565
+ }
5534
5566
  }
5535
5567
  //
5536
5568
  // Update regular syncState
@@ -5823,7 +5855,7 @@ function MessagesFromServerConsumer(db) {
5823
5855
  };
5824
5856
  }
5825
5857
 
5826
- const wm = new WeakMap();
5858
+ const wm$1 = new WeakMap();
5827
5859
  const DEXIE_CLOUD_SCHEMA = {
5828
5860
  members: '@id, [userId+realmId], [email+realmId], realmId',
5829
5861
  roles: '[realmId+name]',
@@ -5837,7 +5869,7 @@ let static_counter = 0;
5837
5869
  function DexieCloudDB(dx) {
5838
5870
  if ('vip' in dx)
5839
5871
  dx = dx['vip']; // Avoid race condition. Always map to a vipped dexie that don't block during db.on.ready().
5840
- let db = wm.get(dx.cloud);
5872
+ let db = wm$1.get(dx.cloud);
5841
5873
  if (!db) {
5842
5874
  const localSyncEvent = new Subject();
5843
5875
  let syncStateChangedEvent = new BroadcastedAndLocalEvent(`syncstatechanged-${dx.name}`);
@@ -5925,7 +5957,7 @@ function DexieCloudDB(dx) {
5925
5957
  Object.assign(db, helperMethods);
5926
5958
  db.messageConsumer = MessagesFromServerConsumer(db);
5927
5959
  db.messageProducer = new Subject();
5928
- wm.set(dx.cloud, db);
5960
+ wm$1.set(dx.cloud, db);
5929
5961
  }
5930
5962
  return db;
5931
5963
  }
@@ -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')
@@ -8097,7 +8171,7 @@ function dexieCloud(dexie) {
8097
8171
  const syncComplete = new Subject();
8098
8172
  dexie.cloud = {
8099
8173
  // @ts-ignore
8100
- version: "4.1.0-alpha.20",
8174
+ version: "4.1.0-alpha.23",
8101
8175
  options: Object.assign({}, DEFAULT_OPTIONS),
8102
8176
  schema: null,
8103
8177
  get currentUserId() {
@@ -8399,7 +8473,7 @@ function dexieCloud(dexie) {
8399
8473
  }
8400
8474
  }
8401
8475
  // @ts-ignore
8402
- dexieCloud.version = "4.1.0-alpha.20";
8476
+ dexieCloud.version = "4.1.0-alpha.23";
8403
8477
  Dexie.Cloud = dexieCloud;
8404
8478
 
8405
8479
  const ydocTriggers = {};