@vue-skuilder/db 0.1.33 → 0.1.34
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 +26 -6
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.mjs +26 -6
- package/dist/core/index.mjs.map +1 -1
- package/dist/impl/couch/index.js +26 -6
- package/dist/impl/couch/index.js.map +1 -1
- package/dist/impl/couch/index.mjs +26 -6
- package/dist/impl/couch/index.mjs.map +1 -1
- package/dist/impl/static/index.js +26 -6
- package/dist/impl/static/index.js.map +1 -1
- package/dist/impl/static/index.mjs +26 -6
- package/dist/impl/static/index.mjs.map +1 -1
- package/dist/index.d.cts +12 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +62 -23
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +62 -23
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
- package/src/core/navigators/generators/srs.ts +23 -1
- package/src/impl/common/BaseUserDB.ts +9 -6
- package/src/study/SessionController.ts +50 -20
- package/src/study/services/ResponseProcessor.ts +5 -1
package/dist/index.mjs
CHANGED
|
@@ -2539,7 +2539,26 @@ var init_srs = __esm({
|
|
|
2539
2539
|
const courseId = this.course.getCourseID();
|
|
2540
2540
|
const reviews = await this.user.getPendingReviews(courseId);
|
|
2541
2541
|
const now = moment3.utc();
|
|
2542
|
-
|
|
2542
|
+
let dueReviews = reviews.filter((r) => now.isAfter(moment3.utc(r.reviewTime)));
|
|
2543
|
+
if (dueReviews.length > 0) {
|
|
2544
|
+
const dueCardIds = [...new Set(dueReviews.map((r) => r.cardId))];
|
|
2545
|
+
const tagsByCard = await this.course.getAppliedTagsBatch(dueCardIds);
|
|
2546
|
+
const skippedReviewIds = [];
|
|
2547
|
+
dueReviews = dueReviews.filter((r) => {
|
|
2548
|
+
const tags = tagsByCard.get(r.cardId) ?? [];
|
|
2549
|
+
if (tags.includes("srs:skip")) {
|
|
2550
|
+
skippedReviewIds.push(r._id);
|
|
2551
|
+
return false;
|
|
2552
|
+
}
|
|
2553
|
+
return true;
|
|
2554
|
+
});
|
|
2555
|
+
if (skippedReviewIds.length > 0) {
|
|
2556
|
+
logger.info(`[SRS] Removing ${skippedReviewIds.length} scheduled reviews for srs:skip cards`);
|
|
2557
|
+
for (const id of skippedReviewIds) {
|
|
2558
|
+
void this.user.removeScheduledCardReview(id);
|
|
2559
|
+
}
|
|
2560
|
+
}
|
|
2561
|
+
}
|
|
2543
2562
|
const backlogPressure = this.computeBacklogPressure(dueReviews.length);
|
|
2544
2563
|
if (dueReviews.length > 0) {
|
|
2545
2564
|
const pressureNote = backlogPressure > 0 ? ` [backlog pressure: +${backlogPressure.toFixed(2)}]` : ` [healthy backlog]`;
|
|
@@ -7764,17 +7783,18 @@ Currently logged-in as ${this._username}.`
|
|
|
7764
7783
|
* @param course_id optional specification of individual course
|
|
7765
7784
|
*/
|
|
7766
7785
|
async getSeenCards(course_id) {
|
|
7767
|
-
|
|
7786
|
+
const basePrefix = DocTypePrefixes["CARDRECORD" /* CARDRECORD */];
|
|
7787
|
+
let filterPrefix = basePrefix;
|
|
7768
7788
|
if (course_id) {
|
|
7769
|
-
|
|
7789
|
+
filterPrefix += `-${course_id}-`;
|
|
7770
7790
|
}
|
|
7771
|
-
const docs = await filterAllDocsByPrefix(this.localDB,
|
|
7791
|
+
const docs = await filterAllDocsByPrefix(this.localDB, filterPrefix, {
|
|
7772
7792
|
include_docs: false
|
|
7773
7793
|
});
|
|
7774
7794
|
const ret = [];
|
|
7775
7795
|
docs.rows.forEach((row) => {
|
|
7776
|
-
if (row.id.startsWith(
|
|
7777
|
-
ret.push(row.id.substr(
|
|
7796
|
+
if (row.id.startsWith(filterPrefix)) {
|
|
7797
|
+
ret.push(row.id.substr(filterPrefix.length));
|
|
7778
7798
|
}
|
|
7779
7799
|
});
|
|
7780
7800
|
return ret;
|
|
@@ -10572,7 +10592,10 @@ var ResponseProcessor = class {
|
|
|
10572
10592
|
*/
|
|
10573
10593
|
processCorrectResponse(cardRecord, history, studySessionItem, courseRegistrationDoc, currentCard, courseId, cardId) {
|
|
10574
10594
|
if (cardRecord.priorAttemps === 0) {
|
|
10575
|
-
|
|
10595
|
+
const skipSrs = currentCard.card.tags.includes("srs:skip");
|
|
10596
|
+
if (!skipSrs) {
|
|
10597
|
+
void this.srsService.scheduleReview(history, studySessionItem);
|
|
10598
|
+
}
|
|
10576
10599
|
const { globalScore, taggedPerformance } = this.parsePerformance(cardRecord.performance);
|
|
10577
10600
|
if (taggedPerformance) {
|
|
10578
10601
|
const tagKeys = Object.keys(taggedPerformance).filter((k) => k !== "_global");
|
|
@@ -13069,6 +13092,15 @@ var SessionController = class _SessionController extends Loggable {
|
|
|
13069
13092
|
* Individual replans can override via `ReplanOptions.limit`.
|
|
13070
13093
|
*/
|
|
13071
13094
|
_defaultBatchLimit = 20;
|
|
13095
|
+
/**
|
|
13096
|
+
* Maximum number of reviews enqueued at session start. Reviews live
|
|
13097
|
+
* outside the replan flow — the queue drains via consumption and is
|
|
13098
|
+
* not refilled mid-session. The session timer caps total review
|
|
13099
|
+
* exposure, so overfilling here is intentional. Default is generous
|
|
13100
|
+
* to accommodate Anki-style power users with hundreds of due reviews;
|
|
13101
|
+
* apps targeting nimbler sessions should override via constructor.
|
|
13102
|
+
*/
|
|
13103
|
+
_initialReviewCap = 200;
|
|
13072
13104
|
sources;
|
|
13073
13105
|
// dataLayer and getViewComponent now injected into CardHydrationService
|
|
13074
13106
|
_sessionRecord = [];
|
|
@@ -13144,6 +13176,8 @@ var SessionController = class _SessionController extends Loggable {
|
|
|
13144
13176
|
* @param options.defaultBatchLimit - Default pipeline batch size (default: 20).
|
|
13145
13177
|
* Smaller values for newer users cause more frequent replans, keeping plans
|
|
13146
13178
|
* aligned with rapidly-changing user state.
|
|
13179
|
+
* @param options.initialReviewCap - Max reviews loaded at session start (default: 200).
|
|
13180
|
+
* Applied only on initial planning; replans do not refill the review queue.
|
|
13147
13181
|
*/
|
|
13148
13182
|
constructor(sources, time, dataLayer, getViewComponent, mixer, options) {
|
|
13149
13183
|
super();
|
|
@@ -13166,10 +13200,14 @@ var SessionController = class _SessionController extends Loggable {
|
|
|
13166
13200
|
if (options?.defaultBatchLimit !== void 0) {
|
|
13167
13201
|
this._defaultBatchLimit = options.defaultBatchLimit;
|
|
13168
13202
|
}
|
|
13203
|
+
if (options?.initialReviewCap !== void 0) {
|
|
13204
|
+
this._initialReviewCap = options.initialReviewCap;
|
|
13205
|
+
}
|
|
13169
13206
|
this.log(`Session constructed:
|
|
13170
13207
|
startTime: ${this.startTime}
|
|
13171
13208
|
endTime: ${this.endTime}
|
|
13172
|
-
defaultBatchLimit: ${this._defaultBatchLimit}
|
|
13209
|
+
defaultBatchLimit: ${this._defaultBatchLimit}
|
|
13210
|
+
initialReviewCap: ${this._initialReviewCap}`);
|
|
13173
13211
|
}
|
|
13174
13212
|
tick() {
|
|
13175
13213
|
this._secondsRemaining = Math.floor((this.endTime.valueOf() - Date.now()) / 1e3);
|
|
@@ -13254,16 +13292,16 @@ var SessionController = class _SessionController extends Loggable {
|
|
|
13254
13292
|
this.log("Replan already in progress, awaiting existing replan");
|
|
13255
13293
|
return this._replanPromise;
|
|
13256
13294
|
}
|
|
13295
|
+
if (!opts.hints) opts.hints = {};
|
|
13296
|
+
const hints = opts.hints;
|
|
13297
|
+
const excludeSet = new Set(hints.excludeCards ?? []);
|
|
13257
13298
|
if (this._currentCard?.item.cardID) {
|
|
13258
|
-
|
|
13259
|
-
|
|
13260
|
-
|
|
13261
|
-
|
|
13262
|
-
if (!excludeCards.includes(currentId)) {
|
|
13263
|
-
excludeCards.push(currentId);
|
|
13264
|
-
}
|
|
13265
|
-
hints.excludeCards = excludeCards;
|
|
13299
|
+
excludeSet.add(this._currentCard.item.cardID);
|
|
13300
|
+
}
|
|
13301
|
+
for (const rec of this._sessionRecord) {
|
|
13302
|
+
excludeSet.add(rec.card.card_id);
|
|
13266
13303
|
}
|
|
13304
|
+
hints.excludeCards = [...excludeSet];
|
|
13267
13305
|
if (opts.hints) {
|
|
13268
13306
|
const hintsWithLabel = opts.label ? { ...opts.hints, _label: opts.label } : opts.hints;
|
|
13269
13307
|
for (const source of this.sources) {
|
|
@@ -13433,12 +13471,13 @@ var SessionController = class _SessionController extends Loggable {
|
|
|
13433
13471
|
async getWeightedContent(options) {
|
|
13434
13472
|
const replan = options?.replan ?? false;
|
|
13435
13473
|
const additive = options?.additive ?? false;
|
|
13436
|
-
const
|
|
13474
|
+
const newLimit = options?.limit ?? this._defaultBatchLimit;
|
|
13475
|
+
const fetchLimit = replan ? newLimit : newLimit + this._initialReviewCap;
|
|
13437
13476
|
const batches = [];
|
|
13438
13477
|
for (let i = 0; i < this.sources.length; i++) {
|
|
13439
13478
|
const source = this.sources[i];
|
|
13440
13479
|
try {
|
|
13441
|
-
const weighted = (await source.getWeightedCards(
|
|
13480
|
+
const weighted = (await source.getWeightedCards(fetchLimit)).cards;
|
|
13442
13481
|
batches.push({
|
|
13443
13482
|
sourceIndex: i,
|
|
13444
13483
|
weighted
|
|
@@ -13459,7 +13498,7 @@ var SessionController = class _SessionController extends Loggable {
|
|
|
13459
13498
|
`Cannot start session: failed to load content from all ${this.sources.length} source(s). Check logs for details.`
|
|
13460
13499
|
);
|
|
13461
13500
|
}
|
|
13462
|
-
const mixedWeighted = this.mixer.mix(batches,
|
|
13501
|
+
const mixedWeighted = this.mixer.mix(batches, fetchLimit * this.sources.length);
|
|
13463
13502
|
const sourceIds = batches.map((b) => {
|
|
13464
13503
|
const firstCard = b.weighted[0];
|
|
13465
13504
|
return firstCard?.courseId || `source-${b.sourceIndex}`;
|
|
@@ -13476,18 +13515,18 @@ var SessionController = class _SessionController extends Loggable {
|
|
|
13476
13515
|
})
|
|
13477
13516
|
);
|
|
13478
13517
|
const sourceNames = sourceIds.map((id) => this.courseNameCache.get(id));
|
|
13479
|
-
const quotaPerSource = this.mixer instanceof QuotaRoundRobinMixer ? Math.ceil(
|
|
13518
|
+
const quotaPerSource = this.mixer instanceof QuotaRoundRobinMixer ? Math.ceil(fetchLimit * this.sources.length / batches.length) : void 0;
|
|
13480
13519
|
captureMixerRun(
|
|
13481
13520
|
this.mixer.constructor.name,
|
|
13482
13521
|
batches,
|
|
13483
13522
|
sourceIds,
|
|
13484
13523
|
sourceNames,
|
|
13485
|
-
|
|
13524
|
+
fetchLimit * this.sources.length,
|
|
13486
13525
|
quotaPerSource,
|
|
13487
13526
|
mixedWeighted
|
|
13488
13527
|
);
|
|
13489
|
-
const reviewWeighted = mixedWeighted.filter((w) => getCardOrigin(w) === "review");
|
|
13490
|
-
const newWeighted = mixedWeighted.filter((w) => getCardOrigin(w) === "new");
|
|
13528
|
+
const reviewWeighted = mixedWeighted.filter((w) => getCardOrigin(w) === "review").slice(0, this._initialReviewCap);
|
|
13529
|
+
const newWeighted = mixedWeighted.filter((w) => getCardOrigin(w) === "new").slice(0, newLimit);
|
|
13491
13530
|
logger.debug(`[reviews] got ${reviewWeighted.length} reviews from mixer`);
|
|
13492
13531
|
let report = replan ? "Replan content:\n" : "Mixed content session created with:\n";
|
|
13493
13532
|
if (!replan) {
|