@vue-skuilder/db 0.1.31-a → 0.1.31

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.
Files changed (50) hide show
  1. package/dist/{contentSource-BmnmvH8C.d.ts → contentSource-Bdwkvqa8.d.ts} +35 -4
  2. package/dist/{contentSource-DfBbaLA-.d.cts → contentSource-DF1nUbPQ.d.cts} +35 -4
  3. package/dist/core/index.d.cts +48 -3
  4. package/dist/core/index.d.ts +48 -3
  5. package/dist/core/index.js +587 -56
  6. package/dist/core/index.js.map +1 -1
  7. package/dist/core/index.mjs +586 -56
  8. package/dist/core/index.mjs.map +1 -1
  9. package/dist/{dataLayerProvider-BeRXVMs5.d.cts → dataLayerProvider-BKmVoyJR.d.ts} +20 -1
  10. package/dist/{dataLayerProvider-CG9GfaAY.d.ts → dataLayerProvider-BQdfJuBN.d.cts} +20 -1
  11. package/dist/impl/couch/index.d.cts +156 -4
  12. package/dist/impl/couch/index.d.ts +156 -4
  13. package/dist/impl/couch/index.js +805 -47
  14. package/dist/impl/couch/index.js.map +1 -1
  15. package/dist/impl/couch/index.mjs +804 -47
  16. package/dist/impl/couch/index.mjs.map +1 -1
  17. package/dist/impl/static/index.d.cts +3 -2
  18. package/dist/impl/static/index.d.ts +3 -2
  19. package/dist/impl/static/index.js +542 -37
  20. package/dist/impl/static/index.js.map +1 -1
  21. package/dist/impl/static/index.mjs +542 -37
  22. package/dist/impl/static/index.mjs.map +1 -1
  23. package/dist/index.d.cts +64 -3
  24. package/dist/index.d.ts +64 -3
  25. package/dist/index.js +1040 -90
  26. package/dist/index.js.map +1 -1
  27. package/dist/index.mjs +1030 -81
  28. package/dist/index.mjs.map +1 -1
  29. package/docs/navigators-architecture.md +64 -5
  30. package/package.json +3 -3
  31. package/src/core/interfaces/contentSource.ts +6 -0
  32. package/src/core/interfaces/courseDB.ts +6 -0
  33. package/src/core/interfaces/dataLayerProvider.ts +20 -0
  34. package/src/core/navigators/Pipeline.ts +414 -9
  35. package/src/core/navigators/PipelineAssembler.ts +23 -18
  36. package/src/core/navigators/PipelineDebugger.ts +115 -1
  37. package/src/core/navigators/filters/hierarchyDefinition.ts +78 -8
  38. package/src/core/navigators/generators/prescribed.ts +95 -0
  39. package/src/core/navigators/index.ts +55 -10
  40. package/src/impl/common/BaseUserDB.ts +4 -1
  41. package/src/impl/couch/CourseSyncService.ts +356 -0
  42. package/src/impl/couch/PouchDataLayerProvider.ts +21 -1
  43. package/src/impl/couch/courseDB.ts +60 -13
  44. package/src/impl/couch/index.ts +1 -0
  45. package/src/impl/static/courseDB.ts +5 -0
  46. package/src/study/ItemQueue.ts +42 -0
  47. package/src/study/SessionController.ts +195 -22
  48. package/src/study/SpacedRepetition.ts +7 -2
  49. package/tests/core/navigators/Pipeline.test.ts +1 -1
  50. package/tests/core/navigators/PipelineAssembler.test.ts +15 -14
@@ -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
- Register in `NavigatorRoles` as `NavigatorRole.FILTER`.
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.31-a",
7
+ "version": "0.1.31",
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.31-a",
51
+ "@vue-skuilder/common": "0.1.31",
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.31a"
65
+ "stableVersion": "0.1.31"
66
66
  }
@@ -79,6 +79,12 @@ export interface StudyContentSource {
79
79
  * Used for recording learning outcomes.
80
80
  */
81
81
  getOrchestrationContext?(): Promise<OrchestrationContext>;
82
+
83
+ /**
84
+ * Set ephemeral hints for the next pipeline run.
85
+ * No-op for sources that don't support hints.
86
+ */
87
+ setEphemeralHints?(hints: Record<string, unknown>): void;
82
88
  }
83
89
  // #endregion docs_StudyContentSource
84
90
 
@@ -100,6 +100,12 @@ export interface CourseDBInterface extends NavigationStrategyManager, StudyConte
100
100
  */
101
101
  getAppliedTagsBatch(cardIds: string[]): Promise<Map<string, string[]>>;
102
102
 
103
+ /**
104
+ * Get all card IDs in the course.
105
+ * Used by diagnostics to scan the full card space.
106
+ */
107
+ getAllCardIds(): Promise<string[]>;
108
+
103
109
  /**
104
110
  * Add a tag to a card
105
111
  */
@@ -56,4 +56,24 @@ export interface DataLayerProvider {
56
56
  * Check if this data layer is read-only
57
57
  */
58
58
  isReadOnly(): boolean;
59
+
60
+ /**
61
+ * Trigger local replication of a course database.
62
+ *
63
+ * When a course opts in via `CourseConfig.localSync.enabled`, this method
64
+ * replicates the remote course DB to a local PouchDB instance. Subsequent
65
+ * `getCourseDB()` calls for that course will return a CourseDB that reads
66
+ * from the local replica (fast, no network) and writes to the remote
67
+ * (ELO updates, admin ops).
68
+ *
69
+ * Safe to call multiple times — concurrent calls coalesce. Returns when
70
+ * sync is complete (or immediately if already synced / disabled).
71
+ *
72
+ * Implementations that don't support local sync may no-op.
73
+ *
74
+ * @param courseId - The course to sync locally
75
+ * @param forceEnabled - Skip CourseConfig check and sync regardless.
76
+ * Use when the caller already knows local sync is desired.
77
+ */
78
+ ensureCourseSynced?(courseId: string, forceEnabled?: boolean): Promise<void>;
59
79
  }