@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/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
- const dueReviews = reviews.filter((r) => now.isAfter(moment3.utc(r.reviewTime)));
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
- let prefix = DocTypePrefixes["CARDRECORD" /* CARDRECORD */];
7786
+ const basePrefix = DocTypePrefixes["CARDRECORD" /* CARDRECORD */];
7787
+ let filterPrefix = basePrefix;
7768
7788
  if (course_id) {
7769
- prefix += course_id;
7789
+ filterPrefix += `-${course_id}-`;
7770
7790
  }
7771
- const docs = await filterAllDocsByPrefix(this.localDB, prefix, {
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(DocTypePrefixes["CARDRECORD" /* CARDRECORD */])) {
7777
- ret.push(row.id.substr(DocTypePrefixes["CARDRECORD" /* CARDRECORD */].length));
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
- void this.srsService.scheduleReview(history, studySessionItem);
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
- const currentId = this._currentCard.item.cardID;
13259
- if (!opts.hints) opts.hints = {};
13260
- const hints = opts.hints;
13261
- const excludeCards = hints.excludeCards ?? [];
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 limit = options?.limit ?? this._defaultBatchLimit;
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(limit)).cards;
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, limit * this.sources.length);
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(limit * this.sources.length / batches.length) : void 0;
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
- limit * this.sources.length,
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) {