@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.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
- const dueReviews = reviews.filter((r) => now.isAfter(import_moment3.default.utc(r.reviewTime)));
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
- let prefix = DocTypePrefixes["CARDRECORD" /* CARDRECORD */];
7805
+ const basePrefix = DocTypePrefixes["CARDRECORD" /* CARDRECORD */];
7806
+ let filterPrefix = basePrefix;
7787
7807
  if (course_id) {
7788
- prefix += course_id;
7808
+ filterPrefix += `-${course_id}-`;
7789
7809
  }
7790
- const docs = await filterAllDocsByPrefix(this.localDB, prefix, {
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(DocTypePrefixes["CARDRECORD" /* CARDRECORD */])) {
7796
- ret.push(row.id.substr(DocTypePrefixes["CARDRECORD" /* CARDRECORD */].length));
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
- void this.srsService.scheduleReview(history, studySessionItem);
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
- const currentId = this._currentCard.item.cardID;
13361
- if (!opts.hints) opts.hints = {};
13362
- const hints = opts.hints;
13363
- const excludeCards = hints.excludeCards ?? [];
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 limit = options?.limit ?? this._defaultBatchLimit;
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(limit)).cards;
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, limit * this.sources.length);
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(limit * this.sources.length / batches.length) : void 0;
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
- limit * this.sources.length,
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) {