@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/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 +3 -1
- package/dist/impl/static/index.d.ts +3 -1
- package/dist/impl/static/index.js +71 -13
- package/dist/impl/static/index.js.map +1 -1
- package/dist/impl/static/index.mjs +71 -13
- 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 +149 -36
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +149 -36
- 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 +32 -8
- package/src/impl/static/coursesDB.ts +15 -2
- 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);
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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(
|
|
4350
|
-
|
|
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
|
-
|
|
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[
|
|
4378
|
-
return new StaticCourseDB(
|
|
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
|
-
|
|
4894
|
-
|
|
4895
|
-
|
|
4896
|
-
|
|
4897
|
-
|
|
4898
|
-
|
|
4899
|
-
|
|
4900
|
-
|
|
4901
|
-
|
|
4902
|
-
|
|
4903
|
-
|
|
4904
|
-
|
|
4905
|
-
|
|
4906
|
-
|
|
4907
|
-
|
|
4908
|
-
|
|
4909
|
-
|
|
4910
|
-
|
|
4911
|
-
|
|
4912
|
-
|
|
4913
|
-
|
|
4914
|
-
|
|
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(
|