@vue-skuilder/db 0.1.14-2 → 0.1.15

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);
@@ -4211,11 +4247,18 @@ var init_coursesDB = __esm({
4211
4247
  "use strict";
4212
4248
  init_logger();
4213
4249
  StaticCoursesDB = class {
4214
- constructor(manifests) {
4250
+ constructor(manifests, dependencyNameToCourseId) {
4215
4251
  this.manifests = manifests;
4252
+ this.dependencyNameToCourseId = dependencyNameToCourseId;
4216
4253
  }
4217
4254
  async getCourseConfig(courseId) {
4218
- const manifest = this.manifests[courseId];
4255
+ let manifest = this.manifests[courseId];
4256
+ if (!manifest && this.dependencyNameToCourseId) {
4257
+ const mappedCourseId = this.dependencyNameToCourseId.get(courseId);
4258
+ if (mappedCourseId) {
4259
+ manifest = this.manifests[mappedCourseId];
4260
+ }
4261
+ }
4219
4262
  if (!manifest) {
4220
4263
  logger.warn(`Course manifest for ${courseId} not found`);
4221
4264
  throw new Error(`Course ${courseId} not found`);
@@ -4317,6 +4360,8 @@ var init_StaticDataLayerProvider = __esm({
4317
4360
  initialized = false;
4318
4361
  courseUnpackers = /* @__PURE__ */ new Map();
4319
4362
  manifests = {};
4363
+ // Mapping from dependency name to actual courseId for backwards compatibility
4364
+ dependencyNameToCourseId = /* @__PURE__ */ new Map();
4320
4365
  constructor(config) {
4321
4366
  this.config = {
4322
4367
  localStoragePrefix: config.localStoragePrefix || "skuilder-static",
@@ -4344,10 +4389,15 @@ var init_StaticDataLayerProvider = __esm({
4344
4389
  throw new Error(`Failed to fetch final content manifest for ${courseName} at ${finalManifestUrl}`);
4345
4390
  }
4346
4391
  const finalManifest = await finalManifestResponse.json();
4347
- this.manifests[courseName] = finalManifest;
4392
+ const courseId = finalManifest.courseId || finalManifest.courseConfig?.courseID;
4393
+ if (!courseId) {
4394
+ throw new Error(`Course manifest for ${courseName} missing courseId`);
4395
+ }
4396
+ this.manifests[courseId] = finalManifest;
4348
4397
  const unpacker = new StaticDataUnpacker(finalManifest, baseUrl);
4349
- this.courseUnpackers.set(courseName, unpacker);
4350
- logger.info(`[StaticDataLayerProvider] Successfully resolved and prepared course: ${courseName}`);
4398
+ this.courseUnpackers.set(courseId, unpacker);
4399
+ this.dependencyNameToCourseId.set(courseName, courseId);
4400
+ logger.info(`[StaticDataLayerProvider] Successfully resolved and prepared course: ${courseName} (courseId: ${courseId})`);
4351
4401
  }
4352
4402
  } catch (e) {
4353
4403
  logger.error(`[StaticDataLayerProvider] Failed to resolve dependency ${courseName}:`, e);
@@ -4370,15 +4420,23 @@ var init_StaticDataLayerProvider = __esm({
4370
4420
  return BaseUser.Dummy(syncStrategy);
4371
4421
  }
4372
4422
  getCourseDB(courseId) {
4373
- const unpacker = this.courseUnpackers.get(courseId);
4423
+ let unpacker = this.courseUnpackers.get(courseId);
4424
+ let actualCourseId = courseId;
4425
+ if (!unpacker) {
4426
+ const mappedCourseId = this.dependencyNameToCourseId.get(courseId);
4427
+ if (mappedCourseId) {
4428
+ unpacker = this.courseUnpackers.get(mappedCourseId);
4429
+ actualCourseId = mappedCourseId;
4430
+ }
4431
+ }
4374
4432
  if (!unpacker) {
4375
4433
  throw new Error(`Course ${courseId} not found or failed to initialize in static data layer.`);
4376
4434
  }
4377
- const manifest = this.manifests[courseId];
4378
- return new StaticCourseDB(courseId, unpacker, this.getUserDB(), manifest);
4435
+ const manifest = this.manifests[actualCourseId];
4436
+ return new StaticCourseDB(actualCourseId, unpacker, this.getUserDB(), manifest);
4379
4437
  }
4380
4438
  getCoursesDB() {
4381
- return new StaticCoursesDB(this.manifests);
4439
+ return new StaticCoursesDB(this.manifests, this.dependencyNameToCourseId);
4382
4440
  }
4383
4441
  async getClassroomDB(_classId, _type) {
4384
4442
  throw new Error("Classrooms not supported in static mode");
@@ -4890,29 +4948,34 @@ var ResponseProcessor = class {
4890
4948
  shouldClearFeedbackShadow: true
4891
4949
  };
4892
4950
  }
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
- );
4951
+ try {
4952
+ const history = await cardHistory;
4953
+ if (cardRecord.isCorrect) {
4954
+ return this.processCorrectResponse(
4955
+ cardRecord,
4956
+ history,
4957
+ studySessionItem,
4958
+ courseRegistrationDoc,
4959
+ currentCard,
4960
+ courseId,
4961
+ cardId
4962
+ );
4963
+ } else {
4964
+ return this.processIncorrectResponse(
4965
+ cardRecord,
4966
+ history,
4967
+ courseRegistrationDoc,
4968
+ currentCard,
4969
+ courseId,
4970
+ cardId,
4971
+ maxAttemptsPerView,
4972
+ maxSessionViews,
4973
+ sessionViews
4974
+ );
4975
+ }
4976
+ } catch (e) {
4977
+ logger.error("[ResponseProcessor] Failed to load card history", { e, cardId });
4978
+ throw e;
4916
4979
  }
4917
4980
  }
4918
4981
  /**
@@ -5117,6 +5180,12 @@ var CardHydrationService = class {
5117
5180
  get hydratedCount() {
5118
5181
  return this.hydratedQ.length;
5119
5182
  }
5183
+ /**
5184
+ * Get current failed card cache size.
5185
+ */
5186
+ get failedCacheSize() {
5187
+ return this.failedCardCache.size;
5188
+ }
5120
5189
  /**
5121
5190
  * Fill the hydrated queue up to BUFFER_SIZE with pre-fetched cards.
5122
5191
  */
@@ -6729,6 +6798,50 @@ var SessionController = class extends Loggable {
6729
6798
  reportString() {
6730
6799
  return `${this.reviewQ.dequeueCount} Reviews, ${this.newQ.dequeueCount} New, ${this.failedQ.dequeueCount} failed`;
6731
6800
  }
6801
+ /**
6802
+ * Returns debug information about the current session state.
6803
+ * Used by SessionControllerDebug component for runtime inspection.
6804
+ */
6805
+ getDebugInfo() {
6806
+ const extractQueueItems = (queue, limit = 10) => {
6807
+ const items = [];
6808
+ for (let i = 0; i < Math.min(queue.length, limit); i++) {
6809
+ const item = queue.peek(i);
6810
+ items.push({
6811
+ courseID: item.courseID || "unknown",
6812
+ cardID: item.cardID || "unknown",
6813
+ status: item.status || "unknown"
6814
+ });
6815
+ }
6816
+ return items;
6817
+ };
6818
+ const extractHydratedItems = () => {
6819
+ const items = [];
6820
+ return items;
6821
+ };
6822
+ return {
6823
+ reviewQueue: {
6824
+ length: this.reviewQ.length,
6825
+ dequeueCount: this.reviewQ.dequeueCount,
6826
+ items: extractQueueItems(this.reviewQ)
6827
+ },
6828
+ newQueue: {
6829
+ length: this.newQ.length,
6830
+ dequeueCount: this.newQ.dequeueCount,
6831
+ items: extractQueueItems(this.newQ)
6832
+ },
6833
+ failedQueue: {
6834
+ length: this.failedQ.length,
6835
+ dequeueCount: this.failedQ.dequeueCount,
6836
+ items: extractQueueItems(this.failedQ)
6837
+ },
6838
+ hydratedCache: {
6839
+ count: this.hydrationService.hydratedCount,
6840
+ failedCacheSize: this.hydrationService.failedCacheSize,
6841
+ items: extractHydratedItems()
6842
+ }
6843
+ };
6844
+ }
6732
6845
  async getScheduledReviews() {
6733
6846
  const reviews = await Promise.all(
6734
6847
  this.sources.map(