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.
@@ -1,4 +1,3 @@
1
1
  import { YSyncState } from "dexie";
2
2
  export interface YDexieCloudSyncState extends YSyncState {
3
- serverRev: string;
4
3
  }
@@ -5,4 +5,5 @@ export declare function applyYServerMessages(yMessages: YServerMessage[], db: De
5
5
  [yTable: string]: number;
6
6
  };
7
7
  resyncNeeded: boolean;
8
+ yServerRevision?: string;
8
9
  }>;
@@ -0,0 +1,10 @@
1
+ import { YjsDoc } from "dexie";
2
+ import { Subject } from "rxjs";
3
+ /** A property (package-private) on Y.Doc that is used
4
+ * to signal that the server wants us to send a 'doc-open' message
5
+ * to the server for this document.
6
+ *
7
+ * @param doc
8
+ * @returns
9
+ */
10
+ export declare function getOpenDocSignal(doc: YjsDoc): Subject<void>;
@@ -3,4 +3,4 @@ export declare function updateYSyncStates(lastUpdateIdsBeforeSync: {
3
3
  [yTable: string]: number;
4
4
  }, receivedUntilsAfterSync: {
5
5
  [yTable: string]: number;
6
- }, db: DexieCloudDB, serverRevision: string): Promise<void>;
6
+ }, db: DexieCloudDB): Promise<void>;
@@ -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.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
  *
@@ -993,6 +993,9 @@
993
993
  break;
994
994
  case 'outdated-server-rev':
995
995
  break;
996
+ case 'y-complete-sync-done':
997
+ writeVarString(encoder, msg.yServerRev);
998
+ break;
996
999
  default:
997
1000
  writeAny(encoder, msg.k);
998
1001
  switch (msg.type) {
@@ -1339,6 +1342,9 @@
1339
1342
  if (type === 'outdated-server-rev') {
1340
1343
  return { type };
1341
1344
  }
1345
+ if (type === 'y-complete-sync-done') {
1346
+ return { type, yServerRev: readVarString(decoder) };
1347
+ }
1342
1348
  const table = readVarString(decoder);
1343
1349
  const prop = readVarString(decoder);
1344
1350
  switch (type) {
@@ -3424,10 +3430,10 @@
3424
3430
  }
3425
3431
 
3426
3432
  // Emulate true-private property db. Why? So it's not stored in DB.
3427
- const wm$1 = new WeakMap();
3433
+ const wm$2 = new WeakMap();
3428
3434
  class AuthPersistedContext {
3429
3435
  constructor(db, userLogin) {
3430
- wm$1.set(this, db);
3436
+ wm$2.set(this, db);
3431
3437
  Object.assign(this, userLogin);
3432
3438
  }
3433
3439
  static load(db, userId) {
@@ -3444,7 +3450,7 @@
3444
3450
  }
3445
3451
  save() {
3446
3452
  return __awaiter(this, void 0, void 0, function* () {
3447
- const db = wm$1.get(this);
3453
+ const db = wm$2.get(this);
3448
3454
  db.table("$logins").put(this);
3449
3455
  });
3450
3456
  }
@@ -4725,6 +4731,7 @@
4725
4731
  lastPull: syncState
4726
4732
  ? {
4727
4733
  serverRevision: syncState.serverRevision,
4734
+ yServerRevision: syncState.yServerRevision,
4728
4735
  realms: syncState.realms,
4729
4736
  inviteRealms: syncState.inviteRealms,
4730
4737
  }
@@ -5072,6 +5079,7 @@
5072
5079
  var _a;
5073
5080
  const receivedUntils = {};
5074
5081
  let resyncNeeded = false;
5082
+ let yServerRevision;
5075
5083
  for (const m of yMessages) {
5076
5084
  switch (m.type) {
5077
5085
  case 'u-s': {
@@ -5128,6 +5136,10 @@
5128
5136
  }
5129
5137
  break;
5130
5138
  }
5139
+ case 'y-complete-sync-done': {
5140
+ yServerRevision = m.yServerRev;
5141
+ break;
5142
+ }
5131
5143
  case 'outdated-server-rev':
5132
5144
  resyncNeeded = true;
5133
5145
  break;
@@ -5136,11 +5148,12 @@
5136
5148
  return {
5137
5149
  receivedUntils,
5138
5150
  resyncNeeded,
5151
+ yServerRevision
5139
5152
  };
5140
5153
  });
5141
5154
  }
5142
5155
 
