@vue-skuilder/db 0.2.0 → 0.2.2
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/core/index.js +80 -21
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.mjs +80 -21
- package/dist/core/index.mjs.map +1 -1
- package/dist/impl/couch/index.d.cts +32 -0
- package/dist/impl/couch/index.d.ts +32 -0
- package/dist/impl/couch/index.js +80 -21
- package/dist/impl/couch/index.js.map +1 -1
- package/dist/impl/couch/index.mjs +80 -21
- package/dist/impl/couch/index.mjs.map +1 -1
- package/dist/impl/static/index.js +8 -5
- package/dist/impl/static/index.js.map +1 -1
- package/dist/impl/static/index.mjs +8 -5
- package/dist/impl/static/index.mjs.map +1 -1
- package/dist/index.d.cts +13 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +94 -22
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +94 -22
- package/dist/index.mjs.map +1 -1
- package/docs/session-lifecycle-and-replan.md +418 -0
- package/package.json +3 -3
- package/src/core/UserDBDebugger.ts +7 -1
- package/src/core/navigators/Pipeline.ts +4 -0
- 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 +56 -1
- 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;
|
|
@@ -5451,6 +5449,7 @@ ${above.rows.map((r) => ` ${r.id}-${r.key}
|
|
|
5451
5449
|
}
|
|
5452
5450
|
async addNavigationStrategy(data) {
|
|
5453
5451
|
logger.debug(`[courseDB] Adding navigation strategy: ${data._id}`);
|
|
5452
|
+
this.invalidateNavigatorCache();
|
|
5454
5453
|
return this.remoteDB.put(data).then(() => {
|
|
5455
5454
|
});
|
|
5456
5455
|
}
|
|
@@ -5517,23 +5516,67 @@ ${e.stack}` : JSON.stringify(e);
|
|
|
5517
5516
|
* @returns Cards sorted by score descending
|
|
5518
5517
|
*/
|
|
5519
5518
|
_pendingHints = null;
|
|
5519
|
+
/**
|
|
5520
|
+
* Session-scoped cache of the broad ELO-neighbor pool used by
|
|
5521
|
+
* getCardsCenteredAtELO. The `elo` view query re-indexes on first touch per
|
|
5522
|
+
* call (PouchDB 9 ignores `stale`), so without this each plan/replan pays
|
|
5523
|
+
* ~1.5-2s. The pool is fetched once and re-ranked against the live (roaming)
|
|
5524
|
+
* ELO in memory on subsequent calls.
|
|
5525
|
+
*/
|
|
5526
|
+
_eloPoolCache = null;
|
|
5527
|
+
_eloPoolTtlMs = 5 * 60 * 1e3;
|
|
5528
|
+
/**
|
|
5529
|
+
* Cached assembled navigator (Pipeline). createNavigator() reads strategy
|
|
5530
|
+
* docs and builds a fresh Pipeline every call — whose internal `_tagCache`
|
|
5531
|
+
* and `_cachedOrchestration` are designed to make replans cheap but never
|
|
5532
|
+
* survive, because the instance is discarded each run. Caching it lets those
|
|
5533
|
+
* caches persist across plan/replan within a session (SessionController holds
|
|
5534
|
+
* one CourseDB instance for the session's lifetime). Rebuilt on user change,
|
|
5535
|
+
* TTL expiry, or explicit invalidation after a strategy-doc write.
|
|
5536
|
+
*/
|
|
5537
|
+
_cachedNavigator = null;
|
|
5538
|
+
_navigatorTtlMs = 5 * 60 * 1e3;
|
|
5520
5539
|
setEphemeralHints(hints) {
|
|
5521
5540
|
this._pendingHints = hints;
|
|
5522
5541
|
}
|
|
5523
5542
|
async getWeightedCards(limit) {
|
|
5524
5543
|
const u = await this._getCurrentUser();
|
|
5525
5544
|
try {
|
|
5526
|
-
const navigator2 = await this.
|
|
5545
|
+
const { navigator: navigator2 } = await this._getCachedNavigator(u);
|
|
5527
5546
|
if (this._pendingHints) {
|
|
5528
5547
|
navigator2.setEphemeralHints(this._pendingHints);
|
|
5529
5548
|
this._pendingHints = null;
|
|
5530
5549
|
}
|
|
5531
|
-
|
|
5550
|
+
const result = await navigator2.getWeightedCards(limit);
|
|
5551
|
+
return result;
|
|
5532
5552
|
} catch (e) {
|
|
5533
5553
|
logger.error(`[courseDB] Error getting weighted cards: ${e}`);
|
|
5534
5554
|
throw e;
|
|
5535
5555
|
}
|
|
5536
5556
|
}
|
|
5557
|
+
/**
|
|
5558
|
+
* Return the assembled navigator, reusing the cached instance when possible.
|
|
5559
|
+
* Reuse preserves the Pipeline's per-session caches (tags, orchestration
|
|
5560
|
+
* context) across replans, which is the dominant per-replan cost once the
|
|
5561
|
+
* ELO-pool cost is removed. Rebuilds on user change or TTL expiry.
|
|
5562
|
+
*/
|
|
5563
|
+
async _getCachedNavigator(user) {
|
|
5564
|
+
const userId = user.getUsername();
|
|
5565
|
+
const now = Date.now();
|
|
5566
|
+
if (this._cachedNavigator && this._cachedNavigator.userId === userId && now - this._cachedNavigator.builtAt < this._navigatorTtlMs) {
|
|
5567
|
+
return { navigator: this._cachedNavigator.navigator, cacheStatus: "hit" };
|
|
5568
|
+
}
|
|
5569
|
+
const navigator2 = await this.createNavigator(user);
|
|
5570
|
+
this._cachedNavigator = { navigator: navigator2, userId, builtAt: now };
|
|
5571
|
+
return { navigator: navigator2, cacheStatus: "miss" };
|
|
5572
|
+
}
|
|
5573
|
+
/**
|
|
5574
|
+
* Drop the cached navigator so the next getWeightedCards() rebuilds it.
|
|
5575
|
+
* Call after mutating this course's navigation strategy documents.
|
|
5576
|
+
*/
|
|
5577
|
+
invalidateNavigatorCache() {
|
|
5578
|
+
this._cachedNavigator = null;
|
|
5579
|
+
}
|
|
5537
5580
|
async getCardsCenteredAtELO(options = {
|
|
5538
5581
|
limit: 99,
|
|
5539
5582
|
elo: "user"
|
|
@@ -5556,20 +5599,31 @@ ${e.stack}` : JSON.stringify(e);
|
|
|
5556
5599
|
} else {
|
|
5557
5600
|
targetElo = options.elo;
|
|
5558
5601
|
}
|
|
5559
|
-
|
|
5560
|
-
|
|
5561
|
-
let
|
|
5562
|
-
|
|
5563
|
-
|
|
5564
|
-
|
|
5565
|
-
|
|
5566
|
-
|
|
5567
|
-
|
|
5568
|
-
|
|
5569
|
-
|
|
5570
|
-
|
|
5571
|
-
|
|
5572
|
-
|
|
5602
|
+
const POOL_SIZE = Math.max(2e3, options.limit * 4);
|
|
5603
|
+
const nowMs = Date.now();
|
|
5604
|
+
let cacheStatus = "hit";
|
|
5605
|
+
if (!this._eloPoolCache || nowMs - this._eloPoolCache.fetchedAt > this._eloPoolTtlMs) {
|
|
5606
|
+
const fetched = await this.getCardsByELO(targetElo, POOL_SIZE);
|
|
5607
|
+
if (fetched.length > 0) {
|
|
5608
|
+
this._eloPoolCache = { rows: fetched, fetchedAt: nowMs };
|
|
5609
|
+
}
|
|
5610
|
+
cacheStatus = "miss";
|
|
5611
|
+
}
|
|
5612
|
+
const rankAgainstCurrentElo = () => {
|
|
5613
|
+
const raw = this._eloPoolCache?.rows ?? [];
|
|
5614
|
+
const survivors = filter ? raw.filter((c) => filter(c)) : raw;
|
|
5615
|
+
return survivors.map((c) => ({ ...c })).sort(
|
|
5616
|
+
(a, b) => Math.abs((a.elo ?? targetElo) - targetElo) - Math.abs((b.elo ?? targetElo) - targetElo)
|
|
5617
|
+
);
|
|
5618
|
+
};
|
|
5619
|
+
let cards = rankAgainstCurrentElo();
|
|
5620
|
+
if (cards.length < options.limit && (cacheStatus === "hit" || !this._eloPoolCache)) {
|
|
5621
|
+
const fetched = await this.getCardsByELO(targetElo, POOL_SIZE);
|
|
5622
|
+
if (fetched.length > 0) {
|
|
5623
|
+
this._eloPoolCache = { rows: fetched, fetchedAt: nowMs };
|
|
5624
|
+
}
|
|
5625
|
+
cards = rankAgainstCurrentElo();
|
|
5626
|
+
cacheStatus = "refresh";
|
|
5573
5627
|
}
|
|
5574
5628
|
const selectedCards = [];
|
|
5575
5629
|
while (selectedCards.length < options.limit && cards.length > 0) {
|
|
@@ -6421,9 +6475,14 @@ var init_UserDBDebugger = __esm({
|
|
|
6421
6475
|
*/
|
|
6422
6476
|
async showScheduledReviews(courseId) {
|
|
6423
6477
|
const userDB = getUserDB();
|
|
6424
|
-
if (!userDB)
|
|
6478
|
+
if (!userDB) {
|
|
6479
|
+
logger.info("[UserDB Debug] Data layer not available");
|
|
6480
|
+
return;
|
|
6481
|
+
}
|
|
6482
|
+
logger.info(`[UserDB Debug] Fetching pending reviews${courseId ? ` for course: ${courseId}` : ""}...`);
|
|
6425
6483
|
try {
|
|
6426
6484
|
const reviews = await userDB.getPendingReviews(courseId);
|
|
6485
|
+
logger.info(`[UserDB Debug] Got ${reviews.length} reviews`);
|
|
6427
6486
|
console.group(`\u{1F4C5} Scheduled Reviews${courseId ? ` (${courseId})` : ""}`);
|
|
6428
6487
|
logger.info(`Total: ${reviews.length}`);
|
|
6429
6488
|
if (reviews.length > 0) {
|