@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/core/index.js +40 -4
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.mjs +40 -4
- package/dist/core/index.mjs.map +1 -1
- package/dist/impl/couch/index.js +40 -4
- package/dist/impl/couch/index.js.map +1 -1
- package/dist/impl/couch/index.mjs +40 -4
- package/dist/impl/couch/index.mjs.map +1 -1
- package/dist/impl/static/index.d.mts +1 -0
- package/dist/impl/static/index.d.ts +1 -0
- package/dist/impl/static/index.js +61 -10
- package/dist/impl/static/index.js.map +1 -1
- package/dist/impl/static/index.mjs +61 -10
- package/dist/impl/static/index.mjs.map +1 -1
- package/dist/index.d.mts +38 -0
- package/dist/index.d.ts +38 -0
- package/dist/index.js +139 -33
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +139 -33
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
- package/src/impl/common/BaseUserDB.ts +12 -2
- package/src/impl/couch/updateQueue.ts +28 -2
- package/src/impl/static/StaticDataLayerProvider.ts +31 -7
- package/src/study/SessionController.ts +50 -0
- package/src/study/services/CardHydrationService.ts +7 -0
- package/src/study/services/ResponseProcessor.ts +64 -25
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
|
|
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
|
-
|
|
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(
|
|
4350
|
-
|
|
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
|
-
|
|
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[
|
|
4378
|
-
return new StaticCourseDB(
|
|
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
|
-
|
|
4894
|
-
|
|
4895
|
-
|
|
4896
|
-
|
|
4897
|
-
|
|
4898
|
-
|
|
4899
|
-
|
|
4900
|
-
|
|
4901
|
-
|
|
4902
|
-
|
|
4903
|
-
|
|
4904
|
-
|
|
4905
|
-
|
|
4906
|
-
|
|
4907
|
-
|
|
4908
|
-
|
|
4909
|
-
|
|
4910
|
-
|
|
4911
|
-
|
|
4912
|
-
|
|
4913
|
-
|
|
4914
|
-
|
|
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(
|