@vue-skuilder/db 0.1.14-2 → 0.1.14

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.mts CHANGED
@@ -331,6 +331,44 @@ declare class SessionController<TView = unknown> extends Loggable {
331
331
  get failedCount(): number;
332
332
  toString(): string;
333
333
  reportString(): string;
334
+ /**
335
+ * Returns debug information about the current session state.
336
+ * Used by SessionControllerDebug component for runtime inspection.
337
+ */
338
+ getDebugInfo(): {
339
+ reviewQueue: {
340
+ length: number;
341
+ dequeueCount: number;
342
+ items: {
343
+ courseID: any;
344
+ cardID: any;
345
+ status: any;
346
+ }[];
347
+ };
348
+ newQueue: {
349
+ length: number;
350
+ dequeueCount: number;
351
+ items: {
352
+ courseID: any;
353
+ cardID: any;
354
+ status: any;
355
+ }[];
356
+ };
357
+ failedQueue: {
358
+ length: number;
359
+ dequeueCount: number;
360
+ items: {
361
+ courseID: any;
362
+ cardID: any;
363
+ status: any;
364
+ }[];
365
+ };
366
+ hydratedCache: {
367
+ count: number;
368
+ failedCacheSize: number;
369
+ items: any[];
370
+ };
371
+ };
334
372
  private getScheduledReviews;
335
373
  private getNewCards;
336
374
  private _selectNextItemToHydrate;
package/dist/index.d.ts CHANGED
@@ -331,6 +331,44 @@ declare class SessionController<TView = unknown> extends Loggable {
331
331
  get failedCount(): number;
332
332
  toString(): string;
333
333
  reportString(): string;
334
+ /**
335
+ * Returns debug information about the current session state.
336
+ * Used by SessionControllerDebug component for runtime inspection.
337
+ */
338
+ getDebugInfo(): {
339
+ reviewQueue: {
340
+ length: number;
341
+ dequeueCount: number;
342
+ items: {
343
+ courseID: any;
344
+ cardID: any;
345
+ status: any;
346
+ }[];
347
+ };
348
+ newQueue: {
349
+ length: number;
350
+ dequeueCount: number;
351
+ items: {
352
+ courseID: any;
353
+ cardID: any;
354
+ status: any;
355
+ }[];
356
+ };
357
+ failedQueue: {
358
+ length: number;
359
+ dequeueCount: number;
360
+ items: {
361
+ courseID: any;
362
+ cardID: any;
363
+ status: any;
364
+ }[];
365
+ };
366
+ hydratedCache: {
367
+ count: number;
368
+ failedCacheSize: number;
369
+ items: any[];
370
+ };
371
+ };
334
372
  private getScheduledReviews;
335
373
  private getNewCards;
336
374
  private _selectNextItemToHydrate;
package/dist/index.js CHANGED
@@ -447,6 +447,33 @@ var init_updateQueue = __esm({
447
447
  // Database for read operations
448
448
  writeDB;
449
449
  // Database for write operations (local-first)
450
+ /**
451
+ * Queues an update for a document and applies it with conflict resolution.
452
+ *
453
+ * @param id - Document ID to update
454
+ * @param update - Partial object or function that transforms the document
455
+ * @returns Promise resolving to the updated document
456
+ *
457
+ * @throws {PouchError} with status 404 if document doesn't exist
458
+ *
459
+ * @remarks
460
+ * **Error Handling Pattern:**
461
+ * - This method does NOT create documents if they don't exist
462
+ * - Callers are responsible for handling 404 errors and creating documents
463
+ * - This design maintains separation of concerns (UpdateQueue handles conflicts, callers handle lifecycle)
464
+ *
465
+ * @example
466
+ * ```typescript
467
+ * try {
468
+ * await updateQueue.update(docId, (doc) => ({ ...doc, field: newValue }));
469
+ * } catch (e) {
470
+ * if ((e as PouchError).status === 404) {
471
+ * // Create the document with initial values
472
+ * await db.put({ _id: docId, field: newValue, ...initialFields });
473
+ * }
474
+ * }
475
+ * ```
476
+ */
450
477
  update(id, update) {
451
478
  logger.debug(`Update requested on doc: ${id}`);
452
479
  if (this.pendingUpdates[id]) {
@@ -479,7 +506,6 @@ var init_updateQueue = __esm({
479
506
  for (let i = 0; i < MAX_RETRIES; i++) {
480
507
  try {
481
508
  const doc = await this.readDB.get(id);
482
- logger.debug(`Retrieved doc: ${id}`);
483
509
  let updatedDoc = { ...doc };
484
510
  const updatesToApply = [...this.pendingUpdates[id]];
485
511
  for (const update of updatesToApply) {
@@ -493,7 +519,6 @@ var init_updateQueue = __esm({
493
519
  }
494
520
  }
495
521
  await this.writeDB.put(updatedDoc);
496
- logger.debug(`Put doc: ${id}`);
497
522
  this.pendingUpdates[id].splice(0, updatesToApply.length);
498
523
  if (this.pendingUpdates[id].length === 0) {
499
524
  this.inprogressUpdates[id] = false;
@@ -508,6 +533,7 @@ var init_updateQueue = __esm({
508
533
  await new Promise((res) => setTimeout(res, 50 * Math.random()));
509
534
  } else if (e.name === "not_found" && i === 0) {
510
535
  logger.warn(`Update failed for ${id} - does not exist. Throwing to caller.`);
536
+ delete this.inprogressUpdates[id];
511
537
  throw e;
512
538
  } else {
513
539
  delete this.inprogressUpdates[id];
@@ -3158,12 +3184,22 @@ Currently logged-in as ${this._username}.`
3158
3184
  }
3159
3185
  /**
3160
3186
  * Logs a record of the user's interaction with the card and returns the card's
3161
- * up-to-date history
3187
+ * up-to-date history.
3188
+ *
3189
+ * **Automatic Initialization:**
3190
+ * If this is the user's first interaction with the card (CardHistory doesn't exist),
3191
+ * this method automatically creates the CardHistory document with initial values
3192
+ * (lapses: 0, streak: 0, bestInterval: 0).
3193
+ *
3194
+ * **Error Handling:**
3195
+ * - Handles 404 errors by creating initial CardHistory document
3196
+ * - Re-throws all other errors from UpdateQueue
3162
3197
  *
3163
3198
  * // [ ] #db-refactor extract to a smaller scope - eg, UserStudySession
3164
3199
  *
3165
- * @param record the recent recorded interaction between user and card
3200
+ * @param record - The recent recorded interaction between user and card
3166
3201
  * @returns The updated state of the card's CardHistory data
3202
+ * @throws Error if document creation fails or non-404 database error occurs
3167
3203
  */
3168
3204
  async putCardRecord(record) {
3169
3205
  const cardHistoryID = getCardHistoryID(record.courseID, record.cardID);
@@ -4336,6 +4372,8 @@ var init_StaticDataLayerProvider = __esm({
4336
4372
  initialized = false;
4337
4373
  courseUnpackers = /* @__PURE__ */ new Map();
4338
4374
  manifests = {};
4375
+ // Mapping from dependency name to actual courseId for backwards compatibility
4376
+ dependencyNameToCourseId = /* @__PURE__ */ new Map();
4339
4377
  constructor(config) {
4340
4378
  this.config = {
4341
4379
  localStoragePrefix: config.localStoragePrefix || "skuilder-static",
@@ -4363,10 +4401,15 @@ var init_StaticDataLayerProvider = __esm({
4363
4401
  throw new Error(`Failed to fetch final content manifest for ${courseName} at ${finalManifestUrl}`);
4364
4402
  }
4365
4403
  const finalManifest = await finalManifestResponse.json();
4366
- this.manifests[courseName] = finalManifest;
4404
+ const courseId = finalManifest.courseId || finalManifest.courseConfig?.courseID;
4405
+ if (!courseId) {
4406
+ throw new Error(`Course manifest for ${courseName} missing courseId`);
4407
+ }
4408
+ this.manifests[courseId] = finalManifest;
4367
4409
  const unpacker = new StaticDataUnpacker(finalManifest, baseUrl);
4368
- this.courseUnpackers.set(courseName, unpacker);
4369
- logger.info(`[StaticDataLayerProvider] Successfully resolved and prepared course: ${courseName}`);
4410
+ this.courseUnpackers.set(courseId, unpacker);
4411
+ this.dependencyNameToCourseId.set(courseName, courseId);
4412
+ logger.info(`[StaticDataLayerProvider] Successfully resolved and prepared course: ${courseName} (courseId: ${courseId})`);
4370
4413
  }
4371
4414
  } catch (e) {
4372
4415
  logger.error(`[StaticDataLayerProvider] Failed to resolve dependency ${courseName}:`, e);
@@ -4389,12 +4432,20 @@ var init_StaticDataLayerProvider = __esm({
4389
4432
  return BaseUser.Dummy(syncStrategy);
4390
4433
  }
4391
4434
  getCourseDB(courseId) {
4392
- const unpacker = this.courseUnpackers.get(courseId);
4435
+ let unpacker = this.courseUnpackers.get(courseId);
4436
+ let actualCourseId = courseId;
4437
+ if (!unpacker) {
4438
+ const mappedCourseId = this.dependencyNameToCourseId.get(courseId);
4439
+ if (mappedCourseId) {
4440
+ unpacker = this.courseUnpackers.get(mappedCourseId);
4441
+ actualCourseId = mappedCourseId;
4442
+ }
4443
+ }
4393
4444
  if (!unpacker) {
4394
4445
  throw new Error(`Course ${courseId} not found or failed to initialize in static data layer.`);
4395
4446
  }
4396
- const manifest = this.manifests[courseId];
4397
- return new StaticCourseDB(courseId, unpacker, this.getUserDB(), manifest);
4447
+ const manifest = this.manifests[actualCourseId];
4448
+ return new StaticCourseDB(actualCourseId, unpacker, this.getUserDB(), manifest);
4398
4449
  }
4399
4450
  getCoursesDB() {
4400
4451
  return new StaticCoursesDB(this.manifests);
@@ -4952,29 +5003,34 @@ var ResponseProcessor = class {
4952
5003
  shouldClearFeedbackShadow: true
4953
5004
  };
4954
5005
  }
4955
- const history = await cardHistory;
4956
- if (cardRecord.isCorrect) {
4957
- return this.processCorrectResponse(
4958
- cardRecord,
4959
- history,
4960
- studySessionItem,
4961
- courseRegistrationDoc,
4962
- currentCard,
4963
- courseId,
4964
- cardId
4965
- );
4966
- } else {
4967
- return this.processIncorrectResponse(
4968
- cardRecord,
4969
- history,
4970
- courseRegistrationDoc,
4971
- currentCard,
4972
- courseId,
4973
- cardId,
4974
- maxAttemptsPerView,
4975
- maxSessionViews,
4976
- sessionViews
4977
- );
5006
+ try {
5007
+ const history = await cardHistory;
5008
+ if (cardRecord.isCorrect) {
5009
+ return this.processCorrectResponse(
5010
+ cardRecord,
5011
+ history,
5012
+ studySessionItem,
5013
+ courseRegistrationDoc,
5014
+ currentCard,
5015
+ courseId,
5016
+ cardId
5017
+ );
5018
+ } else {
5019
+ return this.processIncorrectResponse(
5020
+ cardRecord,
5021
+ history,
5022
+ courseRegistrationDoc,
5023
+ currentCard,
5024
+ courseId,
5025
+ cardId,
5026
+ maxAttemptsPerView,
5027
+ maxSessionViews,
5028
+ sessionViews
5029
+ );
5030
+ }
5031
+ } catch (e) {
5032
+ logger.error("[ResponseProcessor] Failed to load card history", { e, cardId });
5033
+ throw e;
4978
5034
  }
4979
5035
  }
4980
5036
  /**
@@ -5175,6 +5231,12 @@ var CardHydrationService = class {
5175
5231
  get hydratedCount() {
5176
5232
  return this.hydratedQ.length;
5177
5233
  }
5234
+ /**
5235
+ * Get current failed card cache size.
5236
+ */
5237
+ get failedCacheSize() {
5238
+ return this.failedCardCache.size;
5239
+ }
5178
5240
  /**
5179
5241
  * Fill the hydrated queue up to BUFFER_SIZE with pre-fetched cards.
5180
5242
  */
@@ -6787,6 +6849,50 @@ var SessionController = class extends Loggable {
6787
6849
  reportString() {
6788
6850
  return `${this.reviewQ.dequeueCount} Reviews, ${this.newQ.dequeueCount} New, ${this.failedQ.dequeueCount} failed`;
6789
6851
  }
6852
+ /**
6853
+ * Returns debug information about the current session state.
6854
+ * Used by SessionControllerDebug component for runtime inspection.
6855
+ */
6856
+ getDebugInfo() {
6857
+ const extractQueueItems = (queue, limit = 10) => {
6858
+ const items = [];
6859
+ for (let i = 0; i < Math.min(queue.length, limit); i++) {
6860
+ const item = queue.peek(i);
6861
+ items.push({
6862
+ courseID: item.courseID || "unknown",
6863
+ cardID: item.cardID || "unknown",
6864
+ status: item.status || "unknown"
6865
+ });
6866
+ }
6867
+ return items;
6868
+ };
6869
+ const extractHydratedItems = () => {
6870
+ const items = [];
6871
+ return items;
6872
+ };
6873
+ return {
6874
+ reviewQueue: {
6875
+ length: this.reviewQ.length,
6876
+ dequeueCount: this.reviewQ.dequeueCount,
6877
+ items: extractQueueItems(this.reviewQ)
6878
+ },
6879
+ newQueue: {
6880
+ length: this.newQ.length,
6881
+ dequeueCount: this.newQ.dequeueCount,
6882
+ items: extractQueueItems(this.newQ)
6883
+ },
6884
+ failedQueue: {
6885
+ length: this.failedQ.length,
6886
+ dequeueCount: this.failedQ.dequeueCount,
6887
+ items: extractQueueItems(this.failedQ)
6888
+ },
6889
+ hydratedCache: {
6890
+ count: this.hydrationService.hydratedCount,
6891
+ failedCacheSize: this.hydrationService.failedCacheSize,
6892
+ items: extractHydratedItems()
6893
+ }
6894
+ };
6895
+ }
6790
6896
  async getScheduledReviews() {
6791
6897
  const reviews = await Promise.all(
6792
6898
  this.sources.map(