@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
package/dist/core/index.mjs
CHANGED
|
@@ -1806,10 +1806,8 @@ var init_elo = __esm({
|
|
|
1806
1806
|
{ limit, elo: "user" },
|
|
1807
1807
|
(c) => !activeCards.some((ac) => c.cardID === ac.cardID)
|
|
1808
1808
|
)).map((c) => ({ ...c, status: "new" }));
|
|
1809
|
-
const
|
|
1810
|
-
|
|
1811
|
-
const scored = newCards.map((c, i) => {
|
|
1812
|
-
const cardElo = cardEloData[i]?.global?.score ?? 1e3;
|
|
1809
|
+
const scored = newCards.map((c) => {
|
|
1810
|
+
const cardElo = c.elo ?? 1e3;
|
|
1813
1811
|
const distance = Math.abs(cardElo - userGlobalElo);
|
|
1814
1812
|
const rawScore = Math.max(0, 1 - distance / 500);
|
|
1815
1813
|
const samplingKey = rawScore > 0 ? Math.random() ** (1 / rawScore) : 0;
|
|
@@ -4033,7 +4031,8 @@ var init_orchestration = __esm({
|
|
|
4033
4031
|
// src/core/navigators/Pipeline.ts
|
|
4034
4032
|
var Pipeline_exports = {};
|
|
4035
4033
|
__export(Pipeline_exports, {
|
|
4036
|
-
Pipeline: () => Pipeline
|
|
4034
|
+
Pipeline: () => Pipeline,
|
|
4035
|
+
mergeHints: () => mergeHints2
|
|
4037
4036
|
});
|
|
4038
4037
|
import { toCourseElo as toCourseElo5 } from "@vue-skuilder/common";
|
|
4039
4038
|
function globToRegex(pattern) {
|
|
@@ -5685,6 +5684,7 @@ ${above.rows.map((r) => ` ${r.id}-${r.key}
|
|
|
5685
5684
|
}
|
|
5686
5685
|
async addNavigationStrategy(data) {
|
|
5687
5686
|
logger.debug(`[courseDB] Adding navigation strategy: ${data._id}`);
|
|
5687
|
+
this.invalidateNavigatorCache();
|
|
5688
5688
|
return this.remoteDB.put(data).then(() => {
|
|
5689
5689
|
});
|
|
5690
5690
|
}
|
|
@@ -5751,23 +5751,67 @@ ${e.stack}` : JSON.stringify(e);
|
|
|
5751
5751
|
* @returns Cards sorted by score descending
|
|
5752
5752
|
*/
|
|
5753
5753
|
_pendingHints = null;
|
|
5754
|
+
/**
|
|
5755
|
+
* Session-scoped cache of the broad ELO-neighbor pool used by
|
|
5756
|
+
* getCardsCenteredAtELO. The `elo` view query re-indexes on first touch per
|
|
5757
|
+
* call (PouchDB 9 ignores `stale`), so without this each plan/replan pays
|
|
5758
|
+
* ~1.5-2s. The pool is fetched once and re-ranked against the live (roaming)
|
|
5759
|
+
* ELO in memory on subsequent calls.
|
|
5760
|
+
*/
|
|
5761
|
+
_eloPoolCache = null;
|
|
5762
|
+
_eloPoolTtlMs = 5 * 60 * 1e3;
|
|
5763
|
+
/**
|
|
5764
|
+
* Cached assembled navigator (Pipeline). createNavigator() reads strategy
|
|
5765
|
+
* docs and builds a fresh Pipeline every call — whose internal `_tagCache`
|
|
5766
|
+
* and `_cachedOrchestration` are designed to make replans cheap but never
|
|
5767
|
+
* survive, because the instance is discarded each run. Caching it lets those
|
|
5768
|
+
* caches persist across plan/replan within a session (SessionController holds
|
|
5769
|
+
* one CourseDB instance for the session's lifetime). Rebuilt on user change,
|
|
5770
|
+
* TTL expiry, or explicit invalidation after a strategy-doc write.
|
|
5771
|
+
*/
|
|
5772
|
+
_cachedNavigator = null;
|
|
5773
|
+
_navigatorTtlMs = 5 * 60 * 1e3;
|
|
5754
5774
|
setEphemeralHints(hints) {
|
|
5755
5775
|
this._pendingHints = hints;
|
|
5756
5776
|
}
|
|
5757
5777
|
async getWeightedCards(limit) {
|
|
5758
5778
|
const u = await this._getCurrentUser();
|
|
5759
5779
|
try {
|
|
5760
|
-
const navigator2 = await this.
|
|
5780
|
+
const { navigator: navigator2 } = await this._getCachedNavigator(u);
|
|
5761
5781
|
if (this._pendingHints) {
|
|
5762
5782
|
navigator2.setEphemeralHints(this._pendingHints);
|
|
5763
5783
|
this._pendingHints = null;
|
|
5764
5784
|
}
|
|
5765
|
-
|
|
5785
|
+
const result = await navigator2.getWeightedCards(limit);
|
|
5786
|
+
return result;
|
|
5766
5787
|
} catch (e) {
|
|
5767
5788
|
logger.error(`[courseDB] Error getting weighted cards: ${e}`);
|
|
5768
5789
|
throw e;
|
|
5769
5790
|
}
|
|
5770
5791
|
}
|
|
5792
|
+
/**
|
|
5793
|
+
* Return the assembled navigator, reusing the cached instance when possible.
|
|
5794
|
+
* Reuse preserves the Pipeline's per-session caches (tags, orchestration
|
|
5795
|
+
* context) across replans, which is the dominant per-replan cost once the
|
|
5796
|
+
* ELO-pool cost is removed. Rebuilds on user change or TTL expiry.
|
|
5797
|
+
*/
|
|
5798
|
+
async _getCachedNavigator(user) {
|
|
5799
|
+
const userId = user.getUsername();
|
|
5800
|
+
const now = Date.now();
|
|
5801
|
+
if (this._cachedNavigator && this._cachedNavigator.userId === userId && now - this._cachedNavigator.builtAt < this._navigatorTtlMs) {
|
|
5802
|
+
return { navigator: this._cachedNavigator.navigator, cacheStatus: "hit" };
|
|
5803
|
+
}
|
|
5804
|
+
const navigator2 = await this.createNavigator(user);
|
|
5805
|
+
this._cachedNavigator = { navigator: navigator2, userId, builtAt: now };
|
|
5806
|
+
return { navigator: navigator2, cacheStatus: "miss" };
|
|
5807
|
+
}
|
|
5808
|
+
/**
|
|
5809
|
+
* Drop the cached navigator so the next getWeightedCards() rebuilds it.
|
|
5810
|
+
* Call after mutating this course's navigation strategy documents.
|
|
5811
|
+
*/
|
|
5812
|
+
invalidateNavigatorCache() {
|
|
5813
|
+
this._cachedNavigator = null;
|
|
5814
|
+
}
|
|
5771
5815
|
async getCardsCenteredAtELO(options = {
|
|
5772
5816
|
limit: 99,
|
|
5773
5817
|
elo: "user"
|
|
@@ -5790,20 +5834,31 @@ ${e.stack}` : JSON.stringify(e);
|
|
|
5790
5834
|
} else {
|
|
5791
5835
|
targetElo = options.elo;
|
|
5792
5836
|
}
|
|
5793
|
-
|
|
5794
|
-
|
|
5795
|
-
let
|
|
5796
|
-
|
|
5797
|
-
|
|
5798
|
-
|
|
5799
|
-
|
|
5800
|
-
|
|
5801
|
-
|
|
5802
|
-
|
|
5803
|
-
|
|
5804
|
-
|
|
5805
|
-
|
|
5806
|
-
|
|
5837
|
+
const POOL_SIZE = Math.max(2e3, options.limit * 4);
|
|
5838
|
+
const nowMs = Date.now();
|
|
5839
|
+
let cacheStatus = "hit";
|
|
5840
|
+
if (!this._eloPoolCache || nowMs - this._eloPoolCache.fetchedAt > this._eloPoolTtlMs) {
|
|
5841
|
+
const fetched = await this.getCardsByELO(targetElo, POOL_SIZE);
|
|
5842
|
+
if (fetched.length > 0) {
|
|
5843
|
+
this._eloPoolCache = { rows: fetched, fetchedAt: nowMs };
|
|
5844
|
+
}
|
|
5845
|
+
cacheStatus = "miss";
|
|
5846
|
+
}
|
|
5847
|
+
const rankAgainstCurrentElo = () => {
|
|
5848
|
+
const raw = this._eloPoolCache?.rows ?? [];
|
|
5849
|
+
const survivors = filter ? raw.filter((c) => filter(c)) : raw;
|
|
5850
|
+
return survivors.map((c) => ({ ...c })).sort(
|
|
5851
|
+
(a, b) => Math.abs((a.elo ?? targetElo) - targetElo) - Math.abs((b.elo ?? targetElo) - targetElo)
|
|
5852
|
+
);
|
|
5853
|
+
};
|
|
5854
|
+
let cards = rankAgainstCurrentElo();
|
|
5855
|
+
if (cards.length < options.limit && (cacheStatus === "hit" || !this._eloPoolCache)) {
|
|
5856
|
+
const fetched = await this.getCardsByELO(targetElo, POOL_SIZE);
|
|
5857
|
+
if (fetched.length > 0) {
|
|
5858
|
+
this._eloPoolCache = { rows: fetched, fetchedAt: nowMs };
|
|
5859
|
+
}
|
|
5860
|
+
cards = rankAgainstCurrentElo();
|
|
5861
|
+
cacheStatus = "refresh";
|
|
5807
5862
|
}
|
|
5808
5863
|
const selectedCards = [];
|
|
5809
5864
|
while (selectedCards.length < options.limit && cards.length > 0) {
|