@vue-skuilder/db 0.1.29 → 0.1.30
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-DfBbaLA-.d.cts → contentSource-B7nXusjk.d.cts} +19 -4
- package/dist/{contentSource-BmnmvH8C.d.ts → contentSource-ygoFw9oV.d.ts} +19 -4
- package/dist/core/index.d.cts +3 -3
- package/dist/core/index.d.ts +3 -3
- package/dist/core/index.js +14 -6
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.mjs +13 -6
- package/dist/core/index.mjs.map +1 -1
- package/dist/{dataLayerProvider-BeRXVMs5.d.cts → dataLayerProvider-BW7HvkMt.d.cts} +1 -1
- package/dist/{dataLayerProvider-CG9GfaAY.d.ts → dataLayerProvider-BfXUVDuG.d.ts} +1 -1
- package/dist/impl/couch/index.d.cts +2 -2
- package/dist/impl/couch/index.d.ts +2 -2
- package/dist/impl/couch/index.js +12 -6
- package/dist/impl/couch/index.js.map +1 -1
- package/dist/impl/couch/index.mjs +12 -6
- package/dist/impl/couch/index.mjs.map +1 -1
- package/dist/impl/static/index.d.cts +2 -2
- package/dist/impl/static/index.d.ts +2 -2
- package/dist/impl/static/index.js +12 -6
- package/dist/impl/static/index.js.map +1 -1
- package/dist/impl/static/index.mjs +12 -6
- package/dist/impl/static/index.mjs.map +1 -1
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +14 -6
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +13 -6
- package/dist/index.mjs.map +1 -1
- package/docs/navigators-architecture.md +64 -5
- package/package.json +3 -3
- package/src/core/navigators/index.ts +43 -10
|
@@ -374,6 +374,45 @@ Set `staticWeight: true` for foundational strategies that should not be tuned:
|
|
|
374
374
|
|
|
375
375
|
## Creating New Strategies
|
|
376
376
|
|
|
377
|
+
Strategies can be defined in two places:
|
|
378
|
+
|
|
379
|
+
- **Framework-internal:** Added directly to `NavigatorRoles` in `index.ts`. Used
|
|
380
|
+
for general-purpose strategies shipped with the framework.
|
|
381
|
+
- **Consumer-defined:** Registered at app startup via `registerNavigator()`.
|
|
382
|
+
Used for course-specific strategies that live in the consumer codebase.
|
|
383
|
+
|
|
384
|
+
Both types participate identically in the pipeline once registered.
|
|
385
|
+
|
|
386
|
+
### Registration
|
|
387
|
+
|
|
388
|
+
Framework-internal strategies are listed in the hardcoded `NavigatorRoles` record.
|
|
389
|
+
Consumer-defined strategies use the public `registerNavigator()` API:
|
|
390
|
+
|
|
391
|
+
```typescript
|
|
392
|
+
import { registerNavigator, NavigatorRole } from '@vue-skuilder/db';
|
|
393
|
+
import { MyFilter } from './MyFilter';
|
|
394
|
+
|
|
395
|
+
// At app init, before any study session:
|
|
396
|
+
registerNavigator('myFilter', MyFilter, NavigatorRole.FILTER);
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
The third argument (`role`) is **required** for consumer-defined strategies —
|
|
400
|
+
without it, `PipelineAssembler` cannot classify the strategy and will skip it
|
|
401
|
+
with a warning. For framework-internal strategies the role is already in
|
|
402
|
+
`NavigatorRoles`, so the argument is optional.
|
|
403
|
+
|
|
404
|
+
A corresponding `NAVIGATION_STRATEGY` document must exist in CouchDB with
|
|
405
|
+
`implementingClass` matching the registered name:
|
|
406
|
+
|
|
407
|
+
```json
|
|
408
|
+
{
|
|
409
|
+
"_id": "NAVIGATION_STRATEGY-my-filter",
|
|
410
|
+
"implementingClass": "myFilter",
|
|
411
|
+
"name": "My Filter",
|
|
412
|
+
"serializedData": "{}"
|
|
413
|
+
}
|
|
414
|
+
```
|
|
415
|
+
|
|
377
416
|
### Generator
|
|
378
417
|
|
|
379
418
|
```typescript
|
|
@@ -382,7 +421,7 @@ class MyGenerator extends ContentNavigator implements CardGenerator {
|
|
|
382
421
|
|
|
383
422
|
async getWeightedCards(limit: number, context?: GeneratorContext): Promise<WeightedCard[]> {
|
|
384
423
|
const candidates = await this.findCandidates(limit);
|
|
385
|
-
|
|
424
|
+
|
|
386
425
|
return candidates.map(c => ({
|
|
387
426
|
cardId: c.id,
|
|
388
427
|
courseId: this.course.getCourseID(),
|
|
@@ -400,8 +439,6 @@ class MyGenerator extends ContentNavigator implements CardGenerator {
|
|
|
400
439
|
}
|
|
401
440
|
```
|
|
402
441
|
|
|
403
|
-
Register in `NavigatorRoles` as `NavigatorRole.GENERATOR`.
|
|
404
|
-
|
|
405
442
|
### Filter
|
|
406
443
|
|
|
407
444
|
```typescript
|
|
@@ -413,7 +450,7 @@ class MyFilter extends ContentNavigator implements CardFilter {
|
|
|
413
450
|
const multiplier = this.computeMultiplier(card, context);
|
|
414
451
|
const newScore = card.score * multiplier;
|
|
415
452
|
const action = multiplier < 1 ? 'penalized' : multiplier > 1 ? 'boosted' : 'passed';
|
|
416
|
-
|
|
453
|
+
|
|
417
454
|
return {
|
|
418
455
|
...card,
|
|
419
456
|
score: newScore,
|
|
@@ -434,7 +471,29 @@ class MyFilter extends ContentNavigator implements CardFilter {
|
|
|
434
471
|
}
|
|
435
472
|
```
|
|
436
473
|
|
|
437
|
-
|
|
474
|
+
### Accessing Strategy State from Consumer Filters
|
|
475
|
+
|
|
476
|
+
Consumer strategies can share state with other parts of the consumer app via
|
|
477
|
+
`getStrategyState()` / `putStrategyState()`. Override `strategyKey` to read
|
|
478
|
+
an existing state document:
|
|
479
|
+
|
|
480
|
+
```typescript
|
|
481
|
+
class MyFilter extends ContentNavigator implements CardFilter {
|
|
482
|
+
// Read the same doc that another part of the app writes
|
|
483
|
+
protected get strategyKey(): string {
|
|
484
|
+
return 'MySharedStateKey';
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
async transform(cards: WeightedCard[], context: FilterContext): Promise<WeightedCard[]> {
|
|
488
|
+
const state = await this.getStrategyState<MyStateType>();
|
|
489
|
+
// ... use state for filtering decisions
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
This enables **single source of truth** patterns: the consumer app writes state
|
|
495
|
+
via `UsrCrsDataInterface.putStrategyState()`, and the consumer filter reads it
|
|
496
|
+
via the same key. No framework changes needed.
|
|
438
497
|
|
|
439
498
|
---
|
|
440
499
|
|
package/package.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
7
|
-
"version": "0.1.
|
|
7
|
+
"version": "0.1.30",
|
|
8
8
|
"description": "Database layer for vue-skuilder",
|
|
9
9
|
"main": "dist/index.js",
|
|
10
10
|
"module": "dist/index.mjs",
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
},
|
|
49
49
|
"dependencies": {
|
|
50
50
|
"@nilock2/pouchdb-authentication": "^1.0.2",
|
|
51
|
-
"@vue-skuilder/common": "0.1.
|
|
51
|
+
"@vue-skuilder/common": "0.1.30",
|
|
52
52
|
"cross-fetch": "^4.1.0",
|
|
53
53
|
"moment": "^2.29.4",
|
|
54
54
|
"pouchdb": "^9.0.0",
|
|
@@ -62,5 +62,5 @@
|
|
|
62
62
|
"vite": "^7.0.0",
|
|
63
63
|
"vitest": "^4.0.15"
|
|
64
64
|
},
|
|
65
|
-
"stableVersion": "0.1.
|
|
65
|
+
"stableVersion": "0.1.30"
|
|
66
66
|
}
|
|
@@ -46,10 +46,20 @@ export type NavigatorConstructor = new (
|
|
|
46
46
|
) => ContentNavigator;
|
|
47
47
|
|
|
48
48
|
/**
|
|
49
|
-
*
|
|
49
|
+
* Entry in the navigator registry, storing the constructor and an optional
|
|
50
|
+
* pipeline role. The role is used by PipelineAssembler to classify
|
|
51
|
+
* consumer-registered navigators that aren't in the built-in Navigators enum.
|
|
52
|
+
*/
|
|
53
|
+
interface NavigatorRegistryEntry {
|
|
54
|
+
constructor: NavigatorConstructor;
|
|
55
|
+
role?: NavigatorRole;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Registry mapping implementingClass names to navigator entries.
|
|
50
60
|
* Populated by registerNavigator() and used by ContentNavigator.create().
|
|
51
61
|
*/
|
|
52
|
-
const navigatorRegistry = new Map<string,
|
|
62
|
+
const navigatorRegistry = new Map<string, NavigatorRegistryEntry>();
|
|
53
63
|
|
|
54
64
|
/**
|
|
55
65
|
* Register a navigator implementation.
|
|
@@ -57,15 +67,21 @@ const navigatorRegistry = new Map<string, NavigatorConstructor>();
|
|
|
57
67
|
* Call this to make a navigator available for instantiation by
|
|
58
68
|
* ContentNavigator.create() without relying on dynamic imports.
|
|
59
69
|
*
|
|
60
|
-
*
|
|
70
|
+
* Passing a `role` is optional for built-in navigators (whose roles are in
|
|
71
|
+
* the hardcoded `NavigatorRoles` record), but **required** for consumer-
|
|
72
|
+
* defined navigators that need to participate in pipeline assembly.
|
|
73
|
+
*
|
|
74
|
+
* @param implementingClass - The class name (e.g., 'elo', 'letterGatingFilter')
|
|
61
75
|
* @param constructor - The navigator class constructor
|
|
76
|
+
* @param role - Optional pipeline role (GENERATOR or FILTER)
|
|
62
77
|
*/
|
|
63
78
|
export function registerNavigator(
|
|
64
79
|
implementingClass: string,
|
|
65
|
-
constructor: NavigatorConstructor
|
|
80
|
+
constructor: NavigatorConstructor,
|
|
81
|
+
role?: NavigatorRole
|
|
66
82
|
): void {
|
|
67
|
-
navigatorRegistry.set(implementingClass, constructor);
|
|
68
|
-
logger.debug(`[NavigatorRegistry] Registered: ${implementingClass}`);
|
|
83
|
+
navigatorRegistry.set(implementingClass, { constructor, role });
|
|
84
|
+
logger.debug(`[NavigatorRegistry] Registered: ${implementingClass}${role ? ` (${role})` : ''}`);
|
|
69
85
|
}
|
|
70
86
|
|
|
71
87
|
/**
|
|
@@ -75,7 +91,7 @@ export function registerNavigator(
|
|
|
75
91
|
* @returns The constructor, or undefined if not registered
|
|
76
92
|
*/
|
|
77
93
|
export function getRegisteredNavigator(implementingClass: string): NavigatorConstructor | undefined {
|
|
78
|
-
return navigatorRegistry.get(implementingClass);
|
|
94
|
+
return navigatorRegistry.get(implementingClass)?.constructor;
|
|
79
95
|
}
|
|
80
96
|
|
|
81
97
|
/**
|
|
@@ -88,6 +104,16 @@ export function hasRegisteredNavigator(implementingClass: string): boolean {
|
|
|
88
104
|
return navigatorRegistry.has(implementingClass);
|
|
89
105
|
}
|
|
90
106
|
|
|
107
|
+
/**
|
|
108
|
+
* Get the registered role for a navigator, if one was provided at registration.
|
|
109
|
+
*
|
|
110
|
+
* @param implementingClass - The class name to look up
|
|
111
|
+
* @returns The role, or undefined if not registered or no role was specified
|
|
112
|
+
*/
|
|
113
|
+
export function getRegisteredNavigatorRole(implementingClass: string): NavigatorRole | undefined {
|
|
114
|
+
return navigatorRegistry.get(implementingClass)?.role;
|
|
115
|
+
}
|
|
116
|
+
|
|
91
117
|
/**
|
|
92
118
|
* Get all registered navigator names.
|
|
93
119
|
* Useful for debugging and testing.
|
|
@@ -371,17 +397,24 @@ export const NavigatorRoles: Record<Navigators, NavigatorRole> = {
|
|
|
371
397
|
* @returns true if the navigator is a generator, false otherwise
|
|
372
398
|
*/
|
|
373
399
|
export function isGenerator(impl: string): boolean {
|
|
374
|
-
|
|
400
|
+
if (NavigatorRoles[impl as Navigators] === NavigatorRole.GENERATOR) return true;
|
|
401
|
+
// Fallback: check the registry for consumer-registered navigators
|
|
402
|
+
return getRegisteredNavigatorRole(impl) === NavigatorRole.GENERATOR;
|
|
375
403
|
}
|
|
376
404
|
|
|
377
405
|
/**
|
|
378
406
|
* Check if a navigator implementation is a filter.
|
|
379
407
|
*
|
|
380
|
-
*
|
|
408
|
+
* Checks the built-in NavigatorRoles enum first, then falls back to the
|
|
409
|
+
* navigator registry for consumer-registered navigators.
|
|
410
|
+
*
|
|
411
|
+
* @param impl - Navigator implementation name (e.g., 'elo', 'letterGatingFilter')
|
|
381
412
|
* @returns true if the navigator is a filter, false otherwise
|
|
382
413
|
*/
|
|
383
414
|
export function isFilter(impl: string): boolean {
|
|
384
|
-
|
|
415
|
+
if (NavigatorRoles[impl as Navigators] === NavigatorRole.FILTER) return true;
|
|
416
|
+
// Fallback: check the registry for consumer-registered navigators
|
|
417
|
+
return getRegisteredNavigatorRole(impl) === NavigatorRole.FILTER;
|
|
385
418
|
}
|
|
386
419
|
|
|
387
420
|
/**
|