@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.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
|
|
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);
|
|
@@ -4230,11 +4266,18 @@ var init_coursesDB = __esm({
|
|
|
4230
4266
|
"use strict";
|
|
4231
4267
|
init_logger();
|
|
4232
4268
|
StaticCoursesDB = class {
|
|
4233
|
-
constructor(manifests) {
|
|
4269
|
+
constructor(manifests, dependencyNameToCourseId) {
|
|
4234
4270
|
this.manifests = manifests;
|
|
4271
|
+
this.dependencyNameToCourseId = dependencyNameToCourseId;
|
|
4235
4272
|
}
|
|
4236
4273
|
async getCourseConfig(courseId) {
|
|
4237
|
-
|
|
4274
|
+
let manifest = this.manifests[courseId];
|
|
4275
|
+
if (!manifest && this.dependencyNameToCourseId) {
|
|
4276
|
+
const mappedCourseId = this.dependencyNameToCourseId.get(courseId);
|
|
4277
|
+
if (mappedCourseId) {
|
|
4278
|
+
manifest = this.manifests[mappedCourseId];
|
|
4279
|
+
}
|
|
4280
|
+
}
|
|
4238
4281
|
if (!manifest) {
|
|
4239
4282
|
logger.warn(`Course manifest for ${courseId} not found`);
|
|
4240
4283
|
throw new Error(`Course ${courseId} not found`);
|
|
@@ -4336,6 +4379,8 @@ var init_StaticDataLayerProvider = __esm({
|
|
|
4336
4379
|
initialized = false;
|
|
4337
4380
|
courseUnpackers = /* @__PURE__ */ new Map();
|
|
4338
4381
|
manifests = {};
|
|
4382
|
+
// Mapping from dependency name to actual courseId for backwards compatibility
|
|
4383
|
+
dependencyNameToCourseId = /* @__PURE__ */ new Map();
|
|
4339
4384
|
constructor(config) {
|
|
4340
4385
|
this.config = {
|
|
4341
4386
|
localStoragePrefix: config.localStoragePrefix || "skuilder-static",
|
|
@@ -4363,10 +4408,15 @@ var init_StaticDataLayerProvider = __esm({
|
|
|
4363
4408
|
throw new Error(`Failed to fetch final content manifest for ${courseName} at ${finalManifestUrl}`);
|
|
4364
4409
|
}
|
|
4365
4410
|
const finalManifest = await finalManifestResponse.json();
|
|
4366
|
-
|
|
4411
|
+
const courseId = finalManifest.courseId || finalManifest.courseConfig?.courseID;
|
|
4412
|
+
if (!courseId) {
|
|
4413
|
+
throw new Error(`Course manifest for ${courseName} missing courseId`);
|
|
4414
|
+
}
|
|
4415
|
+
this.manifests[courseId] = finalManifest;
|
|
4367
4416
|
const unpacker = new StaticDataUnpacker(finalManifest, baseUrl);
|
|
4368
|
-
this.courseUnpackers.set(
|
|
4369
|
-
|
|
4417
|
+
this.courseUnpackers.set(courseId, unpacker);
|
|
4418
|
+
this.dependencyNameToCourseId.set(courseName, courseId);
|
|
4419
|
+
logger.info(`[StaticDataLayerProvider] Successfully resolved and prepared course: ${courseName} (courseId: ${courseId})`);
|
|
4370
4420
|
}
|
|
4371
4421
|
} catch (e) {
|
|
4372
4422
|
logger.error(`[StaticDataLayerProvider] Failed to resolve dependency ${courseName}:`, e);
|
|
@@ -4389,15 +4439,23 @@ var init_StaticDataLayerProvider = __esm({
|
|
|
4389
4439
|
return BaseUser.Dummy(syncStrategy);
|
|
4390
4440
|
}
|
|
4391
4441
|
getCourseDB(courseId) {
|
|
4392
|
-
|
|
4442
|
+
let unpacker = this.courseUnpackers.get(courseId);
|
|
4443
|
+
let actualCourseId = courseId;
|
|
4444
|
+
if (!unpacker) {
|
|
4445
|
+
const mappedCourseId = this.dependencyNameToCourseId.get(courseId);
|
|
4446
|
+
if (mappedCourseId) {
|
|
4447
|
+
unpacker = this.courseUnpackers.get(mappedCourseId);
|
|
4448
|
+
actualCourseId = mappedCourseId;
|
|
4449
|
+
}
|
|
4450
|
+
}
|
|
4393
4451
|
if (!unpacker) {
|
|
4394
4452
|
throw new Error(`Course ${courseId} not found or failed to initialize in static data layer.`);
|
|
4395
4453
|
}
|
|
4396
|
-
const manifest = this.manifests[
|
|
4397
|
-
return new StaticCourseDB(
|
|
4454
|
+
const manifest = this.manifests[actualCourseId];
|
|
4455
|
+
return new StaticCourseDB(actualCourseId, unpacker, this.getUserDB(), manifest);
|
|
4398
4456
|
}
|
|
4399
4457
|
getCoursesDB() {
|
|
4400
|
-
return new StaticCoursesDB(this.manifests);
|
|
4458
|
+
return new StaticCoursesDB(this.manifests, this.dependencyNameToCourseId);
|
|
4401
4459
|
}
|
|
4402
4460
|
async getClassroomDB(_classId, _type) {
|
|
4403
4461
|
throw new Error("Classrooms not supported in static mode");
|
|
@@ -4952,29 +5010,34 @@ var ResponseProcessor = class {
|
|
|
4952
5010
|
shouldClearFeedbackShadow: true
|
|
4953
5011
|
};
|
|
4954
5012
|
}
|
|
4955
|
-
|
|
4956
|
-
|
|
4957
|
-
|
|
4958
|
-
|
|
4959
|
-
|
|
4960
|
-
|
|
4961
|
-
|
|
4962
|
-
|
|
4963
|
-
|
|
4964
|
-
|
|
4965
|
-
|
|
4966
|
-
|
|
4967
|
-
|
|
4968
|
-
|
|
4969
|
-
|
|
4970
|
-
|
|
4971
|
-
|
|
4972
|
-
|
|
4973
|
-
|
|
4974
|
-
|
|
4975
|
-
|
|
4976
|
-
|
|
4977
|
-
|
|
5013
|
+
try {
|
|
5014
|
+
const history = await cardHistory;
|
|
5015
|
+
if (cardRecord.isCorrect) {
|
|
5016
|
+
return this.processCorrectResponse(
|
|
5017
|
+
cardRecord,
|
|
5018
|
+
history,
|
|
5019
|
+
studySessionItem,
|
|
5020
|
+
courseRegistrationDoc,
|
|
5021
|
+
currentCard,
|
|
5022
|
+
courseId,
|
|
5023
|
+
cardId
|
|
5024
|
+
);
|
|
5025
|
+
} else {
|
|
5026
|
+
return this.processIncorrectResponse(
|
|
5027
|
+
cardRecord,
|
|
5028
|
+
history,
|
|
5029
|
+
courseRegistrationDoc,
|
|
5030
|
+
currentCard,
|
|
5031
|
+
courseId,
|
|
5032
|
+
cardId,
|
|
5033
|
+
maxAttemptsPerView,
|
|
5034
|
+
maxSessionViews,
|
|
5035
|
+
sessionViews
|
|
5036
|
+
);
|
|
5037
|
+
}
|
|
5038
|
+
} catch (e) {
|
|
5039
|
+
logger.error("[ResponseProcessor] Failed to load card history", { e, cardId });
|
|
5040
|
+
throw e;
|
|
4978
5041
|
}
|
|
4979
5042
|
}
|
|
4980
5043
|
/**
|
|
@@ -5175,6 +5238,12 @@ var CardHydrationService = class {
|
|
|
5175
5238
|
get hydratedCount() {
|
|
5176
5239
|
return this.hydratedQ.length;
|
|
5177
5240
|
}
|
|
5241
|
+
/**
|
|
5242
|
+
* Get current failed card cache size.
|
|
5243
|
+
*/
|
|
5244
|
+
get failedCacheSize() {
|
|
5245
|
+
return this.failedCardCache.size;
|
|
5246
|
+
}
|
|
5178
5247
|
/**
|
|
5179
5248
|
* Fill the hydrated queue up to BUFFER_SIZE with pre-fetched cards.
|
|
5180
5249
|
*/
|
|
@@ -6787,6 +6856,50 @@ var SessionController = class extends Loggable {
|
|
|
6787
6856
|
reportString() {
|
|
6788
6857
|
return `${this.reviewQ.dequeueCount} Reviews, ${this.newQ.dequeueCount} New, ${this.failedQ.dequeueCount} failed`;
|
|
6789
6858
|
}
|
|
6859
|
+
/**
|
|
6860
|
+
* Returns debug information about the current session state.
|
|
6861
|
+
* Used by SessionControllerDebug component for runtime inspection.
|
|
6862
|
+
*/
|
|
6863
|
+
getDebugInfo() {
|
|
6864
|
+
const extractQueueItems = (queue, limit = 10) => {
|
|
6865
|
+
const items = [];
|
|
6866
|
+
for (let i = 0; i < Math.min(queue.length, limit); i++) {
|
|
6867
|
+
const item = queue.peek(i);
|
|
6868
|
+
items.push({
|
|
6869
|
+
courseID: item.courseID || "unknown",
|
|
6870
|
+
cardID: item.cardID || "unknown",
|
|
6871
|
+
status: item.status || "unknown"
|
|
6872
|
+
});
|
|
6873
|
+
}
|
|
6874
|
+
return items;
|
|
6875
|
+
};
|
|
6876
|
+
const extractHydratedItems = () => {
|
|
6877
|
+
const items = [];
|
|
6878
|
+
return items;
|
|
6879
|
+
};
|
|
6880
|
+
return {
|
|
6881
|
+
reviewQueue: {
|
|
6882
|
+
length: this.reviewQ.length,
|
|
6883
|
+
dequeueCount: this.reviewQ.dequeueCount,
|
|
6884
|
+
items: extractQueueItems(this.reviewQ)
|
|
6885
|
+
},
|
|
6886
|
+
newQueue: {
|
|
6887
|
+
length: this.newQ.length,
|
|
6888
|
+
dequeueCount: this.newQ.dequeueCount,
|
|
6889
|
+
items: extractQueueItems(this.newQ)
|
|
6890
|
+
},
|
|
6891
|
+
failedQueue: {
|
|
6892
|
+
length: this.failedQ.length,
|
|
6893
|
+
dequeueCount: this.failedQ.dequeueCount,
|
|
6894
|
+
items: extractQueueItems(this.failedQ)
|
|
6895
|
+
},
|
|
6896
|
+
hydratedCache: {
|
|
6897
|
+
count: this.hydrationService.hydratedCount,
|
|
6898
|
+
failedCacheSize: this.hydrationService.failedCacheSize,
|
|
6899
|
+
items: extractHydratedItems()
|
|
6900
|
+
}
|
|
6901
|
+
};
|
|
6902
|
+
}
|
|
6790
6903
|
async getScheduledReviews() {
|
|
6791
6904
|
const reviews = await Promise.all(
|
|
6792
6905
|
this.sources.map(
|