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,4 +1,3 @@
1
1
  import { YSyncState } from "dexie";
2
2
  export interface YDexieCloudSyncState extends YSyncState {
3
- serverRev: string;
4
3
  }
@@ -1,5 +1,9 @@
1
1
  import { DexieCloudDB } from '../db/DexieCloudDB';
2
2
  import { YServerMessage } from 'dexie-cloud-common';
3
3
  export declare function applyYServerMessages(yMessages: YServerMessage[], db: DexieCloudDB): Promise<{
4
- [yTable: string]: number;
4
+ receivedUntils: {
5
+ [yTable: string]: number;
6
+ };
7
+ resyncNeeded: boolean;
8
+ yServerRevision?: string;
5
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.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
  *
@@ -982,13 +982,20 @@
982
982
  function encodeYMessage(msg) {
983
983
  const encoder = new Encoder();
984
984
  writeVarString(encoder, msg.type);
985
- writeVarString(encoder, msg.table);
986
- writeVarString(encoder, msg.prop);
985
+ if ('table' in msg)
986
+ writeVarString(encoder, msg.table);
987
+ if ('prop' in msg)
988
+ writeVarString(encoder, msg.prop);
987
989
  switch (msg.type) {
988
990
  case 'u-ack':
989
991
  case 'u-reject':
990
992
  writeBigUint64(encoder, BigInt(msg.i));
991
993
  break;
994
+ case 'outdated-server-rev':
995
+ break;
996
+ case 'y-complete-sync-done':
997
+ writeVarString(encoder, msg.yServerRev);
998
+ break;
992
999
  default:
993
1000
  writeAny(encoder, msg.k);
994
1001
  switch (msg.type) {
@@ -1332,6 +1339,12 @@
1332
1339
  function decodeYMessage(a) {
1333
1340
  const decoder = new Decoder(a);
1334
1341
  const type = readVarString(decoder);
1342
+ if (type === 'outdated-server-rev') {
1343
+ return { type };
1344
+ }
1345
+ if (type === 'y-complete-sync-done') {
1346
+ return { type, yServerRev: readVarString(decoder) };
1347
+ }
1335
1348
  const table = readVarString(decoder);
1336
1349
  const prop = readVarString(decoder);
1337
1350
  switch (type) {
@@ -3417,10 +3430,10 @@
3417
3430
  }
3418
3431
 
3419
3432
  // Emulate true-private property db. Why? So it's not stored in DB.
3420
- const wm$1 = new WeakMap();
3433
+ const wm$2 = new WeakMap();
3421
3434
  class AuthPersistedContext {
3422
3435
  constructor(db, userLogin) {
3423
- wm$1.set(this, db);
3436
+ wm$2.set(this, db);
3424
3437
  Object.assign(this, userLogin);
3425
3438
  }
3426
3439
  static load(db, userId) {
@@ -3437,7 +3450,7 @@
3437
3450
  }
3438
3451
  save() {
3439
3452
  return __awaiter(this, void 0, void 0, function* () {
3440
- const db = wm$1.get(this);
3453
+ const db = wm$2.get(this);
3441
3454
  db.table("$logins").put(this);
3442
3455
  });
3443
3456
  }
@@ -4718,6 +4731,7 @@
4718
4731
  lastPull: syncState
4719
4732
  ? {
4720
4733
  serverRevision: syncState.serverRevision,
4734
+ yServerRevision: syncState.yServerRevision,
4721
4735
  realms: syncState.realms,
4722
4736
  inviteRealms: syncState.inviteRealms,
4723
4737
  }
@@ -5063,12 +5077,14 @@
5063
5077
  function applyYServerMessages(yMessages, db) {
5064
5078
  return __awaiter(this, void 0, void 0, function* () {
5065
5079
  var _a;
5066
- const result = {};
5080
+ const receivedUntils = {};
5081
+ let resyncNeeded = false;
5082
+ let yServerRevision;
5067
5083
  for (const m of yMessages) {
5068
5084
  switch (m.type) {
5069
5085
  case 'u-s': {
5070
5086
  const utbl = getUpdatesTable(db, m.table, m.prop);
5071
- result[utbl.name] = yield utbl.add({
5087
+ receivedUntils[utbl.name] = yield utbl.add({
5072
5088
  k: m.k,
5073
5089
  u: m.u,
5074
5090
  });
@@ -5097,13 +5113,13 @@
5097
5113
  // and destroy it's open document if there is one.
5098
5114
  const primaryKey = (_a = (yield utbl.get(m.i))) === null || _a === void 0 ? void 0 : _a.k;
5099
5115
  if (primaryKey != null) {
5100
- yield db.transaction('rw', utbl, tx => {
5116
+ yield db.transaction('rw', utbl, (tx) => {
5101
5117
  // @ts-ignore
5102
5118
  tx.idbtrans._rejecting_y_ypdate = true; // Inform ydoc triggers that we delete because of a rejection and not GC
5103
5119
  return utbl
5104
5120
  .where('i')
5105
5121
  .aboveOrEqual(m.i)
5106
- .filter(u => Dexie.cmp(u.k, primaryKey) === 0 && ((u.f || 0) & 1) === 1)
5122
+ .filter((u) => Dexie.cmp(u.k, primaryKey) === 0 && ((u.f || 0) & 1) === 1)
5107
5123
  .delete();
5108
5124
  });
5109
5125
  // Destroy active doc
@@ -5120,13 +5136,24 @@
5120
5136
  }
5121
5137
  break;
5122
5138
  }
5139
+ case 'y-complete-sync-done': {
5140
+ yServerRevision = m.yServerRev;
5141
+ break;
5142
+ }
5143
+ case 'outdated-server-rev':
5144
+ resyncNeeded = true;
5145
+ break;
5123
5146
  }
5124
5147
  }
5125
- return result;
5148
+ return {
5149
+ receivedUntils,
5150
+ resyncNeeded,
5151
+ yServerRevision
5152
+ };
5126
5153
  });
5127
5154
  }
5128
5155
 
5129
- function updateYSyncStates(lastUpdateIdsBeforeSync, receivedUntilsAfterSync, db, serverRevision) {
5156
+ function updateYSyncStates(lastUpdateIdsBeforeSync, receivedUntilsAfterSync, db) {
5130
5157
  return __awaiter(this, void 0, void 0, function* () {
5131
5158
  var _a, _b, _c, _d, _e;
5132
5159
  // We want to update unsentFrom for each yTable to the value specified in first argument
@@ -5175,14 +5202,12 @@
5175
5202
  yield db.table(yTable).add({
5176
5203
  i: DEXIE_CLOUD_SYNCER_ID,
5177
5204
  unsentFrom,
5178
- receivedUntil,
5179
- serverRev: serverRevision,
5205
+ receivedUntil
5180
5206
  });
5181
5207
  }
5182
5208
  else {
5183
5209
  state.unsentFrom = Math.max(unsentFrom, state.unsentFrom || 1);
5184
5210
  state.receivedUntil = Math.max(receivedUntil, state.receivedUntil || 0);
5185
- state.serverRev = serverRevision;
5186
5211
  yield db.table(yTable).put(state);
5187
5212
  }
5188
5213
  }));
@@ -5518,6 +5543,7 @@
5518
5543
  newSyncState.realms = res.realms;
5519
5544
  newSyncState.inviteRealms = res.inviteRealms;
5520
5545
  newSyncState.serverRevision = res.serverRevision;
5546
+ newSyncState.yServerRevision = res.serverRevision;
5521
5547
  newSyncState.timestamp = new Date();
5522
5548
  delete newSyncState.error;
5523
5549
  const filteredChanges = filterServerChangesThroughAddedClientChanges(res.changes, addedClientChanges);
@@ -5529,11 +5555,17 @@
5529
5555
  //
5530
5556
  // apply yMessages
5531
5557
  //
5532
- const receivedUntils = yield applyYServerMessages(res.yMessages, db);
5558
+ const { receivedUntils, resyncNeeded, yServerRevision } = yield applyYServerMessages(res.yMessages, db);
5559
+ if (yServerRevision) {
5560
+ newSyncState.yServerRevision = yServerRevision;
5561
+ }
5533
5562
  //
5534
5563
  // update Y SyncStates
5535
5564
  //
5536
- yield updateYSyncStates(lastUpdateIds, receivedUntils, db, res.serverRevision);
5565
+ yield updateYSyncStates(lastUpdateIds, receivedUntils, db);
5566
+ if (resyncNeeded) {
5567
+ newSyncState.yDownloadedRealms = {}; // Will trigger a full download of Y-documents below...
5568
+ }
5537
5569
  }
5538
5570
  //
5539
5571
  // Update regular syncState
@@ -5826,7 +5858,7 @@
5826
5858
  };
5827
5859
  }
5828
5860
 
5829
- const wm = new WeakMap();
5861
+ const wm$1 = new WeakMap();
5830
5862
  const DEXIE_CLOUD_SCHEMA = {
5831
5863
  members: '@id, [userId+realmId], [email+realmId], realmId',
5832
5864
  roles: '[realmId+name]',
@@ -5840,7 +5872,7 @@
5840
5872
  function DexieCloudDB(dx) {
5841
5873
  if ('vip' in dx)
5842
5874
  dx = dx['vip']; // Avoid race condition. Always map to a vipped dexie that don't block during db.on.ready().
5843
- let db = wm.get(dx.cloud);
5875
+ let db = wm$1.get(dx.cloud);
5844
5876
  if (!db) {
5845
5877
  const localSyncEvent = new rxjs.Subject();
5846
5878
  let syncStateChangedEvent = new BroadcastedAndLocalEvent(`syncstatechanged-${dx.name}`);
@@ -5928,7 +5960,7 @@
5928
5960
  Object.assign(db, helperMethods);
5929
5961
  db.messageConsumer = MessagesFromServerConsumer(db);
5930
5962
  db.messageProducer = new rxjs.Subject();
5931
- wm.set(dx.cloud, db);
5963
+ wm$1.set(dx.cloud, db);
5932
5964
  }
5933
5965
  return db;
5934
5966
  }
@@ -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')
@@ -8100,7 +8174,7 @@
8100
8174
  const syncComplete = new rxjs.Subject();
8101
8175
  dexie.cloud = {
8102
8176
  // @ts-ignore
8103
- version: "4.1.0-alpha.20",
8177
+ version: "4.1.0-alpha.23",
8104
8178
  options: Object.assign({}, DEFAULT_OPTIONS),
8105
8179
  schema: null,
8106
8180
  get currentUserId() {
@@ -8402,7 +8476,7 @@
8402
8476
  }
8403
8477
  }
8404
8478
  // @ts-ignore
8405
- dexieCloud.version = "4.1.0-alpha.20";
8479
+ dexieCloud.version = "4.1.0-alpha.23";
8406
8480
  Dexie.Cloud = dexieCloud;
8407
8481
 
8408
8482
  const ydocTriggers = {};