@vue-skuilder/db 0.2.18 → 0.2.20

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.
Files changed (36) hide show
  1. package/dist/{SyncStrategy-BJMZq3WO.d.cts → SyncStrategy-CCU1H81I.d.cts} +8 -2
  2. package/dist/{SyncStrategy-BJMZq3WO.d.ts → SyncStrategy-CCU1H81I.d.ts} +8 -2
  3. package/dist/{contentSource-CudEz5Tm.d.ts → contentSource-BDRX_YYJ.d.ts} +13 -1
  4. package/dist/{contentSource-CBqZCoGU.d.cts → contentSource-D-LQYFyx.d.cts} +13 -1
  5. package/dist/core/index.d.cts +3 -3
  6. package/dist/core/index.d.ts +3 -3
  7. package/dist/core/index.js +38 -7
  8. package/dist/core/index.js.map +1 -1
  9. package/dist/core/index.mjs +38 -7
  10. package/dist/core/index.mjs.map +1 -1
  11. package/dist/{dataLayerProvider-BBA8tJNx.d.cts → dataLayerProvider-Dd2UcCif.d.cts} +1 -1
  12. package/dist/{dataLayerProvider-C9WgkBzR.d.ts → dataLayerProvider-Dpshuql4.d.ts} +1 -1
  13. package/dist/impl/couch/index.d.cts +20 -4
  14. package/dist/impl/couch/index.d.ts +20 -4
  15. package/dist/impl/couch/index.js +80 -11
  16. package/dist/impl/couch/index.js.map +1 -1
  17. package/dist/impl/couch/index.mjs +80 -11
  18. package/dist/impl/couch/index.mjs.map +1 -1
  19. package/dist/impl/static/index.d.cts +3 -3
  20. package/dist/impl/static/index.d.ts +3 -3
  21. package/dist/impl/static/index.js +38 -7
  22. package/dist/impl/static/index.js.map +1 -1
  23. package/dist/impl/static/index.mjs +38 -7
  24. package/dist/impl/static/index.mjs.map +1 -1
  25. package/dist/index.d.cts +3 -3
  26. package/dist/index.d.ts +3 -3
  27. package/dist/index.js +80 -11
  28. package/dist/index.js.map +1 -1
  29. package/dist/index.mjs +80 -11
  30. package/dist/index.mjs.map +1 -1
  31. package/package.json +3 -3
  32. package/src/core/interfaces/userDB.ts +14 -1
  33. package/src/impl/common/BaseUserDB.ts +56 -8
  34. package/src/impl/common/SyncStrategy.ts +17 -4
  35. package/src/impl/couch/CouchDBSyncStrategy.ts +52 -4
  36. package/tests/impl/hydration.test.ts +49 -0
