@vue-skuilder/db 0.2.1 → 0.2.3
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/{contentSource-Ht3N2f-y.d.ts → contentSource-Cplhv3bJ.d.ts} +1 -1
- package/dist/{contentSource-BMlMwSiG.d.cts → contentSource-kI9_jwTu.d.cts} +1 -1
- package/dist/core/index.d.cts +5 -5
- package/dist/core/index.d.ts +5 -5
- package/dist/core/index.js +76 -21
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.mjs +76 -21
- package/dist/core/index.mjs.map +1 -1
- package/dist/{dataLayerProvider-BEqB8VBR.d.cts → dataLayerProvider-CiA2Rr0v.d.cts} +1 -1
- package/dist/{dataLayerProvider-DObSXjnf.d.ts → dataLayerProvider-DrBqOUa3.d.ts} +1 -1
- package/dist/impl/couch/index.d.cts +35 -3
- package/dist/impl/couch/index.d.ts +35 -3
- package/dist/impl/couch/index.js +76 -21
- package/dist/impl/couch/index.js.map +1 -1
- package/dist/impl/couch/index.mjs +76 -21
- package/dist/impl/couch/index.mjs.map +1 -1
- package/dist/impl/static/index.d.cts +4 -4
- package/dist/impl/static/index.d.ts +4 -4
- package/dist/impl/static/index.js +4 -5
- package/dist/impl/static/index.js.map +1 -1
- package/dist/impl/static/index.mjs +4 -5
- package/dist/impl/static/index.mjs.map +1 -1
- package/dist/{index-BWvO-_rJ.d.ts → index-BLLT5BYE.d.ts} +1 -1
- package/dist/{index-Ba7hYbHj.d.cts → index-k9NFHpS1.d.cts} +1 -1
- package/dist/index.d.cts +164 -10
- package/dist/index.d.ts +164 -10
- package/dist/index.js +215 -28
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +215 -28
- package/dist/index.mjs.map +1 -1
- package/dist/{types-W8n-B6HG.d.cts → types-BFUa1pa3.d.cts} +1 -1
- package/dist/{types-CJrLM1Ew.d.ts → types-CHgpWQAY.d.ts} +1 -1
- package/dist/{types-legacy-JXDxinpU.d.cts → types-legacy-4tlwHnXo.d.cts} +1 -1
- package/dist/{types-legacy-JXDxinpU.d.ts → types-legacy-4tlwHnXo.d.ts} +1 -1
- package/dist/util/packer/index.d.cts +3 -3
- package/dist/util/packer/index.d.ts +3 -3
- package/docs/session-lifecycle-and-replan.md +418 -0
- package/package.json +3 -3
- package/src/core/navigators/Pipeline.ts +5 -1
- package/src/core/navigators/generators/elo.ts +19 -6
- package/src/core/navigators/generators/srs.ts +10 -0
- package/src/impl/couch/courseDB.ts +146 -17
- package/src/study/SessionController.ts +295 -13
- package/src/study/services/CardHydrationService.ts +24 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { U as UserDBInterface, a as UserDBReader, C as CourseDBInterface, b as CoursesDBInterface, c as ClassroomDBInterface, A as AdminDBInterface } from './contentSource-
|
|
1
|
+
import { U as UserDBInterface, a as UserDBReader, C as CourseDBInterface, b as CoursesDBInterface, c as ClassroomDBInterface, A as AdminDBInterface } from './contentSource-kI9_jwTu.cjs';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Main factory interface for data access
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { U as UserDBInterface, a as UserDBReader, C as CourseDBInterface, b as CoursesDBInterface, c as ClassroomDBInterface, A as AdminDBInterface } from './contentSource-
|
|
1
|
+
import { U as UserDBInterface, a as UserDBReader, C as CourseDBInterface, b as CoursesDBInterface, c as ClassroomDBInterface, A as AdminDBInterface } from './contentSource-Cplhv3bJ.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Main factory interface for data access
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { T as TagStub, a as Tag, S as SkuilderCourseData, Q as QualifiedCardID } from '../../types-legacy-
|
|
1
|
+
import { T as TagStub, a as Tag, S as SkuilderCourseData, Q as QualifiedCardID } from '../../types-legacy-4tlwHnXo.cjs';
|
|
2
2
|
import { Moment } from 'moment';
|
|
3
|
-
import { A as AdminDBInterface, g as AssignedContent, h as StudyContentSource, i as StudentClassroomDBInterface, U as UserDBInterface, G as GeneratorResult, T as TeacherClassroomDBInterface, b as CoursesDBInterface, C as CourseDBInterface, d as CourseInfo, D as DataLayerResult, e as ContentNavigationStrategyData, f as ContentNavigator, R as ReplanHints, S as StudySessionItem, j as ScheduledCard } from '../../contentSource-
|
|
4
|
-
export { q as ContentSourceID, k as StudySessionFailedItem, l as StudySessionFailedNewItem, m as StudySessionFailedReviewItem, n as StudySessionNewItem, o as StudySessionReviewItem, r as getStudySource, p as isReview } from '../../contentSource-
|
|
3
|
+
import { A as AdminDBInterface, g as AssignedContent, h as StudyContentSource, i as StudentClassroomDBInterface, U as UserDBInterface, G as GeneratorResult, T as TeacherClassroomDBInterface, b as CoursesDBInterface, C as CourseDBInterface, d as CourseInfo, D as DataLayerResult, e as ContentNavigationStrategyData, f as ContentNavigator, R as ReplanHints, S as StudySessionItem, j as ScheduledCard } from '../../contentSource-kI9_jwTu.cjs';
|
|
4
|
+
export { q as ContentSourceID, k as StudySessionFailedItem, l as StudySessionFailedNewItem, m as StudySessionFailedReviewItem, n as StudySessionNewItem, o as StudySessionReviewItem, r as getStudySource, p as isReview } from '../../contentSource-kI9_jwTu.cjs';
|
|
5
5
|
import * as _vue_skuilder_common from '@vue-skuilder/common';
|
|
6
6
|
import { ClassroomConfig, DataShape, CourseElo, CourseConfig as CourseConfig$1 } from '@vue-skuilder/common';
|
|
7
7
|
import { S as SyncStrategy, A as AccountCreationResult, a as AuthenticationResult } from '../../SyncStrategy-CyATpyLQ.cjs';
|
|
@@ -279,8 +279,40 @@ declare class CourseDB implements CourseDBInterface {
|
|
|
279
279
|
* @returns Cards sorted by score descending
|
|
280
280
|
*/
|
|
281
281
|
private _pendingHints;
|
|
282
|
+
/**
|
|
283
|
+
* Session-scoped cache of the broad ELO-neighbor pool used by
|
|
284
|
+
* getCardsCenteredAtELO. The `elo` view query re-indexes on first touch per
|
|
285
|
+
* call (PouchDB 9 ignores `stale`), so without this each plan/replan pays
|
|
286
|
+
* ~1.5-2s. The pool is fetched once and re-ranked against the live (roaming)
|
|
287
|
+
* ELO in memory on subsequent calls.
|
|
288
|
+
*/
|
|
289
|
+
private _eloPoolCache;
|
|
290
|
+
private readonly _eloPoolTtlMs;
|
|
291
|
+
/**
|
|
292
|
+
* Cached assembled navigator (Pipeline). createNavigator() reads strategy
|
|
293
|
+
* docs and builds a fresh Pipeline every call — whose internal `_tagCache`
|
|
294
|
+
* and `_cachedOrchestration` are designed to make replans cheap but never
|
|
295
|
+
* survive, because the instance is discarded each run. Caching it lets those
|
|
296
|
+
* caches persist across plan/replan within a session (SessionController holds
|
|
297
|
+
* one CourseDB instance for the session's lifetime). Rebuilt on user change,
|
|
298
|
+
* TTL expiry, or explicit invalidation after a strategy-doc write.
|
|
299
|
+
*/
|
|
300
|
+
private _cachedNavigator;
|
|
301
|
+
private readonly _navigatorTtlMs;
|
|
282
302
|
setEphemeralHints(hints: ReplanHints): void;
|
|
283
303
|
getWeightedCards(limit: number): Promise<GeneratorResult>;
|
|
304
|
+
/**
|
|
305
|
+
* Return the assembled navigator, reusing the cached instance when possible.
|
|
306
|
+
* Reuse preserves the Pipeline's per-session caches (tags, orchestration
|
|
307
|
+
* context) across replans, which is the dominant per-replan cost once the
|
|
308
|
+
* ELO-pool cost is removed. Rebuilds on user change or TTL expiry.
|
|
309
|
+
*/
|
|
310
|
+
private _getCachedNavigator;
|
|
311
|
+
/**
|
|
312
|
+
* Drop the cached navigator so the next getWeightedCards() rebuilds it.
|
|
313
|
+
* Call after mutating this course's navigation strategy documents.
|
|
314
|
+
*/
|
|
315
|
+
invalidateNavigatorCache(): void;
|
|
284
316
|
getCardsCenteredAtELO(options?: {
|
|
285
317
|
limit: number;
|
|
286
318
|
elo: 'user' | 'random' | number;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { T as TagStub, a as Tag, S as SkuilderCourseData, Q as QualifiedCardID } from '../../types-legacy-
|
|
1
|
+
import { T as TagStub, a as Tag, S as SkuilderCourseData, Q as QualifiedCardID } from '../../types-legacy-4tlwHnXo.js';
|
|
2
2
|
import { Moment } from 'moment';
|
|
3
|
-
import { A as AdminDBInterface, g as AssignedContent, h as StudyContentSource, i as StudentClassroomDBInterface, U as UserDBInterface, G as GeneratorResult, T as TeacherClassroomDBInterface, b as CoursesDBInterface, C as CourseDBInterface, d as CourseInfo, D as DataLayerResult, e as ContentNavigationStrategyData, f as ContentNavigator, R as ReplanHints, S as StudySessionItem, j as ScheduledCard } from '../../contentSource-
|
|
4
|
-
export { q as ContentSourceID, k as StudySessionFailedItem, l as StudySessionFailedNewItem, m as StudySessionFailedReviewItem, n as StudySessionNewItem, o as StudySessionReviewItem, r as getStudySource, p as isReview } from '../../contentSource-
|
|
3
|
+
import { A as AdminDBInterface, g as AssignedContent, h as StudyContentSource, i as StudentClassroomDBInterface, U as UserDBInterface, G as GeneratorResult, T as TeacherClassroomDBInterface, b as CoursesDBInterface, C as CourseDBInterface, d as CourseInfo, D as DataLayerResult, e as ContentNavigationStrategyData, f as ContentNavigator, R as ReplanHints, S as StudySessionItem, j as ScheduledCard } from '../../contentSource-Cplhv3bJ.js';
|
|
4
|
+
export { q as ContentSourceID, k as StudySessionFailedItem, l as StudySessionFailedNewItem, m as StudySessionFailedReviewItem, n as StudySessionNewItem, o as StudySessionReviewItem, r as getStudySource, p as isReview } from '../../contentSource-Cplhv3bJ.js';
|
|
5
5
|
import * as _vue_skuilder_common from '@vue-skuilder/common';
|
|
6
6
|
import { ClassroomConfig, DataShape, CourseElo, CourseConfig as CourseConfig$1 } from '@vue-skuilder/common';
|
|
7
7
|
import { S as SyncStrategy, A as AccountCreationResult, a as AuthenticationResult } from '../../SyncStrategy-CyATpyLQ.js';
|
|
@@ -279,8 +279,40 @@ declare class CourseDB implements CourseDBInterface {
|
|
|
279
279
|
* @returns Cards sorted by score descending
|
|
280
280
|
*/
|
|
281
281
|
private _pendingHints;
|
|
282
|
+
/**
|
|
283
|
+
* Session-scoped cache of the broad ELO-neighbor pool used by
|
|
284
|
+
* getCardsCenteredAtELO. The `elo` view query re-indexes on first touch per
|
|
285
|
+
* call (PouchDB 9 ignores `stale`), so without this each plan/replan pays
|
|
286
|
+
* ~1.5-2s. The pool is fetched once and re-ranked against the live (roaming)
|
|
287
|
+
* ELO in memory on subsequent calls.
|
|
288
|
+
*/
|
|
289
|
+
private _eloPoolCache;
|
|
290
|
+
private readonly _eloPoolTtlMs;
|
|
291
|
+
/**
|
|
292
|
+
* Cached assembled navigator (Pipeline). createNavigator() reads strategy
|
|
293
|
+
* docs and builds a fresh Pipeline every call — whose internal `_tagCache`
|
|
294
|
+
* and `_cachedOrchestration` are designed to make replans cheap but never
|
|
295
|
+
* survive, because the instance is discarded each run. Caching it lets those
|
|
296
|
+
* caches persist across plan/replan within a session (SessionController holds
|
|
297
|
+
* one CourseDB instance for the session's lifetime). Rebuilt on user change,
|
|
298
|
+
* TTL expiry, or explicit invalidation after a strategy-doc write.
|
|
299
|
+
*/
|
|
300
|
+
private _cachedNavigator;
|
|
301
|
+
private readonly _navigatorTtlMs;
|
|
282
302
|
setEphemeralHints(hints: ReplanHints): void;
|
|
283
303
|
getWeightedCards(limit: number): Promise<GeneratorResult>;
|
|
304
|
+
/**
|
|
305
|
+
* Return the assembled navigator, reusing the cached instance when possible.
|
|
306
|
+
* Reuse preserves the Pipeline's per-session caches (tags, orchestration
|
|
307
|
+
* context) across replans, which is the dominant per-replan cost once the
|
|
308
|
+
* ELO-pool cost is removed. Rebuilds on user change or TTL expiry.
|
|
309
|
+
*/
|
|
310
|
+
private _getCachedNavigator;
|
|
311
|
+
/**
|
|
312
|
+
* Drop the cached navigator so the next getWeightedCards() rebuilds it.
|
|
313
|
+
* Call after mutating this course's navigation strategy documents.
|
|
314
|
+
*/
|
|
315
|
+
invalidateNavigatorCache(): void;
|
|
284
316
|
getCardsCenteredAtELO(options?: {
|
|
285
317
|
limit: number;
|
|
286
318
|
elo: 'user' | 'random' | number;
|
package/dist/impl/couch/index.js
CHANGED
|
@@ -1753,10 +1753,8 @@ var init_elo = __esm({
|
|
|
1753
1753
|
{ limit, elo: "user" },
|
|
1754
1754
|
(c) => !activeCards.some((ac) => c.cardID === ac.cardID)
|
|
1755
1755
|
)).map((c) => ({ ...c, status: "new" }));
|
|
1756
|
-
const
|
|
1757
|
-
|
|
1758
|
-
const scored = newCards.map((c, i) => {
|
|
1759
|
-
const cardElo = cardEloData[i]?.global?.score ?? 1e3;
|
|
1756
|
+
const scored = newCards.map((c) => {
|
|
1757
|
+
const cardElo = c.elo ?? 1e3;
|
|
1760
1758
|
const distance = Math.abs(cardElo - userGlobalElo);
|
|
1761
1759
|
const rawScore = Math.max(0, 1 - distance / 500);
|
|
1762
1760
|
const samplingKey = rawScore > 0 ? Math.random() ** (1 / rawScore) : 0;
|
|
@@ -3734,7 +3732,8 @@ var init_orchestration = __esm({
|
|
|
3734
3732
|
// src/core/navigators/Pipeline.ts
|
|
3735
3733
|
var Pipeline_exports = {};
|
|
3736
3734
|
__export(Pipeline_exports, {
|
|
3737
|
-
Pipeline: () => Pipeline
|
|
3735
|
+
Pipeline: () => Pipeline,
|
|
3736
|
+
mergeHints: () => mergeHints2
|
|
3738
3737
|
});
|
|
3739
3738
|
function globToRegex(pattern) {
|
|
3740
3739
|
const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
|
|
@@ -5468,6 +5467,7 @@ ${above.rows.map((r) => ` ${r.id}-${r.key}
|
|
|
5468
5467
|
}
|
|
5469
5468
|
async addNavigationStrategy(data) {
|
|
5470
5469
|
logger.debug(`[courseDB] Adding navigation strategy: ${data._id}`);
|
|
5470
|
+
this.invalidateNavigatorCache();
|
|
5471
5471
|
return this.remoteDB.put(data).then(() => {
|
|
5472
5472
|
});
|
|
5473
5473
|
}
|
|
@@ -5534,23 +5534,67 @@ ${e.stack}` : JSON.stringify(e);
|
|
|
5534
5534
|
* @returns Cards sorted by score descending
|
|
5535
5535
|
*/
|
|
5536
5536
|
_pendingHints = null;
|
|
5537
|
+
/**
|
|
5538
|
+
* Session-scoped cache of the broad ELO-neighbor pool used by
|
|
5539
|
+
* getCardsCenteredAtELO. The `elo` view query re-indexes on first touch per
|
|
5540
|
+
* call (PouchDB 9 ignores `stale`), so without this each plan/replan pays
|
|
5541
|
+
* ~1.5-2s. The pool is fetched once and re-ranked against the live (roaming)
|
|
5542
|
+
* ELO in memory on subsequent calls.
|
|
5543
|
+
*/
|
|
5544
|
+
_eloPoolCache = null;
|
|
5545
|
+
_eloPoolTtlMs = 5 * 60 * 1e3;
|
|
5546
|
+
/**
|
|
5547
|
+
* Cached assembled navigator (Pipeline). createNavigator() reads strategy
|
|
5548
|
+
* docs and builds a fresh Pipeline every call — whose internal `_tagCache`
|
|
5549
|
+
* and `_cachedOrchestration` are designed to make replans cheap but never
|
|
5550
|
+
* survive, because the instance is discarded each run. Caching it lets those
|
|
5551
|
+
* caches persist across plan/replan within a session (SessionController holds
|
|
5552
|
+
* one CourseDB instance for the session's lifetime). Rebuilt on user change,
|
|
5553
|
+
* TTL expiry, or explicit invalidation after a strategy-doc write.
|
|
5554
|
+
*/
|
|
5555
|
+
_cachedNavigator = null;
|
|
5556
|
+
_navigatorTtlMs = 5 * 60 * 1e3;
|
|
5537
5557
|
setEphemeralHints(hints) {
|
|
5538
5558
|
this._pendingHints = hints;
|
|
5539
5559
|
}
|
|
5540
5560
|
async getWeightedCards(limit) {
|
|
5541
5561
|
const u = await this._getCurrentUser();
|
|
5542
5562
|
try {
|
|
5543
|
-
const navigator2 = await this.
|
|
5563
|
+
const { navigator: navigator2 } = await this._getCachedNavigator(u);
|
|
5544
5564
|
if (this._pendingHints) {
|
|
5545
5565
|
navigator2.setEphemeralHints(this._pendingHints);
|
|
5546
5566
|
this._pendingHints = null;
|
|
5547
5567
|
}
|
|
5548
|
-
|
|
5568
|
+
const result = await navigator2.getWeightedCards(limit);
|
|
5569
|
+
return result;
|
|
5549
5570
|
} catch (e) {
|
|
5550
5571
|
logger.error(`[courseDB] Error getting weighted cards: ${e}`);
|
|
5551
5572
|
throw e;
|
|
5552
5573
|
}
|
|
5553
5574
|
}
|
|
5575
|
+
/**
|
|
5576
|
+
* Return the assembled navigator, reusing the cached instance when possible.
|
|
5577
|
+
* Reuse preserves the Pipeline's per-session caches (tags, orchestration
|
|
5578
|
+
* context) across replans, which is the dominant per-replan cost once the
|
|
5579
|
+
* ELO-pool cost is removed. Rebuilds on user change or TTL expiry.
|
|
5580
|
+
*/
|
|
5581
|
+
async _getCachedNavigator(user) {
|
|
5582
|
+
const userId = user.getUsername();
|
|
5583
|
+
const now = Date.now();
|
|
5584
|
+
if (this._cachedNavigator && this._cachedNavigator.userId === userId && now - this._cachedNavigator.builtAt < this._navigatorTtlMs) {
|
|
5585
|
+
return { navigator: this._cachedNavigator.navigator, cacheStatus: "hit" };
|
|
5586
|
+
}
|
|
5587
|
+
const navigator2 = await this.createNavigator(user);
|
|
5588
|
+
this._cachedNavigator = { navigator: navigator2, userId, builtAt: now };
|
|
5589
|
+
return { navigator: navigator2, cacheStatus: "miss" };
|
|
5590
|
+
}
|
|
5591
|
+
/**
|
|
5592
|
+
* Drop the cached navigator so the next getWeightedCards() rebuilds it.
|
|
5593
|
+
* Call after mutating this course's navigation strategy documents.
|
|
5594
|
+
*/
|
|
5595
|
+
invalidateNavigatorCache() {
|
|
5596
|
+
this._cachedNavigator = null;
|
|
5597
|
+
}
|
|
5554
5598
|
async getCardsCenteredAtELO(options = {
|
|
5555
5599
|
limit: 99,
|
|
5556
5600
|
elo: "user"
|
|
@@ -5573,20 +5617,31 @@ ${e.stack}` : JSON.stringify(e);
|
|
|
5573
5617
|
} else {
|
|
5574
5618
|
targetElo = options.elo;
|
|
5575
5619
|
}
|
|
5576
|
-
|
|
5577
|
-
|
|
5578
|
-
let
|
|
5579
|
-
|
|
5580
|
-
|
|
5581
|
-
|
|
5582
|
-
|
|
5583
|
-
|
|
5584
|
-
|
|
5585
|
-
|
|
5586
|
-
|
|
5587
|
-
|
|
5588
|
-
|
|
5589
|
-
|
|
5620
|
+
const POOL_SIZE = Math.max(2e3, options.limit * 4);
|
|
5621
|
+
const nowMs = Date.now();
|
|
5622
|
+
let cacheStatus = "hit";
|
|
5623
|
+
if (!this._eloPoolCache || nowMs - this._eloPoolCache.fetchedAt > this._eloPoolTtlMs) {
|
|
5624
|
+
const fetched = await this.getCardsByELO(targetElo, POOL_SIZE);
|
|
5625
|
+
if (fetched.length > 0) {
|
|
5626
|
+
this._eloPoolCache = { rows: fetched, fetchedAt: nowMs };
|
|
5627
|
+
}
|
|
5628
|
+
cacheStatus = "miss";
|
|
5629
|
+
}
|
|
5630
|
+
const rankAgainstCurrentElo = () => {
|
|
5631
|
+
const raw = this._eloPoolCache?.rows ?? [];
|
|
5632
|
+
const survivors = filter ? raw.filter((c) => filter(c)) : raw;
|
|
5633
|
+
return survivors.map((c) => ({ ...c })).sort(
|
|
5634
|
+
(a, b) => Math.abs((a.elo ?? targetElo) - targetElo) - Math.abs((b.elo ?? targetElo) - targetElo)
|
|
5635
|
+
);
|
|
5636
|
+
};
|
|
5637
|
+
let cards = rankAgainstCurrentElo();
|
|
5638
|
+
if (cards.length < options.limit && (cacheStatus === "hit" || !this._eloPoolCache)) {
|
|
5639
|
+
const fetched = await this.getCardsByELO(targetElo, POOL_SIZE);
|
|
5640
|
+
if (fetched.length > 0) {
|
|
5641
|
+
this._eloPoolCache = { rows: fetched, fetchedAt: nowMs };
|
|
5642
|
+
}
|
|
5643
|
+
cards = rankAgainstCurrentElo();
|
|
5644
|
+
cacheStatus = "refresh";
|
|
5590
5645
|
}
|
|
5591
5646
|
const selectedCards = [];
|
|
5592
5647
|
while (selectedCards.length < options.limit && cards.length > 0) {
|