5143
- function updateYSyncStates(lastUpdateIdsBeforeSync, receivedUntilsAfterSync, db, serverRevision) {
5156
+ function updateYSyncStates(lastUpdateIdsBeforeSync, receivedUntilsAfterSync, db) {
5144
5157
  return __awaiter(this, void 0, void 0, function* () {
5145
5158
  var _a, _b, _c, _d, _e;
5146
5159
  // We want to update unsentFrom for each yTable to the value specified in first argument
@@ -5189,14 +5202,12 @@
5189
5202
  yield db.table(yTable).add({
5190
5203
  i: DEXIE_CLOUD_SYNCER_ID,
5191
5204
  unsentFrom,
5192
- receivedUntil,
5193
- serverRev: serverRevision,
5205
+ receivedUntil
5194
5206
  });
5195
5207
  }
5196
5208
  else {
5197
5209
  state.unsentFrom = Math.max(unsentFrom, state.unsentFrom || 1);
5198
5210
  state.receivedUntil = Math.max(receivedUntil, state.receivedUntil || 0);
5199
- state.serverRev = serverRevision;
5200
5211
  yield db.table(yTable).put(state);
5201
5212
  }
5202
5213
  }));
@@ -5532,6 +5543,7 @@
5532
5543
  newSyncState.realms = res.realms;
5533
5544
  newSyncState.inviteRealms = res.inviteRealms;
5534
5545
  newSyncState.serverRevision = res.serverRevision;
5546
+ newSyncState.yServerRevision = res.serverRevision;
5535
5547
  newSyncState.timestamp = new Date();
5536
5548
  delete newSyncState.error;
5537
5549
  const filteredChanges = filterServerChangesThroughAddedClientChanges(res.changes, addedClientChanges);
@@ -5543,11 +5555,14 @@
5543
5555
  //
5544
5556
  // apply yMessages
5545
5557
  //
5546
- const { receivedUntils, resyncNeeded } = yield applyYServerMessages(res.yMessages, db);
5558
+ const { receivedUntils, resyncNeeded, yServerRevision } = yield applyYServerMessages(res.yMessages, db);
5559
+ if (yServerRevision) {
5560
+ newSyncState.yServerRevision = yServerRevision;
5561
+ }
5547
5562
  //
5548
5563
  // update Y SyncStates
5549
5564
  //
5550
- yield updateYSyncStates(lastUpdateIds, receivedUntils, db, res.serverRevision);
5565
+ yield updateYSyncStates(lastUpdateIds, receivedUntils, db);
5551
5566
  if (resyncNeeded) {
5552
5567
  newSyncState.yDownloadedRealms = {}; // Will trigger a full download of Y-documents below...
5553
5568
  }
@@ -5843,7 +5858,7 @@
5843
5858
  };
5844
5859
  }
5845
5860
 
5846
- const wm = new WeakMap();
5861
+ const wm$1 = new WeakMap();
5847
5862
  const DEXIE_CLOUD_SCHEMA = {
5848
5863
  members: '@id, [userId+realmId], [email+realmId], realmId',
5849
5864
  roles: '[realmId+name]',
@@ -5857,7 +5872,7 @@
5857
5872
  function DexieCloudDB(dx) {
5858
5873
  if ('vip' in dx)
5859
5874
  dx = dx['vip']; // Avoid race condition. Always map to a vipped dexie that don't block during db.on.ready().
5860
- let db = wm.get(dx.cloud);
5875
+ let db = wm$1.get(dx.cloud);
5861
5876
  if (!db) {
5862
5877
  const localSyncEvent = new rxjs.Subject();
5863
5878
  let syncStateChangedEvent = new BroadcastedAndLocalEvent(`syncstatechanged-${dx.name}`);
@@ -5945,7 +5960,7 @@
5945
5960
  Object.assign(db, helperMethods);
5946
5961
  db.messageConsumer = MessagesFromServerConsumer(db);
5947
5962
  db.messageProducer = new rxjs.Subject();
5948
- wm.set(dx.cloud, db);
5963
+ wm$1.set(dx.cloud, db);
5949
5964
  }
5950
5965
  return db;
5951
5966
  }
@@ -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')
@@ -8130,7 +8174,7 @@
8130
8174
  const syncComplete = new rxjs.Subject();
8131
8175
  dexie.cloud = {
8132
8176
  // @ts-ignore
8133
- version: "4.1.0-alpha.21",
8177
+ version: "4.1.0-alpha.23",
8134
8178
  options: Object.assign({}, DEFAULT_OPTIONS),
8135
8179
  schema: null,
8136
8180
  get currentUserId() {
@@ -8432,7 +8476,7 @@
8432
8476
  }
8433
8477
  }
8434
8478
  // @ts-ignore
8435
- dexieCloud.version = "4.1.0-alpha.21";
8479
+ dexieCloud.version = "4.1.0-alpha.23";
8436
8480
  Dexie.Cloud = dexieCloud;
8437
8481
 
8438
8482
  const ydocTriggers = {};