package/dist/index.mjs CHANGED
@@ -7361,22 +7361,60 @@ var init_CouchDBSyncStrategy = __esm({
7361
7361
  * before we know what the remote already has, which is the conflict-leaf
7362
7362
  * problem hydration exists to avoid. Local changes go up when startSync()
7363
7363
  * takes over.
7364
+ *
7365
+ * Into an EMPTY local DB this skips deleted documents, because a mirror that
7366
+ * never held a document does not need to be told it was removed. That
7367
+ * matters more than it sounds: scheduled reviews (`card_review_*`) are
7368
+ * created and deleted once per review completed, so the changes feed is
7369
+ * dominated by tombstones and grows without bound, while the durable record
7370
+ * lives in card history. One real account measured 385 live documents behind
7371
+ * 8,370 deletions — an unfiltered pull moved 9,520 documents and took ~20s,
7372
+ * blowing the hydration timeout on a phone; filtered, the same pull moves
7373
+ * 404 and takes ~2s, reaching a byte-identical local state.
7374
+ *
7375
+ * The filter runs server-side, so `last_seq` still reports the source's true
7376
+ * sequence. Returning it lets startSync() begin the live pull from there
7377
+ * instead of re-walking the same history in the background — without that,
7378
+ * the cost is merely deferred, not removed.
7364
7379
  */
7365
7380
  async hydrate(localDB, remoteDB) {
7366
- const replication = pouchdb_setup_default.replicate(remoteDB, localDB, {});
7381
+ const pristine = (await localDB.info()).doc_count === 0;
7382
+ const replication = pouchdb_setup_default.replicate(
7383
+ remoteDB,
7384
+ localDB,
7385
+ pristine ? { selector: { _deleted: { $exists: false } } } : {}
7386
+ );
7367
7387
  this.hydrationHandle = replication;
7368
7388
  try {
7369
7389
  const info = await replication;
7370
- return { docsWritten: info.docs_written };
7390
+ return {
7391
+ docsWritten: info.docs_written,
7392
+ // Only a pristine pull is a trustworthy starting point for the live
7393
+ // sync; otherwise leave startSync() to its own checkpoint.
7394
+ lastSeq: pristine ? info.last_seq : void 0
7395
+ };
7371
7396
  } finally {
7372
7397
  this.hydrationHandle = void 0;
7373
7398
  }
7374
7399
  }
7375
- startSync(localDB, remoteDB) {
7400
+ startSync(localDB, remoteDB, since) {
7376
7401
  if (localDB.name !== remoteDB.name) {
7377
7402
  this.syncHandle = pouchdb_setup_default.sync(localDB, remoteDB, {
7378
7403
  live: true,
7379
- retry: true
7404
+ retry: true,
7405
+ // `since` applies to the pull only, and only when hydrate() just
7406
+ // established that local matches remote at that sequence. Without it,
7407
+ // a filtered hydration leaves the pull with no usable checkpoint (the
7408
+ // filter changes the replication id), so it restarts from zero and
7409
+ // re-walks in the background exactly the tombstone history hydration
7410
+ // just skipped — same work, now competing with the study session.
7411
+ //
7412
+ // Deliberately NOT persisted across launches: on later boots hydration
7413
+ // is skipped and the pull's own checkpoint is the correct resume
7414
+ // point, so a stored sequence could only be stale. If the app dies
7415
+ // before that first checkpoint is written, the next launch simply
7416
+ // walks the full feed once.
7417
+ ...since !== void 0 ? { pull: { since } } : {}
7380
7418
  });
7381
7419
  }
7382
7420
  }
@@ -7896,16 +7934,44 @@ var init_BaseUserDB = __esm({
7896
7934
  // Database to use for write operations (local-first approach)
7897
7935
  updateQueue;
7898
7936
  _hydration = { state: "not-required" };
7937
+ /** In-flight hydration, so concurrent callers can wait for a real answer. */
7938
+ _hydrationPromise = null;
7939
+ /**
7940
+ * Sequence the local mirror was filled to by a hydration that succeeded in
7941
+ * THIS init(), handed to startSync() so the live pull can skip history it
7942
+ * would otherwise re-walk. Undefined whenever there is no such guarantee —
7943
+ * hydration skipped, not required, or failed. Never persisted; see the
7944
+ * `since` note in CouchDBSyncStrategy.startSync().
7945
+ */
7946
+ _hydratedSeq;
7899
7947
  /**
7900
- * How far the local mirror can be trusted. See {@link UserHydrationStatus}.
7948
+ * How far the local mirror can be trusted, RIGHT NOW. See
7949
+ * {@link UserHydrationStatus}.
7901
7950
  *
7902
- * Consumers that would otherwise read a 404 as "new user" — onboarding
7903
- * gates, first-run experiences, progress dashboards should check for
7904
- * `failed` and present a retry affordance rather than an empty state.
7951
+ * This is a snapshot, and during login it can legitimately read
7952
+ * `hydrating` prefer awaitHydration() anywhere a decision hangs on the
7953
+ * answer.
7905
7954
  */
7906
7955
  hydrationStatus() {
7907
7956
  return { ...this._hydration };
7908
7957
  }
7958
+ /**
7959
+ * The hydration status once it has settled — never `hydrating`.
7960
+ *
7961
+ * Resolves immediately unless a pull is in flight. Exists for callers that
7962
+ * must decide something (a route guard, a first-run branch) and would
7963
+ * otherwise sample `hydrating` and guess. Since init() is awaited by both
7964
+ * app startup and login(), that only happens when something navigates
7965
+ * concurrently with a login still in progress — a window that stretches to
7966
+ * HYDRATION_TIMEOUT_MS on a slow connection.
7967
+ */
7968
+ async awaitHydration() {
7969
+ try {
7970
+ await this._hydrationPromise;
7971
+ } catch {
7972
+ }
7973
+ return this.hydrationStatus();
7974
+ }
7909
7975
  /**
7910
7976
  * Whether a missing document may be treated as genuinely absent, allowing
7911
7977
  * callers to create it with defaults.
@@ -8362,8 +8428,9 @@ Currently logged-in as ${this._username}.`
8362
8428
  }
8363
8429
  this.syncStrategy.stopSync?.();
8364
8430
  this.setDBandQ();
8365
- await this.hydrateLocalMirror();
8366
- this.syncStrategy.startSync(this.localDB, this.remoteDB);
8431
+ this._hydrationPromise = this.hydrateLocalMirror();
8432
+ await this._hydrationPromise;
8433
+ this.syncStrategy.startSync(this.localDB, this.remoteDB, this._hydratedSeq);
8367
8434
  this.applyDesignDocs().catch((error) => {
8368
8435
  log4(`Error in applyDesignDocs background task: ${error}`);
8369
8436
  if (error && typeof error === "object") {
@@ -8391,6 +8458,7 @@ Currently logged-in as ${this._username}.`
8391
8458
  * ceiling). Callers decide what a failure means for them.
8392
8459
  */
8393
8460
  async hydrateLocalMirror() {
8461
+ this._hydratedSeq = void 0;
8394
8462
  if (this.localDB.name === this.remoteDB.name || !this.syncStrategy.hydrate) {
8395
8463
  this._hydration = { state: "not-required" };
8396
8464
  return;
@@ -8402,11 +8470,12 @@ Currently logged-in as ${this._username}.`
8402
8470
  this._hydration = { state: "hydrating" };
8403
8471
  const start = Date.now();
8404
8472
  try {
8405
- const { docsWritten } = await withTimeout(
8473
+ const { docsWritten, lastSeq } = await withTimeout(
8406
8474
  this.syncStrategy.hydrate(this.localDB, this.remoteDB),
8407
8475
  HYDRATION_TIMEOUT_MS,
8408
8476
  `Hydration of local mirror for ${this._username}`
8409
8477
  );
8478
+ this._hydratedSeq = lastSeq;
8410
8479
  await this.writeHydrationMarker();
8411
8480
  this._hydration = {
8412
8481
  state: "hydrated",