@vue-skuilder/db 0.2.1 → 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 +74 -20
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.mjs +74 -20
- 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 +74 -20
- package/dist/impl/couch/index.js.map +1 -1
- package/dist/impl/couch/index.mjs +74 -20
- package/dist/impl/couch/index.mjs.map +1 -1
- package/dist/impl/static/index.js +2 -4
- package/dist/impl/static/index.js.map +1 -1
- package/dist/impl/static/index.mjs +2 -4
- package/dist/impl/static/index.mjs.map +1 -1
- package/dist/index.js +74 -20
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +74 -20
- package/dist/index.mjs.map +1 -1
- package/docs/session-lifecycle-and-replan.md +418 -0
- package/package.json +3 -3
- 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 +33 -0
- package/src/study/services/CardHydrationService.ts +24 -0
package/dist/index.js
CHANGED
|
@@ -1982,10 +1982,8 @@ var init_elo = __esm({
|
|
|
1982
1982
|
{ limit, elo: "user" },
|
|
1983
1983
|
(c) => !activeCards.some((ac) => c.cardID === ac.cardID)
|
|
1984
1984
|
)).map((c) => ({ ...c, status: "new" }));
|
|
1985
|
-
const
|
|
1986
|
-
|
|
1987
|
-
const scored = newCards.map((c, i) => {
|
|
1988
|
-
const cardElo = cardEloData[i]?.global?.score ?? 1e3;
|
|
1985
|
+
const scored = newCards.map((c) => {
|
|
1986
|
+
const cardElo = c.elo ?? 1e3;
|
|
1989
1987
|
const distance = Math.abs(cardElo - userGlobalElo);
|
|
1990
1988
|
const rawScore = Math.max(0, 1 - distance / 500);
|
|
1991
1989
|
const samplingKey = rawScore > 0 ? Math.random() ** (1 / rawScore) : 0;
|
|
@@ -5901,6 +5899,7 @@ ${above.rows.map((r) => ` ${r.id}-${r.key}
|
|
|
5901
5899
|
}
|
|
5902
5900
|
async addNavigationStrategy(data) {
|
|
5903
5901
|
logger.debug(`[courseDB] Adding navigation strategy: ${data._id}`);
|
|
5902
|
+
this.invalidateNavigatorCache();
|
|
5904
5903
|
return this.remoteDB.put(data).then(() => {
|
|
5905
5904
|
});
|
|
5906
5905
|
}
|
|
@@ -5967,23 +5966,67 @@ ${e.stack}` : JSON.stringify(e);
|
|
|
5967
5966
|
* @returns Cards sorted by score descending
|
|
5968
5967
|
*/
|
|
5969
5968
|
_pendingHints = null;
|
|
5969
|
+
/**
|
|
5970
|
+
* Session-scoped cache of the broad ELO-neighbor pool used by
|
|
5971
|
+
* getCardsCenteredAtELO. The `elo` view query re-indexes on first touch per
|
|
5972
|
+
* call (PouchDB 9 ignores `stale`), so without this each plan/replan pays
|
|
5973
|
+
* ~1.5-2s. The pool is fetched once and re-ranked against the live (roaming)
|
|
5974
|
+
* ELO in memory on subsequent calls.
|
|
5975
|
+
*/
|
|
5976
|
+
_eloPoolCache = null;
|
|
5977
|
+
_eloPoolTtlMs = 5 * 60 * 1e3;
|
|
5978
|
+
/**
|
|
5979
|
+
* Cached assembled navigator (Pipeline). createNavigator() reads strategy
|
|
5980
|
+
* docs and builds a fresh Pipeline every call — whose internal `_tagCache`
|
|
5981
|
+
* and `_cachedOrchestration` are designed to make replans cheap but never
|
|
5982
|
+
* survive, because the instance is discarded each run. Caching it lets those
|
|
5983
|
+
* caches persist across plan/replan within a session (SessionController holds
|
|
5984
|
+
* one CourseDB instance for the session's lifetime). Rebuilt on user change,
|
|
5985
|
+
* TTL expiry, or explicit invalidation after a strategy-doc write.
|
|
5986
|
+
*/
|
|
5987
|
+
_cachedNavigator = null;
|
|
5988
|
+
_navigatorTtlMs = 5 * 60 * 1e3;
|
|
5970
5989
|
setEphemeralHints(hints) {
|
|
5971
5990
|
this._pendingHints = hints;
|
|
5972
5991
|
}
|
|
5973
5992
|
async getWeightedCards(limit) {
|
|
5974
5993
|
const u = await this._getCurrentUser();
|
|
5975
5994
|
try {
|
|
5976
|
-
const navigator2 = await this.
|
|
5995
|
+
const { navigator: navigator2 } = await this._getCachedNavigator(u);
|
|
5977
5996
|
if (this._pendingHints) {
|
|
5978
5997
|
navigator2.setEphemeralHints(this._pendingHints);
|
|
5979
5998
|
this._pendingHints = null;
|
|
5980
5999
|
}
|
|
5981
|
-
|
|
6000
|
+
const result = await navigator2.getWeightedCards(limit);
|
|
6001
|
+
return result;
|
|
5982
6002
|
} catch (e) {
|
|
5983
6003
|
logger.error(`[courseDB] Error getting weighted cards: ${e}`);
|
|
5984
6004
|
throw e;
|
|
5985
6005
|
}
|
|
5986
6006
|
}
|
|
6007
|
+
/**
|
|
6008
|
+
* Return the assembled navigator, reusing the cached instance when possible.
|
|
6009
|
+
* Reuse preserves the Pipeline's per-session caches (tags, orchestration
|
|
6010
|
+
* context) across replans, which is the dominant per-replan cost once the
|
|
6011
|
+
* ELO-pool cost is removed. Rebuilds on user change or TTL expiry.
|
|
6012
|
+
*/
|
|
6013
|
+
async _getCachedNavigator(user) {
|
|
6014
|
+
const userId = user.getUsername();
|
|
6015
|
+
const now = Date.now();
|
|
6016
|
+
if (this._cachedNavigator && this._cachedNavigator.userId === userId && now - this._cachedNavigator.builtAt < this._navigatorTtlMs) {
|
|
6017
|
+
return { navigator: this._cachedNavigator.navigator, cacheStatus: "hit" };
|
|
6018
|
+
}
|
|
6019
|
+
const navigator2 = await this.createNavigator(user);
|
|
6020
|
+
this._cachedNavigator = { navigator: navigator2, userId, builtAt: now };
|
|
6021
|
+
return { navigator: navigator2, cacheStatus: "miss" };
|
|
6022
|
+
}
|
|
6023
|
+
/**
|
|
6024
|
+
* Drop the cached navigator so the next getWeightedCards() rebuilds it.
|
|
6025
|
+
* Call after mutating this course's navigation strategy documents.
|
|
6026
|
+
*/
|
|
6027
|
+
invalidateNavigatorCache() {
|
|
6028
|
+
this._cachedNavigator = null;
|
|
6029
|
+
}
|
|
5987
6030
|
async getCardsCenteredAtELO(options = {
|
|
5988
6031
|
limit: 99,
|
|
5989
6032
|
elo: "user"
|
|
@@ -6006,20 +6049,31 @@ ${e.stack}` : JSON.stringify(e);
|
|
|
6006
6049
|
} else {
|
|
6007
6050
|
targetElo = options.elo;
|
|
6008
6051
|
}
|
|
6009
|
-
|
|
6010
|
-
|
|
6011
|
-
let
|
|
6012
|
-
|
|
6013
|
-
|
|
6014
|
-
|
|
6015
|
-
|
|
6016
|
-
|
|
6017
|
-
|
|
6018
|
-
|
|
6019
|
-
|
|
6020
|
-
|
|
6021
|
-
|
|
6022
|
-
|
|
6052
|
+
const POOL_SIZE = Math.max(2e3, options.limit * 4);
|
|
6053
|
+
const nowMs = Date.now();
|
|
6054
|
+
let cacheStatus = "hit";
|
|
6055
|
+
if (!this._eloPoolCache || nowMs - this._eloPoolCache.fetchedAt > this._eloPoolTtlMs) {
|
|
6056
|
+
const fetched = await this.getCardsByELO(targetElo, POOL_SIZE);
|
|
6057
|
+
if (fetched.length > 0) {
|
|
6058
|
+
this._eloPoolCache = { rows: fetched, fetchedAt: nowMs };
|
|
6059
|
+
}
|
|
6060
|
+
cacheStatus = "miss";
|
|
6061
|
+
}
|
|
6062
|
+
const rankAgainstCurrentElo = () => {
|
|
6063
|
+
const raw = this._eloPoolCache?.rows ?? [];
|
|
6064
|
+
const survivors = filter ? raw.filter((c) => filter(c)) : raw;
|
|
6065
|
+
return survivors.map((c) => ({ ...c })).sort(
|
|
6066
|
+
(a, b) => Math.abs((a.elo ?? targetElo) - targetElo) - Math.abs((b.elo ?? targetElo) - targetElo)
|
|
6067
|
+
);
|
|
6068
|
+
};
|
|
6069
|
+
let cards = rankAgainstCurrentElo();
|
|
6070
|
+
if (cards.length < options.limit && (cacheStatus === "hit" || !this._eloPoolCache)) {
|
|
6071
|
+
const fetched = await this.getCardsByELO(targetElo, POOL_SIZE);
|
|
6072
|
+
if (fetched.length > 0) {
|
|
6073
|
+
this._eloPoolCache = { rows: fetched, fetchedAt: nowMs };
|
|
6074
|
+
}
|
|
6075
|
+
cards = rankAgainstCurrentElo();
|
|
6076
|
+
cacheStatus = "refresh";
|
|
6023
6077
|
}
|
|
6024
6078
|
const selectedCards = [];
|
|
6025
6079
|
while (selectedCards.length < options.limit && cards.length > 0) {
|