@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.mjs CHANGED
@@ -424,6 +424,33 @@ var init_updateQueue = __esm({
424
424
  // Database for read operations
425
425
  writeDB;
426
426
  // Database for write operations (local-first)
427
+ /**
428
+ * Queues an update for a document and applies it with conflict resolution.
429
+ *
430
+ * @param id - Document ID to update
431
+ * @param update - Partial object or function that transforms the document
432
+ * @returns Promise resolving to the updated document
433
+ *
434
+ * @throws {PouchError} with status 404 if document doesn't exist
435
+ *
436
+ * @remarks
437
+ * **Error Handling Pattern:**
438
+ * - This method does NOT create documents if they don't exist
439
+ * - Callers are responsible for handling 404 errors and creating documents
440
+ * - This design maintains separation of concerns (UpdateQueue handles conflicts, callers handle lifecycle)
441
+ *
442
+ * @example
443
+ * ```typescript
444
+ * try {
445
+ * await updateQueue.update(docId, (doc) => ({ ...doc, field: newValue }));
446
+ * } catch (e) {
447
+ * if ((e as PouchError).status === 404) {
448
+ * // Create the document with initial values
449
+ * await db.put({ _id: docId, field: newValue, ...initialFields });
450
+ * }
451
+ * }
452
+ * ```
453
+ */
427
454
  update(id, update) {
428
455
  logger.debug(`Update requested on doc: ${id}`);
429
456
  if (this.pendingUpdates[id]) {
@@ -456,7 +483,6 @@ var init_updateQueue = __esm({
456
483
  for (let i = 0; i < MAX_RETRIES; i++) {
457
484
  try {
458
485
  const doc = await this.readDB.get(id);
459
- logger.debug(`Retrieved doc: ${id}`);
460
486
  let updatedDoc = { ...doc };
461
487
  const updatesToApply = [...this.pendingUpdates[id]];
462
488
  for (const update of updatesToApply) {
@@ -470,7 +496,6 @@ var init_updateQueue = __esm({
470
496
  }
471
497
  }
472
498
  await this.writeDB.put(updatedDoc);
473
- logger.debug(`Put doc: ${id}`);
474
499
  this.pendingUpdates[id].splice(0, updatesToApply.length);
475
500
  if (this.pendingUpdates[id].length === 0) {
476
501
  this.inprogressUpdates[id] = false;
@@ -485,6 +510,7 @@ var init_updateQueue = __esm({
485
510
  await new Promise((res) => setTimeout(res, 50 * Math.random()));
486
511
  } else if (e.name === "not_found" && i === 0) {
487
512
  logger.warn(`Update failed for ${id} - does not exist. Throwing to caller.`);
513
+ delete this.inprogressUpdates[id];
488
514
  throw e;
489
515
  } else {
490
516
  delete this.inprogressUpdates[id];
@@ -3139,12 +3165,22 @@ Currently logged-in as ${this._username}.`
3139
3165
  }
3140
3166
  /**
3141
3167
  * Logs a record of the user's interaction with the card and returns the card's
3142
- * up-to-date history
3168
+ * up-to-date history.
3169
+ *
3170
+ * **Automatic Initialization:**
3171
+ * If this is the user's first interaction with the card (CardHistory doesn't exist),
3172
+ * this method automatically creates the CardHistory document with initial values
3173
+ * (lapses: 0, streak: 0, bestInterval: 0).
3174
+ *
3175
+ * **Error Handling:**
3176
+ * - Handles 404 errors by creating initial CardHistory document
3177
+ * - Re-throws all other errors from UpdateQueue
3143
3178
  *
3144
3179
  * // [ ] #db-refactor extract to a smaller scope - eg, UserStudySession
3145
3180
  *
3146
- * @param record the recent recorded interaction between user and card
3181
+ * @param record - The recent recorded interaction between user and card
3147
3182
  * @returns The updated state of the card's CardHistory data
3183
+ * @throws Error if document creation fails or non-404 database error occurs
3148
3184
  */
3149
3185
  async putCardRecord(record) {
3150
3186
  const cardHistoryID = getCardHistoryID(record.courseID, record.cardID);
@@ -4317,6 +4353,8 @@ var init_StaticDataLayerProvider = __esm({
4317
4353
  initialized = false;
4318
4354
  courseUnpackers = /* @__PURE__ */ new Map();
4319
4355
  manifests = {};
4356
+ // Mapping from dependency name to actual courseId for backwards compatibility
4357
+ dependencyNameToCourseId = /* @__PURE__ */ new Map();
4320
4358
  constructor(config) {
4321
4359
  this.config = {
4322
4360
  localStoragePrefix: config.localStoragePrefix || "skuilder-static",
@@ -4344,10 +4382,15 @@ var init_StaticDataLayerProvider = __esm({
4344
4382
  throw new Error(`Failed to fetch final content manifest for ${courseName} at ${finalManifestUrl}`);
4345
4383
  }
4346
4384
  const finalManifest = await finalManifestResponse.json();
4347
- this.manifests[courseName] = finalManifest;
4385
+ const courseId = finalManifest.courseId || finalManifest.courseConfig?.courseID;
4386
+ if (!courseId) {
4387
+ throw new Error(`Course manifest for ${courseName} missing courseId`);
4388
+ }
4389
+ this.manifests[courseId] = finalManifest;
4348
4390
  const unpacker = new StaticDataUnpacker(finalManifest, baseUrl);
4349
- this.courseUnpackers.set(courseName, unpacker);
4350
- logger.info(`[StaticDataLayerProvider] Successfully resolved and prepared course: ${courseName}`);
4391
+ this.courseUnpackers.set(courseId, unpacker);
4392
+ this.dependencyNameToCourseId.set(courseName, courseId);
4393
+ logger.info(`[StaticDataLayerProvider] Successfully resolved and prepared course: ${courseName} (courseId: ${courseId})`);
4351
4394
  }
4352
4395
  } catch (e) {
4353
4396
  logger.error(`[StaticDataLayerProvider] Failed to resolve dependency ${courseName}:`, e);
@@ -4370,12 +4413,20 @@ var init_StaticDataLayerProvider = __esm({
4370
4413
  return BaseUser.Dummy(syncStrategy);
4371
4414
  }
4372
4415
  getCourseDB(courseId) {
4373
- const unpacker = this.courseUnpackers.get(courseId);
4416
+ let unpacker = this.courseUnpackers.get(courseId);
4417
+ let actualCourseId = courseId;
4418
+ if (!unpacker) {
4419
+ const mappedCourseId = this.dependencyNameToCourseId.get(courseId);
4420
+ if (mappedCourseId) {
4421
+ unpacker = this.courseUnpackers.get(mappedCourseId);
4422
+ actualCourseId = mappedCourseId;
4423
+ }
4424
+ }
4374
4425
  if (!unpacker) {
4375
4426
  throw new Error(`Course ${courseId} not found or failed to initialize in static data layer.`);
4376
4427
  }
4377
- const manifest = this.manifests[courseId];
4378
- return new StaticCourseDB(courseId, unpacker, this.getUserDB(), manifest);
4428
+ const manifest = this.manifests[actualCourseId];
4429
+ return new StaticCourseDB(actualCourseId, unpacker, this.getUserDB(), manifest);
4379
4430
  }
4380
4431
  getCoursesDB() {
4381
4432
  return new StaticCoursesDB(this.manifests);
@@ -4890,29 +4941,34 @@ var ResponseProcessor = class {
4890
4941
  shouldClearFeedbackShadow: true
4891
4942
  };
4892
4943
  }
4893
- const history = await cardHistory;
4894
- if (cardRecord.isCorrect) {
4895
- return this.processCorrectResponse(
4896
- cardRecord,
4897
- history,
4898
- studySessionItem,
4899
- courseRegistrationDoc,
4900
- currentCard,
4901
- courseId,
4902
- cardId
4903
- );
4904
- } else {
4905
- return this.processIncorrectResponse(
4906
- cardRecord,
4907
- history,
4908
- courseRegistrationDoc,
4909
- currentCard,
4910
- courseId,
4911
- cardId,
4912
- maxAttemptsPerView,
4913
- maxSessionViews,
4914
- sessionViews
4915
- );
4944
+ try {
4945
+ const history = await cardHistory;
4946
+ if (cardRecord.isCorrect) {
4947
+ return this.processCorrectResponse(
4948
+ cardRecord,
4949
+ history,
4950
+ studySessionItem,
4951
+ courseRegistrationDoc,
4952
+ currentCard,
4953
+ courseId,
4954
+ cardId
4955
+ );
4956
+ } else {
4957
+ return this.processIncorrectResponse(
4958
+ cardRecord,
4959
+ history,
4960
+ courseRegistrationDoc,
4961
+ currentCard,
4962
+ courseId,
4963
+ cardId,
4964
+ maxAttemptsPerView,
4965
+ maxSessionViews,
4966
+ sessionViews
4967
+ );
4968
+ }
4969
+ } catch (e) {
4970
+ logger.error("[ResponseProcessor] Failed to load card history", { e, cardId });
4971
+ throw e;
4916
4972
  }
4917
4973
  }
4918
4974
  /**
@@ -5117,6 +5173,12 @@ var CardHydrationService = class {
5117
5173
  get hydratedCount() {
5118
5174
  return this.hydratedQ.length;
5119
5175
  }
5176
+ /**
5177
+ * Get current failed card cache size.
5178
+ */
5179
+ get failedCacheSize() {
5180
+ return this.failedCardCache.size;
5181
+ }
5120
5182
  /**
5121
5183
  * Fill the hydrated queue up to BUFFER_SIZE with pre-fetched cards.
5122
5184
  */
@@ -6729,6 +6791,50 @@ var SessionController = class extends Loggable {
6729
6791
  reportString() {
6730
6792
  return `${this.reviewQ.dequeueCount} Reviews, ${this.newQ.dequeueCount} New, ${this.failedQ.dequeueCount} failed`;
6731
6793
  }
6794
+ /**
6795
+ * Returns debug information about the current session state.
6796
+ * Used by SessionControllerDebug component for runtime inspection.
6797
+ */
6798
+ getDebugInfo() {
6799
+ const extractQueueItems = (queue, limit = 10) => {
6800
+ const items = [];
6801
+ for (let i = 0; i < Math.min(queue.length, limit); i++) {
6802
+ const item = queue.peek(i);
6803
+ items.push({
6804
+ courseID: item.courseID || "unknown",
6805
+ cardID: item.cardID || "unknown",
6806
+ status: item.status || "unknown"
6807
+ });
6808
+ }
6809
+ return items;
6810
+ };
6811
+ const extractHydratedItems = () => {
6812
+ const items = [];
6813
+ return items;
6814
+ };
6815
+ return {
6816
+ reviewQueue: {
6817
+ length: this.reviewQ.length,
6818
+ dequeueCount: this.reviewQ.dequeueCount,
6819
+ items: extractQueueItems(this.reviewQ)
6820
+ },
6821
+ newQueue: {
6822
+ length: this.newQ.length,
6823
+ dequeueCount: this.newQ.dequeueCount,
6824
+ items: extractQueueItems(this.newQ)
6825
+ },
6826
+ failedQueue: {
6827
+ length: this.failedQ.length,
6828
+ dequeueCount: this.failedQ.dequeueCount,
6829
+ items: extractQueueItems(this.failedQ)
6830
+ },
6831
+ hydratedCache: {
6832
+ count: this.hydrationService.hydratedCount,
6833
+ failedCacheSize: this.hydrationService.failedCacheSize,
6834
+ items: extractHydratedItems()
6835
+ }
6836
+ };
6837
+ }
6732
6838
  async getScheduledReviews() {
6733
6839
  const reviews = await Promise.all(
6734
6840
  this.sources.map(