@vue-skuilder/db 0.1.33 → 0.1.35
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 +65 -23
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +65 -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 +64 -20
- package/src/study/services/ResponseProcessor.ts +5 -1
package/dist/index.d.cts
CHANGED
|
@@ -387,6 +387,15 @@ declare class SessionController<TView = unknown> extends Loggable {
|
|
|
387
387
|
* Individual replans can override via `ReplanOptions.limit`.
|
|
388
388
|
*/
|
|
389
389
|
private _defaultBatchLimit;
|
|
390
|
+
/**
|
|
391
|
+
* Maximum number of reviews enqueued at session start. Reviews live
|
|
392
|
+
* outside the replan flow — the queue drains via consumption and is
|
|
393
|
+
* not refilled mid-session. The session timer caps total review
|
|
394
|
+
* exposure, so overfilling here is intentional. Default is generous
|
|
395
|
+
* to accommodate Anki-style power users with hundreds of due reviews;
|
|
396
|
+
* apps targeting nimbler sessions should override via constructor.
|
|
397
|
+
*/
|
|
398
|
+
private _initialReviewCap;
|
|
390
399
|
private sources;
|
|
391
400
|
private _sessionRecord;
|
|
392
401
|
set sessionRecord(r: StudySessionRecord[]);
|
|
@@ -444,9 +453,12 @@ declare class SessionController<TView = unknown> extends Loggable {
|
|
|
444
453
|
* @param options.defaultBatchLimit - Default pipeline batch size (default: 20).
|
|
445
454
|
* Smaller values for newer users cause more frequent replans, keeping plans
|
|
446
455
|
* aligned with rapidly-changing user state.
|
|
456
|
+
* @param options.initialReviewCap - Max reviews loaded at session start (default: 200).
|
|
457
|
+
* Applied only on initial planning; replans do not refill the review queue.
|
|
447
458
|
*/
|
|
448
459
|
constructor(sources: StudyContentSource[], time: number, dataLayer: DataLayerProvider, getViewComponent: (viewId: string) => TView, mixer?: SourceMixer, options?: {
|
|
449
460
|
defaultBatchLimit?: number;
|
|
461
|
+
initialReviewCap?: number;
|
|
450
462
|
});
|
|
451
463
|
private tick;
|
|
452
464
|
/**
|
package/dist/index.d.ts
CHANGED
|
@@ -387,6 +387,15 @@ declare class SessionController<TView = unknown> extends Loggable {
|
|
|
387
387
|
* Individual replans can override via `ReplanOptions.limit`.
|
|
388
388
|
*/
|
|
389
389
|
private _defaultBatchLimit;
|
|
390
|
+
/**
|
|
391
|
+
* Maximum number of reviews enqueued at session start. Reviews live
|
|
392
|
+
* outside the replan flow — the queue drains via consumption and is
|
|
393
|
+
* not refilled mid-session. The session timer caps total review
|
|
394
|
+
* exposure, so overfilling here is intentional. Default is generous
|
|
395
|
+
* to accommodate Anki-style power users with hundreds of due reviews;
|
|
396
|
+
* apps targeting nimbler sessions should override via constructor.
|
|
397
|
+
*/
|
|
398
|
+
private _initialReviewCap;
|
|
390
399
|
private sources;
|
|
391
400
|
private _sessionRecord;
|
|
392
401
|
set sessionRecord(r: StudySessionRecord[]);
|
|
@@ -444,9 +453,12 @@ declare class SessionController<TView = unknown> extends Loggable {
|
|
|
444
453
|
* @param options.defaultBatchLimit - Default pipeline batch size (default: 20).
|
|
445
454
|
* Smaller values for newer users cause more frequent replans, keeping plans
|
|
446
455
|
* aligned with rapidly-changing user state.
|
|
456
|
+
* @param options.initialReviewCap - Max reviews loaded at session start (default: 200).
|
|
457
|
+
* Applied only on initial planning; replans do not refill the review queue.
|
|
447
458
|
*/
|
|
448
459
|
constructor(sources: StudyContentSource[], time: number, dataLayer: DataLayerProvider, getViewComponent: (viewId: string) => TView, mixer?: SourceMixer, options?: {
|
|
449
460
|
defaultBatchLimit?: number;
|
|
461
|
+
initialReviewCap?: number;
|
|
450
462
|
});
|
|
451
463
|
private tick;
|
|
452
464
|
/**
|
package/dist/index.js
CHANGED
|
@@ -2562,7 +2562,26 @@ var init_srs = __esm({
|
|
|
2562
2562
|
const courseId = this.course.getCourseID();
|
|
2563
2563
|
const reviews = await this.user.getPendingReviews(courseId);
|
|
2564
2564
|
const now = import_moment3.default.utc();
|
|
2565
|
-
|
|
2565
|
+
let dueReviews = reviews.filter((r) => now.isAfter(import_moment3.default.utc(r.reviewTime)));
|
|
2566
|
+
if (dueReviews.length > 0) {
|
|
2567
|
+
const dueCardIds = [...new Set(dueReviews.map((r) => r.cardId))];
|
|
2568
|
+
const tagsByCard = await this.course.getAppliedTagsBatch(dueCardIds);
|
|
2569
|
+
const skippedReviewIds = [];
|
|
2570
|
+
dueReviews = dueReviews.filter((r) => {
|
|
2571
|
+
const tags = tagsByCard.get(r.cardId) ?? [];
|
|
2572
|
+
if (tags.includes("srs:skip")) {
|
|
2573
|
+
skippedReviewIds.push(r._id);
|
|
2574
|
+
return false;
|
|
2575
|
+
}
|
|
2576
|
+
return true;
|
|
2577
|
+
});
|
|
2578
|
+
if (skippedReviewIds.length > 0) {
|
|
2579
|
+
logger.info(`[SRS] Removing ${skippedReviewIds.length} scheduled reviews for srs:skip cards`);
|
|
2580
|
+
for (const id of skippedReviewIds) {
|
|
2581
|
+
void this.user.removeScheduledCardReview(id);
|
|
2582
|
+
}
|
|
2583
|
+
}
|
|
2584
|
+
}
|
|
2566
2585
|
const backlogPressure = this.computeBacklogPressure(dueReviews.length);
|
|
2567
2586
|
if (dueReviews.length > 0) {
|
|
2568
2587
|
const pressureNote = backlogPressure > 0 ? ` [backlog pressure: +${backlogPressure.toFixed(2)}]` : ` [healthy backlog]`;
|
|
@@ -7783,17 +7802,18 @@ Currently logged-in as ${this._username}.`
|
|
|
7783
7802
|
* @param course_id optional specification of individual course
|
|
7784
7803
|
*/
|
|
7785
7804
|
async getSeenCards(course_id) {
|
|
7786
|
-
|
|
7805
|
+
const basePrefix = DocTypePrefixes["CARDRECORD" /* CARDRECORD */];
|
|
7806
|
+
let filterPrefix = basePrefix;
|
|
7787
7807
|
if (course_id) {
|
|
7788
|
-
|
|
7808
|
+
filterPrefix += `-${course_id}-`;
|
|
7789
7809
|
}
|
|
7790
|
-
const docs = await filterAllDocsByPrefix(this.localDB,
|
|
7810
|
+
const docs = await filterAllDocsByPrefix(this.localDB, filterPrefix, {
|
|
7791
7811
|
include_docs: false
|
|
7792
7812
|
});
|
|
7793
7813
|
const ret = [];
|
|
7794
7814
|
docs.rows.forEach((row) => {
|
|
7795
|
-
if (row.id.startsWith(
|
|
7796
|
-
ret.push(row.id.substr(
|
|
7815
|
+
if (row.id.startsWith(filterPrefix)) {
|
|
7816
|
+
ret.push(row.id.substr(filterPrefix.length));
|
|
7797
7817
|
}
|
|
7798
7818
|
});
|
|
7799
7819
|
return ret;
|
|
@@ -10678,7 +10698,10 @@ var ResponseProcessor = class {
|
|
|
10678
10698
|
*/
|
|
10679
10699
|
processCorrectResponse(cardRecord, history, studySessionItem, courseRegistrationDoc, currentCard, courseId, cardId) {
|
|
10680
10700
|
if (cardRecord.priorAttemps === 0) {
|
|
10681
|
-
|
|
10701
|
+
const skipSrs = currentCard.card.tags.includes("srs:skip");
|
|
10702
|
+
if (!skipSrs) {
|
|
10703
|
+
void this.srsService.scheduleReview(history, studySessionItem);
|
|
10704
|
+
}
|
|
10682
10705
|
const { globalScore, taggedPerformance } = this.parsePerformance(cardRecord.performance);
|
|
10683
10706
|
if (taggedPerformance) {
|
|
10684
10707
|
const tagKeys = Object.keys(taggedPerformance).filter((k) => k !== "_global");
|
|
@@ -13171,6 +13194,15 @@ var SessionController = class _SessionController extends Loggable {
|
|
|
13171
13194
|
* Individual replans can override via `ReplanOptions.limit`.
|
|
13172
13195
|
*/
|
|
13173
13196
|
_defaultBatchLimit = 20;
|
|
13197
|
+
/**
|
|
13198
|
+
* Maximum number of reviews enqueued at session start. Reviews live
|
|
13199
|
+
* outside the replan flow — the queue drains via consumption and is
|
|
13200
|
+
* not refilled mid-session. The session timer caps total review
|
|
13201
|
+
* exposure, so overfilling here is intentional. Default is generous
|
|
13202
|
+
* to accommodate Anki-style power users with hundreds of due reviews;
|
|
13203
|
+
* apps targeting nimbler sessions should override via constructor.
|
|
13204
|
+
*/
|
|
13205
|
+
_initialReviewCap = 200;
|
|
13174
13206
|
sources;
|
|
13175
13207
|
// dataLayer and getViewComponent now injected into CardHydrationService
|
|
13176
13208
|
_sessionRecord = [];
|
|
@@ -13246,6 +13278,8 @@ var SessionController = class _SessionController extends Loggable {
|
|
|
13246
13278
|
* @param options.defaultBatchLimit - Default pipeline batch size (default: 20).
|
|
13247
13279
|
* Smaller values for newer users cause more frequent replans, keeping plans
|
|
13248
13280
|
* aligned with rapidly-changing user state.
|
|
13281
|
+
* @param options.initialReviewCap - Max reviews loaded at session start (default: 200).
|
|
13282
|
+
* Applied only on initial planning; replans do not refill the review queue.
|
|
13249
13283
|
*/
|
|
13250
13284
|
constructor(sources, time, dataLayer, getViewComponent, mixer, options) {
|
|
13251
13285
|
super();
|
|
@@ -13268,10 +13302,14 @@ var SessionController = class _SessionController extends Loggable {
|
|
|
13268
13302
|
if (options?.defaultBatchLimit !== void 0) {
|
|
13269
13303
|
this._defaultBatchLimit = options.defaultBatchLimit;
|
|
13270
13304
|
}
|
|
13305
|
+
if (options?.initialReviewCap !== void 0) {
|
|
13306
|
+
this._initialReviewCap = options.initialReviewCap;
|
|
13307
|
+
}
|
|
13271
13308
|
this.log(`Session constructed:
|
|
13272
13309
|
startTime: ${this.startTime}
|
|
13273
13310
|
endTime: ${this.endTime}
|
|
13274
|
-
defaultBatchLimit: ${this._defaultBatchLimit}
|
|
13311
|
+
defaultBatchLimit: ${this._defaultBatchLimit}
|
|
13312
|
+
initialReviewCap: ${this._initialReviewCap}`);
|
|
13275
13313
|
}
|
|
13276
13314
|
tick() {
|
|
13277
13315
|
this._secondsRemaining = Math.floor((this.endTime.valueOf() - Date.now()) / 1e3);
|
|
@@ -13356,16 +13394,19 @@ var SessionController = class _SessionController extends Loggable {
|
|
|
13356
13394
|
this.log("Replan already in progress, awaiting existing replan");
|
|
13357
13395
|
return this._replanPromise;
|
|
13358
13396
|
}
|
|
13397
|
+
if (!opts.hints) opts.hints = {};
|
|
13398
|
+
const hints = opts.hints;
|
|
13399
|
+
const excludeSet = new Set(hints.excludeCards ?? []);
|
|
13359
13400
|
if (this._currentCard?.item.cardID) {
|
|
13360
|
-
|
|
13361
|
-
|
|
13362
|
-
|
|
13363
|
-
|
|
13364
|
-
|
|
13365
|
-
|
|
13366
|
-
|
|
13367
|
-
hints.excludeCards = excludeCards;
|
|
13401
|
+
excludeSet.add(this._currentCard.item.cardID);
|
|
13402
|
+
}
|
|
13403
|
+
for (const rec of this._sessionRecord) {
|
|
13404
|
+
excludeSet.add(rec.card.card_id);
|
|
13405
|
+
}
|
|
13406
|
+
if (this.newQ.length > 0) {
|
|
13407
|
+
excludeSet.add(this.newQ.peek(0).cardID);
|
|
13368
13408
|
}
|
|
13409
|
+
hints.excludeCards = [...excludeSet];
|
|
13369
13410
|
if (opts.hints) {
|
|
13370
13411
|
const hintsWithLabel = opts.label ? { ...opts.hints, _label: opts.label } : opts.hints;
|
|
13371
13412
|
for (const source of this.sources) {
|
|
@@ -13535,12 +13576,13 @@ var SessionController = class _SessionController extends Loggable {
|
|
|
13535
13576
|
async getWeightedContent(options) {
|
|
13536
13577
|
const replan = options?.replan ?? false;
|
|
13537
13578
|
const additive = options?.additive ?? false;
|
|
13538
|
-
const
|
|
13579
|
+
const newLimit = options?.limit ?? this._defaultBatchLimit;
|
|
13580
|
+
const fetchLimit = replan ? newLimit : newLimit + this._initialReviewCap;
|
|
13539
13581
|
const batches = [];
|
|
13540
13582
|
for (let i = 0; i < this.sources.length; i++) {
|
|
13541
13583
|
const source = this.sources[i];
|
|
13542
13584
|
try {
|
|
13543
|
-
const weighted = (await source.getWeightedCards(
|
|
13585
|
+
const weighted = (await source.getWeightedCards(fetchLimit)).cards;
|
|
13544
13586
|
batches.push({
|
|
13545
13587
|
sourceIndex: i,
|
|
13546
13588
|
weighted
|
|
@@ -13561,7 +13603,7 @@ var SessionController = class _SessionController extends Loggable {
|
|
|
13561
13603
|
`Cannot start session: failed to load content from all ${this.sources.length} source(s). Check logs for details.`
|
|
13562
13604
|
);
|
|
13563
13605
|
}
|
|
13564
|
-
const mixedWeighted = this.mixer.mix(batches,
|
|
13606
|
+
const mixedWeighted = this.mixer.mix(batches, fetchLimit * this.sources.length);
|
|
13565
13607
|
const sourceIds = batches.map((b) => {
|
|
13566
13608
|
const firstCard = b.weighted[0];
|
|
13567
13609
|
return firstCard?.courseId || `source-${b.sourceIndex}`;
|
|
@@ -13578,18 +13620,18 @@ var SessionController = class _SessionController extends Loggable {
|
|
|
13578
13620
|
})
|
|
13579
13621
|
);
|
|
13580
13622
|
const sourceNames = sourceIds.map((id) => this.courseNameCache.get(id));
|
|
13581
|
-
const quotaPerSource = this.mixer instanceof QuotaRoundRobinMixer ? Math.ceil(
|
|
13623
|
+
const quotaPerSource = this.mixer instanceof QuotaRoundRobinMixer ? Math.ceil(fetchLimit * this.sources.length / batches.length) : void 0;
|
|
13582
13624
|
captureMixerRun(
|
|
13583
13625
|
this.mixer.constructor.name,
|
|
13584
13626
|
batches,
|
|
13585
13627
|
sourceIds,
|
|
13586
13628
|
sourceNames,
|
|
13587
|
-
|
|
13629
|
+
fetchLimit * this.sources.length,
|
|
13588
13630
|
quotaPerSource,
|
|
13589
13631
|
mixedWeighted
|
|
13590
13632
|
);
|
|
13591
|
-
const reviewWeighted = mixedWeighted.filter((w) => getCardOrigin(w) === "review");
|
|
13592
|
-
const newWeighted = mixedWeighted.filter((w) => getCardOrigin(w) === "new");
|
|
13633
|
+
const reviewWeighted = mixedWeighted.filter((w) => getCardOrigin(w) === "review").slice(0, this._initialReviewCap);
|
|
13634
|
+
const newWeighted = mixedWeighted.filter((w) => getCardOrigin(w) === "new").slice(0, newLimit);
|
|
13593
13635
|
logger.debug(`[reviews] got ${reviewWeighted.length} reviews from mixer`);
|
|
13594
13636
|
let report = replan ? "Replan content:\n" : "Mixed content session created with:\n";
|
|
13595
13637
|
if (!replan) {
|