@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.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,16 @@ 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
|
-
if (!excludeCards.includes(currentId)) {
|
|
13365
|
-
excludeCards.push(currentId);
|
|
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);
|
|
13368
13405
|
}
|
|
13406
|
+
hints.excludeCards = [...excludeSet];
|
|
13369
13407
|
if (opts.hints) {
|
|
13370
13408
|
const hintsWithLabel = opts.label ? { ...opts.hints, _label: opts.label } : opts.hints;
|
|
13371
13409
|
for (const source of this.sources) {
|
|
@@ -13535,12 +13573,13 @@ var SessionController = class _SessionController extends Loggable {
|
|
|
13535
13573
|
async getWeightedContent(options) {
|
|
13536
13574
|
const replan = options?.replan ?? false;
|
|
13537
13575
|
const additive = options?.additive ?? false;
|
|
13538
|
-
const
|
|
13576
|
+
const newLimit = options?.limit ?? this._defaultBatchLimit;
|
|
13577
|
+
const fetchLimit = replan ? newLimit : newLimit + this._initialReviewCap;
|
|
13539
13578
|
const batches = [];
|
|
13540
13579
|
for (let i = 0; i < this.sources.length; i++) {
|
|
13541
13580
|
const source = this.sources[i];
|
|
13542
13581
|
try {
|
|
13543
|
-
const weighted = (await source.getWeightedCards(
|
|
13582
|
+
const weighted = (await source.getWeightedCards(fetchLimit)).cards;
|
|
13544
13583
|
batches.push({
|
|
13545
13584
|
sourceIndex: i,
|
|
13546
13585
|
weighted
|
|
@@ -13561,7 +13600,7 @@ var SessionController = class _SessionController extends Loggable {
|
|
|
13561
13600
|
`Cannot start session: failed to load content from all ${this.sources.length} source(s). Check logs for details.`
|
|
13562
13601
|
);
|
|
13563
13602
|
}
|
|
13564
|
-
const mixedWeighted = this.mixer.mix(batches,
|
|
13603
|
+
const mixedWeighted = this.mixer.mix(batches, fetchLimit * this.sources.length);
|
|
13565
13604
|
const sourceIds = batches.map((b) => {
|
|
13566
13605
|
const firstCard = b.weighted[0];
|
|
13567
13606
|
return firstCard?.courseId || `source-${b.sourceIndex}`;
|
|
@@ -13578,18 +13617,18 @@ var SessionController = class _SessionController extends Loggable {
|
|
|
13578
13617
|
})
|
|
13579
13618
|
);
|
|
13580
13619
|
const sourceNames = sourceIds.map((id) => this.courseNameCache.get(id));
|
|
13581
|
-
const quotaPerSource = this.mixer instanceof QuotaRoundRobinMixer ? Math.ceil(
|
|
13620
|
+
const quotaPerSource = this.mixer instanceof QuotaRoundRobinMixer ? Math.ceil(fetchLimit * this.sources.length / batches.length) : void 0;
|
|
13582
13621
|
captureMixerRun(
|
|
13583
13622
|
this.mixer.constructor.name,
|
|
13584
13623
|
batches,
|
|
13585
13624
|
sourceIds,
|
|
13586
13625
|
sourceNames,
|
|
13587
|
-
|
|
13626
|
+
fetchLimit * this.sources.length,
|
|
13588
13627
|
quotaPerSource,
|
|
13589
13628
|
mixedWeighted
|
|
13590
13629
|
);
|
|
13591
|
-
const reviewWeighted = mixedWeighted.filter((w) => getCardOrigin(w) === "review");
|
|
13592
|
-
const newWeighted = mixedWeighted.filter((w) => getCardOrigin(w) === "new");
|
|
13630
|
+
const reviewWeighted = mixedWeighted.filter((w) => getCardOrigin(w) === "review").slice(0, this._initialReviewCap);
|
|
13631
|
+
const newWeighted = mixedWeighted.filter((w) => getCardOrigin(w) === "new").slice(0, newLimit);
|
|
13593
13632
|
logger.debug(`[reviews] got ${reviewWeighted.length} reviews from mixer`);
|
|
13594
13633
|
let report = replan ? "Replan content:\n" : "Mixed content session created with:\n";
|
|
13595
13634
|
if (!replan) {
|