@vue-skuilder/db 0.1.36 → 0.1.39

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.
package/dist/index.mjs CHANGED
@@ -7767,7 +7767,7 @@ Currently logged-in as ${this._username}.`
7767
7767
  _rev: existingDoc._rev
7768
7768
  });
7769
7769
  } catch (e) {
7770
- if (e instanceof Error && e.name === "not_found") {
7770
+ if (e?.name === "not_found") {
7771
7771
  await this.remoteDB.put(doc);
7772
7772
  } else {
7773
7773
  throw e;
@@ -7794,7 +7794,7 @@ Currently logged-in as ${this._username}.`
7794
7794
  _rev: existingDoc._rev
7795
7795
  });
7796
7796
  } catch (e) {
7797
- if (e instanceof Error && e.name === "conflict" && retries > 0) {
7797
+ if (e?.name === "conflict" && retries > 0) {
7798
7798
  await new Promise((resolve) => setTimeout(resolve, 1e3));
7799
7799
  return this.applyDesignDoc(doc, retries - 1);
7800
7800
  }
@@ -13285,13 +13285,6 @@ var SessionController = class _SessionController extends Loggable {
13285
13285
  * Cleared when the depletion-triggered replan fires (newQ exhausted).
13286
13286
  */
13287
13287
  _suppressQualityReplan = false;
13288
- /**
13289
- * Guards against infinite depletion-triggered replans. Set to true
13290
- * when a depletion replan fires; cleared when a replan produces
13291
- * content (newQ.length > 0 after replan) or when an explicit
13292
- * (non-auto) replan is requested.
13293
- */
13294
- _depletionReplanAttempted = false;
13295
13288
  /**
13296
13289
  * When > 0, the session timer cannot end the session. Decremented on
13297
13290
  * each nextCard() draw. Set by replans that include `minFollowUpCards`.
@@ -13449,9 +13442,6 @@ var SessionController = class _SessionController extends Loggable {
13449
13442
  */
13450
13443
  async requestReplan(options) {
13451
13444
  const opts = this.normalizeReplanOptions(options);
13452
- if (opts.hints || opts.label || opts.limit) {
13453
- this._depletionReplanAttempted = false;
13454
- }
13455
13445
  const hasIntent = this._replanHasIntent(opts);
13456
13446
  if (this._replanPromise) {
13457
13447
  if (!hasIntent) {
@@ -13530,6 +13520,25 @@ var SessionController = class _SessionController extends Loggable {
13530
13520
  }
13531
13521
  await this._executeReplan(opts);
13532
13522
  }
13523
+ /**
13524
+ * Run a replan, bypassing requestReplan()'s coalesce logic.
13525
+ *
13526
+ * Use this when correctness depends on a *fresh* pipeline run, not on
13527
+ * the existence of *some* in-flight replan. Specifically: the
13528
+ * wedge-breaker path in nextCard(), where coalescing into a previous
13529
+ * run that we now know produced insufficient content would re-create
13530
+ * the bug we're trying to prevent.
13531
+ *
13532
+ * Still tracks _replanPromise like requestReplan() does so concurrent
13533
+ * observers (auto-trigger guards in nextCard()) see consistent state.
13534
+ */
13535
+ async _replanUncoalesced(opts) {
13536
+ const run = this._runReplan(opts);
13537
+ this._replanPromise = run.finally(() => {
13538
+ if (this._replanPromise === run) this._replanPromise = null;
13539
+ });
13540
+ await run;
13541
+ }
13533
13542
  /**
13534
13543
  * Normalise the requestReplan argument. Accepts either a ReplanOptions
13535
13544
  * object (new API) or a plain Record<string, unknown> (legacy callers
@@ -13583,9 +13592,6 @@ var SessionController = class _SessionController extends Loggable {
13583
13592
  `[Replan] Only ${wellIndicated}/${_SessionController.MIN_WELL_INDICATED} well-indicated cards after replan`
13584
13593
  );
13585
13594
  }
13586
- if (this.newQ.length > 0) {
13587
- this._depletionReplanAttempted = false;
13588
- }
13589
13595
  await this.hydrationService.ensureHydratedCards();
13590
13596
  const labelTag = opts.label ? ` [${opts.label}]` : "";
13591
13597
  this.log(`Replan complete${labelTag}: newQ now has ${this.newQ.length} cards (mode=${mode})`);
@@ -13864,21 +13870,13 @@ var SessionController = class _SessionController extends Loggable {
13864
13870
  this.log("nextCard: awaiting in-flight replan before drawing");
13865
13871
  await this._replanPromise;
13866
13872
  }
13867
- if (this.newQ.length <= 1 && this._secondsRemaining > 0 && !this._replanPromise && !this._depletionReplanAttempted) {
13873
+ if (this.newQ.length <= 1 && this._secondsRemaining > 0 && !this._replanPromise) {
13868
13874
  this._suppressQualityReplan = false;
13869
- this._depletionReplanAttempted = true;
13870
13875
  const otherContent = this.reviewQ.length + this.failedQ.length;
13871
- if (this.newQ.length === 0 && otherContent === 0) {
13872
- this.log(
13873
- `[AutoReplan:depletion] All queues empty with ${this._secondsRemaining}s remaining. Awaiting replan.`
13874
- );
13875
- await this.requestReplan();
13876
- } else {
13877
- this.log(
13878
- `[AutoReplan:depletion] newQ has ${this.newQ.length} card(s) (${otherContent} in other queues) with ${this._secondsRemaining}s remaining. Triggering background replan.`
13879
- );
13880
- void this.requestReplan();
13881
- }
13876
+ this.log(
13877
+ `[AutoReplan:depletion] newQ has ${this.newQ.length} card(s) (${otherContent} in other queues) with ${this._secondsRemaining}s remaining. Triggering background replan.`
13878
+ );
13879
+ void this.requestReplan();
13882
13880
  }
13883
13881
  const REPLAN_BUFFER = 3;
13884
13882
  if (!this._suppressQualityReplan && this._wellIndicatedRemaining <= REPLAN_BUFFER && this.newQ.length > 0 && !this._replanPromise) {
@@ -13892,6 +13890,27 @@ var SessionController = class _SessionController extends Loggable {
13892
13890
  endSessionTracking();
13893
13891
  return null;
13894
13892
  }
13893
+ const WEDGE_MAX_EMPTY_STREAK = 3;
13894
+ const WEDGE_BACKOFF_MS = 250;
13895
+ let wedgeEmptyStreak = 0;
13896
+ while (this._secondsRemaining > 0 && this.newQ.length === 0 && this.reviewQ.length === 0 && this.failedQ.length === 0) {
13897
+ this.log(
13898
+ `[WedgeBreaker] All queues empty with ${this._secondsRemaining}s remaining. Running pipeline (attempt ${wedgeEmptyStreak + 1}/${WEDGE_MAX_EMPTY_STREAK}).`
13899
+ );
13900
+ await this._replanUncoalesced({ label: "wedge-breaker" });
13901
+ if (this.newQ.length === 0 && this.reviewQ.length === 0 && this.failedQ.length === 0) {
13902
+ wedgeEmptyStreak++;
13903
+ if (wedgeEmptyStreak >= WEDGE_MAX_EMPTY_STREAK) {
13904
+ this.log(
13905
+ `[WedgeBreaker] Pipeline returned no content ${WEDGE_MAX_EMPTY_STREAK} consecutive times. Giving up; session will end.`
13906
+ );
13907
+ break;
13908
+ }
13909
+ await new Promise((resolve) => setTimeout(resolve, WEDGE_BACKOFF_MS));
13910
+ } else {
13911
+ wedgeEmptyStreak = 0;
13912
+ }
13913
+ }
13895
13914
  const MAX_SKIP = 20;
13896
13915
  for (let attempt = 0; attempt < MAX_SKIP; attempt++) {
13897
13916
  const nextItem = this._selectNextItemToHydrate();