@vue-skuilder/db 0.2.1 → 0.2.3
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/{contentSource-Ht3N2f-y.d.ts → contentSource-Cplhv3bJ.d.ts} +1 -1
- package/dist/{contentSource-BMlMwSiG.d.cts → contentSource-kI9_jwTu.d.cts} +1 -1
- package/dist/core/index.d.cts +5 -5
- package/dist/core/index.d.ts +5 -5
- package/dist/core/index.js +76 -21
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.mjs +76 -21
- package/dist/core/index.mjs.map +1 -1
- package/dist/{dataLayerProvider-BEqB8VBR.d.cts → dataLayerProvider-CiA2Rr0v.d.cts} +1 -1
- package/dist/{dataLayerProvider-DObSXjnf.d.ts → dataLayerProvider-DrBqOUa3.d.ts} +1 -1
- package/dist/impl/couch/index.d.cts +35 -3
- package/dist/impl/couch/index.d.ts +35 -3
- package/dist/impl/couch/index.js +76 -21
- package/dist/impl/couch/index.js.map +1 -1
- package/dist/impl/couch/index.mjs +76 -21
- package/dist/impl/couch/index.mjs.map +1 -1
- package/dist/impl/static/index.d.cts +4 -4
- package/dist/impl/static/index.d.ts +4 -4
- package/dist/impl/static/index.js +4 -5
- package/dist/impl/static/index.js.map +1 -1
- package/dist/impl/static/index.mjs +4 -5
- package/dist/impl/static/index.mjs.map +1 -1
- package/dist/{index-BWvO-_rJ.d.ts → index-BLLT5BYE.d.ts} +1 -1
- package/dist/{index-Ba7hYbHj.d.cts → index-k9NFHpS1.d.cts} +1 -1
- package/dist/index.d.cts +164 -10
- package/dist/index.d.ts +164 -10
- package/dist/index.js +215 -28
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +215 -28
- package/dist/index.mjs.map +1 -1
- package/dist/{types-W8n-B6HG.d.cts → types-BFUa1pa3.d.cts} +1 -1
- package/dist/{types-CJrLM1Ew.d.ts → types-CHgpWQAY.d.ts} +1 -1
- package/dist/{types-legacy-JXDxinpU.d.cts → types-legacy-4tlwHnXo.d.cts} +1 -1
- package/dist/{types-legacy-JXDxinpU.d.ts → types-legacy-4tlwHnXo.d.ts} +1 -1
- package/dist/util/packer/index.d.cts +3 -3
- package/dist/util/packer/index.d.ts +3 -3
- package/docs/session-lifecycle-and-replan.md +418 -0
- package/package.json +3 -3
- package/src/core/navigators/Pipeline.ts +5 -1
- package/src/core/navigators/generators/elo.ts +19 -6
- package/src/core/navigators/generators/srs.ts +10 -0
- package/src/impl/couch/courseDB.ts +146 -17
- package/src/study/SessionController.ts +295 -13
- package/src/study/services/CardHydrationService.ts +24 -0
|
@@ -1731,10 +1731,8 @@ var init_elo = __esm({
|
|
|
1731
1731
|
{ limit, elo: "user" },
|
|
1732
1732
|
(c) => !activeCards.some((ac) => c.cardID === ac.cardID)
|
|
1733
1733
|
)).map((c) => ({ ...c, status: "new" }));
|
|
1734
|
-
const
|
|
1735
|
-
|
|
1736
|
-
const scored = newCards.map((c, i) => {
|
|
1737
|
-
const cardElo = cardEloData[i]?.global?.score ?? 1e3;
|
|
1734
|
+
const scored = newCards.map((c) => {
|
|
1735
|
+
const cardElo = c.elo ?? 1e3;
|
|
1738
1736
|
const distance = Math.abs(cardElo - userGlobalElo);
|
|
1739
1737
|
const rawScore = Math.max(0, 1 - distance / 500);
|
|
1740
1738
|
const samplingKey = rawScore > 0 ? Math.random() ** (1 / rawScore) : 0;
|
|
@@ -3712,7 +3710,8 @@ var init_orchestration = __esm({
|
|
|
3712
3710
|
// src/core/navigators/Pipeline.ts
|
|
3713
3711
|
var Pipeline_exports = {};
|
|
3714
3712
|
__export(Pipeline_exports, {
|
|
3715
|
-
Pipeline: () => Pipeline
|
|
3713
|
+
Pipeline: () => Pipeline,
|
|
3714
|
+
mergeHints: () => mergeHints2
|
|
3716
3715
|
});
|
|
3717
3716
|
import { toCourseElo as toCourseElo5 } from "@vue-skuilder/common";
|
|
3718
3717
|
function globToRegex(pattern) {
|
|
@@ -5451,6 +5450,7 @@ ${above.rows.map((r) => ` ${r.id}-${r.key}
|
|
|
5451
5450
|
}
|
|
5452
5451
|
async addNavigationStrategy(data) {
|
|
5453
5452
|
logger.debug(`[courseDB] Adding navigation strategy: ${data._id}`);
|
|
5453
|
+
this.invalidateNavigatorCache();
|
|
5454
5454
|
return this.remoteDB.put(data).then(() => {
|
|
5455
5455
|
});
|
|
5456
5456
|
}
|
|
@@ -5517,23 +5517,67 @@ ${e.stack}` : JSON.stringify(e);
|
|
|
5517
5517
|
* @returns Cards sorted by score descending
|
|
5518
5518
|
*/
|
|
5519
5519
|
_pendingHints = null;
|
|
5520
|
+
/**
|
|
5521
|
+
* Session-scoped cache of the broad ELO-neighbor pool used by
|
|
5522
|
+
* getCardsCenteredAtELO. The `elo` view query re-indexes on first touch per
|
|
5523
|
+
* call (PouchDB 9 ignores `stale`), so without this each plan/replan pays
|
|
5524
|
+
* ~1.5-2s. The pool is fetched once and re-ranked against the live (roaming)
|
|
5525
|
+
* ELO in memory on subsequent calls.
|
|
5526
|
+
*/
|
|
5527
|
+
_eloPoolCache = null;
|
|
5528
|
+
_eloPoolTtlMs = 5 * 60 * 1e3;
|
|
5529
|
+
/**
|
|
5530
|
+
* Cached assembled navigator (Pipeline). createNavigator() reads strategy
|
|
5531
|
+
* docs and builds a fresh Pipeline every call — whose internal `_tagCache`
|
|
5532
|
+
* and `_cachedOrchestration` are designed to make replans cheap but never
|
|
5533
|
+
* survive, because the instance is discarded each run. Caching it lets those
|
|
5534
|
+
* caches persist across plan/replan within a session (SessionController holds
|
|
5535
|
+
* one CourseDB instance for the session's lifetime). Rebuilt on user change,
|
|
5536
|
+
* TTL expiry, or explicit invalidation after a strategy-doc write.
|
|
5537
|
+
*/
|
|
5538
|
+
_cachedNavigator = null;
|
|
5539
|
+
_navigatorTtlMs = 5 * 60 * 1e3;
|
|
5520
5540
|
setEphemeralHints(hints) {
|
|
5521
5541
|
this._pendingHints = hints;
|
|
5522
5542
|
}
|
|
5523
5543
|
async getWeightedCards(limit) {
|
|
5524
5544
|
const u = await this._getCurrentUser();
|
|
5525
5545
|
try {
|
|
5526
|
-
const navigator2 = await this.
|
|
5546
|
+
const { navigator: navigator2 } = await this._getCachedNavigator(u);
|
|
5527
5547
|
if (this._pendingHints) {
|
|
5528
5548
|
navigator2.setEphemeralHints(this._pendingHints);
|
|
5529
5549
|
this._pendingHints = null;
|
|
5530
5550
|
}
|
|
5531
|
-
|
|
5551
|
+
const result = await navigator2.getWeightedCards(limit);
|
|
5552
|
+
return result;
|
|
5532
5553
|
} catch (e) {
|
|
5533
5554
|
logger.error(`[courseDB] Error getting weighted cards: ${e}`);
|
|
5534
5555
|
throw e;
|
|
5535
5556
|
}
|
|
5536
5557
|
}
|
|
5558
|
+
/**
|
|
5559
|
+
* Return the assembled navigator, reusing the cached instance when possible.
|
|
5560
|
+
* Reuse preserves the Pipeline's per-session caches (tags, orchestration
|
|
5561
|
+
* context) across replans, which is the dominant per-replan cost once the
|
|
5562
|
+
* ELO-pool cost is removed. Rebuilds on user change or TTL expiry.
|
|
5563
|
+
*/
|
|
5564
|
+
async _getCachedNavigator(user) {
|
|
5565
|
+
const userId = user.getUsername();
|
|
5566
|
+
const now = Date.now();
|
|
5567
|
+
if (this._cachedNavigator && this._cachedNavigator.userId === userId && now - this._cachedNavigator.builtAt < this._navigatorTtlMs) {
|
|
5568
|
+
return { navigator: this._cachedNavigator.navigator, cacheStatus: "hit" };
|
|
5569
|
+
}
|
|
5570
|
+
const navigator2 = await this.createNavigator(user);
|
|
5571
|
+
this._cachedNavigator = { navigator: navigator2, userId, builtAt: now };
|
|
5572
|
+
return { navigator: navigator2, cacheStatus: "miss" };
|
|
5573
|
+
}
|
|
5574
|
+
/**
|
|
5575
|
+
* Drop the cached navigator so the next getWeightedCards() rebuilds it.
|
|
5576
|
+
* Call after mutating this course's navigation strategy documents.
|
|
5577
|
+
*/
|
|
5578
|
+
invalidateNavigatorCache() {
|
|
5579
|
+
this._cachedNavigator = null;
|
|
5580
|
+
}
|
|
5537
5581
|
async getCardsCenteredAtELO(options = {
|
|
5538
5582
|
limit: 99,
|
|
5539
5583
|
elo: "user"
|
|
@@ -5556,20 +5600,31 @@ ${e.stack}` : JSON.stringify(e);
|
|
|
5556
5600
|
} else {
|
|
5557
5601
|
targetElo = options.elo;
|
|
5558
5602
|
}
|
|
5559
|
-
|
|
5560
|
-
|
|
5561
|
-
let
|
|
5562
|
-
|
|
5563
|
-
|
|
5564
|
-
|
|
5565
|
-
|
|
5566
|
-
|
|
5567
|
-
|
|
5568
|
-
|
|
5569
|
-
|
|
5570
|
-
|
|
5571
|
-
|
|
5572
|
-
|
|
5603
|
+
const POOL_SIZE = Math.max(2e3, options.limit * 4);
|
|
5604
|
+
const nowMs = Date.now();
|
|
5605
|
+
let cacheStatus = "hit";
|
|
5606
|
+
if (!this._eloPoolCache || nowMs - this._eloPoolCache.fetchedAt > this._eloPoolTtlMs) {
|
|
5607
|
+
const fetched = await this.getCardsByELO(targetElo, POOL_SIZE);
|
|
5608
|
+
if (fetched.length > 0) {
|
|
5609
|
+
this._eloPoolCache = { rows: fetched, fetchedAt: nowMs };
|
|
5610
|
+
}
|
|
5611
|
+
cacheStatus = "miss";
|
|
5612
|
+
}
|
|
5613
|
+
const rankAgainstCurrentElo = () => {
|
|
5614
|
+
const raw = this._eloPoolCache?.rows ?? [];
|
|
5615
|
+
const survivors = filter ? raw.filter((c) => filter(c)) : raw;
|
|
5616
|
+
return survivors.map((c) => ({ ...c })).sort(
|
|
5617
|
+
(a, b) => Math.abs((a.elo ?? targetElo) - targetElo) - Math.abs((b.elo ?? targetElo) - targetElo)
|
|
5618
|
+
);
|
|
5619
|
+
};
|
|
5620
|
+
let cards = rankAgainstCurrentElo();
|
|
5621
|
+
if (cards.length < options.limit && (cacheStatus === "hit" || !this._eloPoolCache)) {
|
|
5622
|
+
const fetched = await this.getCardsByELO(targetElo, POOL_SIZE);
|
|
5623
|
+
if (fetched.length > 0) {
|
|
5624
|
+
this._eloPoolCache = { rows: fetched, fetchedAt: nowMs };
|
|
5625
|
+
}
|
|
5626
|
+
cards = rankAgainstCurrentElo();
|
|
5627
|
+
cacheStatus = "refresh";
|
|
5573
5628
|
}
|
|
5574
5629
|
const selectedCards = [];
|
|
5575
5630
|
while (selectedCards.length < options.limit && cards.length > 0) {
|