@vue-skuilder/db 0.1.18 → 0.1.21

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 (87) hide show
  1. package/CLAUDE.md +2 -2
  2. package/dist/{classroomDB-BgfrVb8d.d.ts → contentSource-BP9hznNV.d.ts} +220 -197
  3. package/dist/{classroomDB-CTOenngH.d.cts → contentSource-DsJadoBU.d.cts} +220 -197
  4. package/dist/core/index.d.cts +80 -6
  5. package/dist/core/index.d.ts +80 -6
  6. package/dist/core/index.js +735 -1560
  7. package/dist/core/index.js.map +1 -1
  8. package/dist/core/index.mjs +708 -1539
  9. package/dist/core/index.mjs.map +1 -1
  10. package/dist/{dataLayerProvider-D6PoCwS6.d.cts → dataLayerProvider-CHYrQ5pB.d.cts} +1 -1
  11. package/dist/{dataLayerProvider-CZxC9GtB.d.ts → dataLayerProvider-MDTxXq2l.d.ts} +1 -1
  12. package/dist/impl/couch/index.d.cts +8 -23
  13. package/dist/impl/couch/index.d.ts +8 -23
  14. package/dist/impl/couch/index.js +723 -1578
  15. package/dist/impl/couch/index.js.map +1 -1
  16. package/dist/impl/couch/index.mjs +692 -1552
  17. package/dist/impl/couch/index.mjs.map +1 -1
  18. package/dist/impl/static/index.d.cts +25 -8
  19. package/dist/impl/static/index.d.ts +25 -8
  20. package/dist/impl/static/index.js +700 -1400
  21. package/dist/impl/static/index.js.map +1 -1
  22. package/dist/impl/static/index.mjs +688 -1393
  23. package/dist/impl/static/index.mjs.map +1 -1
  24. package/dist/{index-D-Fa4Smt.d.cts → index-B_j6u5E4.d.cts} +1 -1
  25. package/dist/{index-CD8BZz2k.d.ts → index-Dj0SEgk3.d.ts} +1 -1
  26. package/dist/index.d.cts +71 -63
  27. package/dist/index.d.ts +71 -63
  28. package/dist/index.js +1162 -1996
  29. package/dist/index.js.map +1 -1
  30. package/dist/index.mjs +1124 -1955
  31. package/dist/index.mjs.map +1 -1
  32. package/dist/pouch/index.js +3 -0
  33. package/dist/pouch/index.js.map +1 -1
  34. package/dist/pouch/index.mjs +3 -0
  35. package/dist/pouch/index.mjs.map +1 -1
  36. package/dist/{types-CzPDLAK6.d.cts → types-Bn0itutr.d.cts} +1 -1
  37. package/dist/{types-CewsN87z.d.ts → types-DQaXnuoc.d.ts} +1 -1
  38. package/dist/{types-legacy-6ettoclI.d.cts → types-legacy-DDY4N-Uq.d.cts} +3 -1
  39. package/dist/{types-legacy-6ettoclI.d.ts → types-legacy-DDY4N-Uq.d.ts} +3 -1
  40. package/dist/util/packer/index.d.cts +3 -3
  41. package/dist/util/packer/index.d.ts +3 -3
  42. package/docs/navigators-architecture.md +115 -17
  43. package/package.json +4 -4
  44. package/src/core/index.ts +1 -0
  45. package/src/core/interfaces/classroomDB.ts +5 -13
  46. package/src/core/interfaces/contentSource.ts +6 -66
  47. package/src/core/interfaces/courseDB.ts +15 -7
  48. package/src/core/interfaces/userDB.ts +32 -0
  49. package/src/core/navigators/Pipeline.ts +136 -52
  50. package/src/core/navigators/PipelineAssembler.ts +1 -1
  51. package/src/core/navigators/defaults.ts +84 -0
  52. package/src/core/navigators/{hierarchyDefinition.ts → filters/hierarchyDefinition.ts} +15 -29
  53. package/src/core/navigators/filters/index.ts +3 -0
  54. package/src/core/navigators/filters/inferredPreferenceStub.ts +107 -0
  55. package/src/core/navigators/{interferenceMitigator.ts → filters/interferenceMitigator.ts} +11 -37
  56. package/src/core/navigators/{relativePriority.ts → filters/relativePriority.ts} +12 -38
  57. package/src/core/navigators/filters/userGoalStub.ts +136 -0
  58. package/src/core/navigators/filters/userTagPreference.ts +217 -0
  59. package/src/core/navigators/{CompositeGenerator.ts → generators/CompositeGenerator.ts} +15 -64
  60. package/src/core/navigators/{elo.ts → generators/elo.ts} +13 -63
  61. package/src/core/navigators/{srs.ts → generators/srs.ts} +11 -40
  62. package/src/core/navigators/generators/types.ts +1 -1
  63. package/src/core/navigators/index.ts +95 -91
  64. package/src/core/types/strategyState.ts +84 -0
  65. package/src/core/types/types-legacy.ts +2 -0
  66. package/src/impl/common/BaseUserDB.ts +74 -7
  67. package/src/impl/couch/adminDB.ts +1 -2
  68. package/src/impl/couch/classroomDB.ts +100 -103
  69. package/src/impl/couch/courseDB.ts +35 -91
  70. package/src/impl/couch/pouchdb-setup.ts +7 -0
  71. package/src/impl/static/StaticDataUnpacker.ts +50 -1
  72. package/src/impl/static/courseDB.ts +87 -37
  73. package/src/study/SessionController.ts +122 -202
  74. package/src/study/SourceMixer.ts +65 -0
  75. package/src/study/TagFilteredContentSource.ts +49 -92
  76. package/src/study/index.ts +1 -0
  77. package/src/study/services/CardHydrationService.ts +165 -81
  78. package/src/util/dataDirectory.ts +1 -1
  79. package/src/util/index.ts +0 -1
  80. package/tests/core/navigators/CompositeGenerator.test.ts +44 -168
  81. package/tests/core/navigators/Pipeline.test.ts +6 -72
  82. package/tests/core/navigators/PipelineAssembler.test.ts +8 -58
  83. package/tests/core/navigators/navigators.test.ts +118 -151
  84. package/docs/todo-pipeline-optimization.md +0 -117
  85. package/docs/todo-strategy-state-storage.md +0 -278
  86. package/src/core/navigators/hardcodedOrder.ts +0 -163
  87. package/src/util/tuiLogger.ts +0 -139
@@ -1,8 +1,11 @@
1
- import { describe, it, expect } from 'vitest';
2
- import CompositeGenerator, { AggregationMode } from '../../../src/core/navigators/CompositeGenerator';
1
+ import { describe, it, expect, vi } from 'vitest';
2
+ import CompositeGenerator, {
3
+ AggregationMode,
4
+ } from '../../../src/core/navigators/generators/CompositeGenerator';
3
5
  import { ContentNavigator, WeightedCard } from '../../../src/core/navigators/index';
4
- import { StudySessionNewItem, StudySessionReviewItem } from '../../../src/core';
5
- import { ScheduledCard } from '../../../src/core/types/user';
6
+ import { GeneratorContext } from '../../../src/core/navigators/generators/types';
7
+ import { UserDBInterface } from '../../../src/core/interfaces/userDB';
8
+ import { CourseDBInterface } from '../../../src/core/interfaces/courseDB';
6
9
 
7
10
  // Test helper to create weighted cards with provenance
8
11
  function makeWeightedCard(
@@ -31,39 +34,45 @@ function makeWeightedCard(
31
34
 
32
35
  // Mock ContentNavigator for testing
33
36
  class MockGenerator extends ContentNavigator {
37
+ name: string = 'MockGenerator';
34
38
  private mockWeightedCards: WeightedCard[] = [];
35
- private mockNewCards: StudySessionNewItem[] = [];
36
- private mockReviews: (StudySessionReviewItem & ScheduledCard)[] = [];
37
39
 
38
40
  setWeightedCards(cards: WeightedCard[]) {
39
41
  this.mockWeightedCards = cards;
40
42
  }
41
43
 
42
- setNewCards(cards: StudySessionNewItem[]) {
43
- this.mockNewCards = cards;
44
- }
45
-
46
- setReviews(reviews: (StudySessionReviewItem & ScheduledCard)[]) {
47
- this.mockReviews = reviews;
48
- }
49
-
50
44
  async getWeightedCards(limit: number): Promise<WeightedCard[]> {
51
45
  return this.mockWeightedCards.slice(0, limit);
52
46
  }
47
+ }
53
48
 
54
- async getNewCards(n?: number): Promise<StudySessionNewItem[]> {
55
- return n ? this.mockNewCards.slice(0, n) : this.mockNewCards;
56
- }
49
+ // Create a mock context for tests
50
+ function createMockContext(): GeneratorContext {
51
+ const mockUser = {
52
+ getCourseRegDoc: vi.fn().mockResolvedValue({
53
+ elo: { global: { score: 1000, count: 10 }, tags: {} },
54
+ }),
55
+ } as unknown as UserDBInterface;
57
56
 
58
- async getPendingReviews(): Promise<(StudySessionReviewItem & ScheduledCard)[]> {
59
- return this.mockReviews;
60
- }
57
+ const mockCourse = {
58
+ getCourseID: vi.fn().mockReturnValue('test-course'),
59
+ } as unknown as CourseDBInterface;
60
+
61
+ return {
62
+ user: mockUser,
63
+ course: mockCourse,
64
+ userElo: 1000,
65
+ };
61
66
  }
62
67
 
63
68
  describe('CompositeGenerator', () => {
69
+ const mockContext = createMockContext();
70
+
64
71
  describe('constructor', () => {
65
72
  it('throws error when no generators provided', () => {
66
- expect(() => new CompositeGenerator([])).toThrow('CompositeGenerator requires at least one generator');
73
+ expect(() => new CompositeGenerator([])).toThrow(
74
+ 'CompositeGenerator requires at least one generator'
75
+ );
67
76
  });
68
77
 
69
78
  it('accepts single generator', () => {
@@ -87,7 +96,7 @@ describe('CompositeGenerator', () => {
87
96
  ]);
88
97
 
89
98
  const composite = new CompositeGenerator([generator]);
90
- const result = await composite.getWeightedCards(10);
99
+ const result = await composite.getWeightedCards(10, mockContext);
91
100
 
92
101
  expect(result).toHaveLength(2);
93
102
  expect(result[0].cardId).toBe('card-1');
@@ -105,7 +114,7 @@ describe('CompositeGenerator', () => {
105
114
  ]);
106
115
 
107
116
  const composite = new CompositeGenerator([generator]);
108
- const result = await composite.getWeightedCards(2);
117
+ const result = await composite.getWeightedCards(2, mockContext);
109
118
 
110
119
  expect(result).toHaveLength(2);
111
120
  expect(result[0].cardId).toBe('card-1');
@@ -128,7 +137,7 @@ describe('CompositeGenerator', () => {
128
137
  ]);
129
138
 
130
139
  const composite = new CompositeGenerator([gen1, gen2], AggregationMode.AVERAGE);
131
- const result = await composite.getWeightedCards(10);
140
+ const result = await composite.getWeightedCards(10, mockContext);
132
141
 
133
142
  // Should have 3 unique cards
134
143
  expect(result).toHaveLength(3);
@@ -148,7 +157,7 @@ describe('CompositeGenerator', () => {
148
157
  gen2.setWeightedCards([makeWeightedCard('card-1', 'course-1', 0.9, 'new')]);
149
158
 
150
159
  const composite = new CompositeGenerator([gen1, gen2], AggregationMode.MAX);
151
- const result = await composite.getWeightedCards(10);
160
+ const result = await composite.getWeightedCards(10, mockContext);
152
161
 
153
162
  expect(result).toHaveLength(1);
154
163
  expect(result[0].score).toBe(0.9);
@@ -164,7 +173,7 @@ describe('CompositeGenerator', () => {
164
173
  gen2.setWeightedCards([makeWeightedCard('card-1', 'course-1', 0.6, 'new')]);
165
174
 
166
175
  const composite = new CompositeGenerator([gen1, gen2], AggregationMode.AVERAGE);
167
- const result = await composite.getWeightedCards(10);
176
+ const result = await composite.getWeightedCards(10, mockContext);
168
177
 
169
178
  expect(result).toHaveLength(1);
170
179
  expect(result[0].score).toBeCloseTo(0.7); // (0.8 + 0.6) / 2
@@ -181,7 +190,7 @@ describe('CompositeGenerator', () => {
181
190
  gen3.setWeightedCards([makeWeightedCard('card-1', 'course-1', 0.6, 'new')]);
182
191
 
183
192
  const composite = new CompositeGenerator([gen1, gen2, gen3], AggregationMode.AVERAGE);
184
- const result = await composite.getWeightedCards(10);
193
+ const result = await composite.getWeightedCards(10, mockContext);
185
194
 
186
195
  expect(result).toHaveLength(1);
187
196
  expect(result[0].score).toBeCloseTo(0.7); // (0.9 + 0.6 + 0.6) / 3
@@ -197,7 +206,7 @@ describe('CompositeGenerator', () => {
197
206
  gen2.setWeightedCards([makeWeightedCard('card-1', 'course-1', 0.6, 'new')]);
198
207
 
199
208
  const composite = new CompositeGenerator([gen1, gen2]); // default mode
200
- const result = await composite.getWeightedCards(10);
209
+ const result = await composite.getWeightedCards(10, mockContext);
201
210
 
202
211
  expect(result).toHaveLength(1);
203
212
  // avg = (0.8 + 0.6) / 2 = 0.7
@@ -217,7 +226,7 @@ describe('CompositeGenerator', () => {
217
226
  gen3.setWeightedCards([makeWeightedCard('card-1', 'course-1', 0.6, 'new')]);
218
227
 
219
228
  const composite = new CompositeGenerator([gen1, gen2, gen3]);
220
- const result = await composite.getWeightedCards(10);
229
+ const result = await composite.getWeightedCards(10, mockContext);
221
230
 
222
231
  expect(result).toHaveLength(1);
223
232
  // avg = 0.6
@@ -234,7 +243,7 @@ describe('CompositeGenerator', () => {
234
243
  gen2.setWeightedCards([makeWeightedCard('card-2', 'course-1', 0.6, 'new')]);
235
244
 
236
245
  const composite = new CompositeGenerator([gen1, gen2]);
237
- const result = await composite.getWeightedCards(10);
246
+ const result = await composite.getWeightedCards(10, mockContext);
238
247
 
239
248
  expect(result).toHaveLength(2);
240
249
  // No boost for single-generator cards
@@ -254,7 +263,7 @@ describe('CompositeGenerator', () => {
254
263
  gen2.setWeightedCards([makeWeightedCard('card-1', 'course-1', 0.9, 'new')]);
255
264
 
256
265
  const composite = new CompositeGenerator([gen1, gen2]);
257
- const result = await composite.getWeightedCards(10);
266
+ const result = await composite.getWeightedCards(10, mockContext);
258
267
 
259
268
  expect(result).toHaveLength(1);
260
269
  // avg = 0.9, boost = 1.1, result = 0.99 (would be 0.99, not clamped)
@@ -273,7 +282,7 @@ describe('CompositeGenerator', () => {
273
282
  gen3.setWeightedCards([makeWeightedCard('card-1', 'course-1', 1.0, 'new')]);
274
283
 
275
284
  const composite = new CompositeGenerator([gen1, gen2, gen3]);
276
- const result = await composite.getWeightedCards(10);
285
+ const result = await composite.getWeightedCards(10, mockContext);
277
286
 
278
287
  expect(result).toHaveLength(1);
279
288
  // avg = 1.0, boost = 1.2, result would be 1.2 but clamped to 1.0
@@ -291,7 +300,7 @@ describe('CompositeGenerator', () => {
291
300
  ]);
292
301
 
293
302
  const composite = new CompositeGenerator([gen1]);
294
- const result = await composite.getWeightedCards(10);
303
+ const result = await composite.getWeightedCards(10, mockContext);
295
304
 
296
305
  expect(result).toHaveLength(3);
297
306
  expect(result[0].cardId).toBe('card-high');
@@ -307,12 +316,10 @@ describe('CompositeGenerator', () => {
307
316
  ]);
308
317
 
309
318
  const gen2 = new MockGenerator();
310
- gen2.setWeightedCards([
311
- makeWeightedCard('card-boosted', 'course-1', 0.5, 'new'),
312
- ]);
319
+ gen2.setWeightedCards([makeWeightedCard('card-boosted', 'course-1', 0.5, 'new')]);
313
320
 
314
321
  const composite = new CompositeGenerator([gen1, gen2]);
315
- const result = await composite.getWeightedCards(10);
322
+ const result = await composite.getWeightedCards(10, mockContext);
316
323
 
317
324
  expect(result).toHaveLength(2);
318
325
  // card-boosted: avg=0.5, boost=1.1, final=0.55
@@ -321,135 +328,4 @@ describe('CompositeGenerator', () => {
321
328
  expect(result[1].cardId).toBe('card-boosted');
322
329
  });
323
330
  });
324
-
325
- describe('getNewCards', () => {
326
- it('deduplicates new cards by cardID', async () => {
327
- const gen1 = new MockGenerator();
328
- gen1.setNewCards([
329
- {
330
- cardID: 'card-1',
331
- courseID: 'course-1',
332
- contentSourceType: 'course',
333
- contentSourceID: 'course-1',
334
- status: 'new',
335
- },
336
- {
337
- cardID: 'card-2',
338
- courseID: 'course-1',
339
- contentSourceType: 'course',
340
- contentSourceID: 'course-1',
341
- status: 'new',
342
- },
343
- ]);
344
-
345
- const gen2 = new MockGenerator();
346
- gen2.setNewCards([
347
- {
348
- cardID: 'card-1',
349
- courseID: 'course-1',
350
- contentSourceType: 'course',
351
- contentSourceID: 'course-1',
352
- status: 'new',
353
- },
354
- {
355
- cardID: 'card-3',
356
- courseID: 'course-1',
357
- contentSourceType: 'course',
358
- contentSourceID: 'course-1',
359
- status: 'new',
360
- },
361
- ]);
362
-
363
- const composite = new CompositeGenerator([gen1, gen2]);
364
- const result = await composite.getNewCards();
365
-
366
- expect(result).toHaveLength(3);
367
- const cardIds = result.map((c) => c.cardID);
368
- expect(cardIds).toContain('card-1');
369
- expect(cardIds).toContain('card-2');
370
- expect(cardIds).toContain('card-3');
371
- });
372
-
373
- it('respects limit parameter', async () => {
374
- const gen1 = new MockGenerator();
375
- gen1.setNewCards([
376
- {
377
- cardID: 'card-1',
378
- courseID: 'course-1',
379
- contentSourceType: 'course',
380
- contentSourceID: 'course-1',
381
- status: 'new',
382
- },
383
- {
384
- cardID: 'card-2',
385
- courseID: 'course-1',
386
- contentSourceType: 'course',
387
- contentSourceID: 'course-1',
388
- status: 'new',
389
- },
390
- {
391
- cardID: 'card-3',
392
- courseID: 'course-1',
393
- contentSourceType: 'course',
394
- contentSourceID: 'course-1',
395
- status: 'new',
396
- },
397
- ]);
398
-
399
- const composite = new CompositeGenerator([gen1]);
400
- const result = await composite.getNewCards(2);
401
-
402
- expect(result).toHaveLength(2);
403
- });
404
- });
405
-
406
- describe('getPendingReviews', () => {
407
- it('deduplicates reviews by cardID', async () => {
408
- const review1: StudySessionReviewItem & ScheduledCard = {
409
- cardID: 'card-1',
410
- courseID: 'course-1',
411
- contentSourceType: 'course',
412
- contentSourceID: 'course-1',
413
- status: 'review',
414
- reviewID: 'review-1',
415
- _id: 'scheduled-1',
416
- cardId: 'card-1',
417
- courseId: 'course-1',
418
- scheduledFor: 'course',
419
- schedulingAgentId: 'agent-1',
420
- reviewTime: new Date(),
421
- scheduledAt: new Date(),
422
- } as unknown as StudySessionReviewItem & ScheduledCard;
423
-
424
- const review2: StudySessionReviewItem & ScheduledCard = {
425
- cardID: 'card-2',
426
- courseID: 'course-1',
427
- contentSourceType: 'course',
428
- contentSourceID: 'course-1',
429
- status: 'review',
430
- reviewID: 'review-2',
431
- _id: 'scheduled-2',
432
- cardId: 'card-2',
433
- courseId: 'course-1',
434
- scheduledFor: 'course',
435
- schedulingAgentId: 'agent-1',
436
- reviewTime: new Date(),
437
- scheduledAt: new Date(),
438
- } as unknown as StudySessionReviewItem & ScheduledCard;
439
-
440
- const gen1 = new MockGenerator();
441
- gen1.setReviews([review1, review2]);
442
-
443
- const gen2 = new MockGenerator();
444
- gen2.setReviews([review1]); // Duplicate review1
445
-
446
- const composite = new CompositeGenerator([gen1, gen2]);
447
- const result = await composite.getPendingReviews();
448
-
449
- expect(result).toHaveLength(2);
450
- const cardIds = result.map((c) => c.cardID);
451
- expect(cardIds).toContain('card-1');
452
- expect(cardIds).toContain('card-2');
453
- });
454
- });
455
331
  });
@@ -2,8 +2,7 @@ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
2
  import { Pipeline } from '../../../src/core/navigators/Pipeline';
3
3
  import { WeightedCard, ContentNavigator } from '../../../src/core/navigators/index';
4
4
  import { CardFilter, FilterContext } from '../../../src/core/navigators/filters/types';
5
- import { StudySessionNewItem, StudySessionReviewItem } from '../../../src/core/interfaces/contentSource';
6
- import { ScheduledCard } from '../../../src/core/types/user';
5
+
7
6
  import { CourseDBInterface } from '../../../src/core/interfaces/courseDB';
8
7
  import { UserDBInterface } from '../../../src/core/interfaces/userDB';
9
8
 
@@ -15,6 +14,7 @@ import { UserDBInterface } from '../../../src/core/interfaces/userDB';
15
14
  * Mock generator that returns predefined weighted cards
16
15
  */
17
16
  class MockGenerator extends ContentNavigator {
17
+ name: string = 'MockGenerator';
18
18
  private cards: WeightedCard[];
19
19
 
20
20
  constructor(cards: WeightedCard[]) {
@@ -25,14 +25,6 @@ class MockGenerator extends ContentNavigator {
25
25
  async getWeightedCards(limit: number): Promise<WeightedCard[]> {
26
26
  return this.cards.slice(0, limit);
27
27
  }
28
-
29
- async getNewCards(_n?: number): Promise<StudySessionNewItem[]> {
30
- return [];
31
- }
32
-
33
- async getPendingReviews(): Promise<(StudySessionReviewItem & ScheduledCard)[]> {
34
- return [];
35
- }
36
28
  }
37
29
 
38
30
  /**
@@ -102,6 +94,8 @@ function createMockContext(): { user: UserDBInterface; course: CourseDBInterface
102
94
 
103
95
  const mockCourse = {
104
96
  getCourseID: vi.fn().mockReturnValue('test-course'),
97
+ getCourseConfig: vi.fn().mockResolvedValue({ name: 'Test Course', id: 'test-course' }),
98
+ getAppliedTagsBatch: vi.fn().mockResolvedValue(new Map()),
105
99
  } as unknown as CourseDBInterface;
106
100
 
107
101
  return { user: mockUser, course: mockCourse };
@@ -156,11 +150,7 @@ describe('Pipeline', () => {
156
150
  });
157
151
 
158
152
  it('should sort cards by score descending', async () => {
159
- const cards = [
160
- createCard('low', 0.3),
161
- createCard('high', 0.9),
162
- createCard('mid', 0.6),
163
- ];
153
+ const cards = [createCard('low', 0.3), createCard('high', 0.9), createCard('mid', 0.6)];
164
154
  const generator = new MockGenerator(cards);
165
155
  const pipeline = new Pipeline(generator, [], mockUser, mockCourse);
166
156
 
@@ -225,10 +215,7 @@ describe('Pipeline', () => {
225
215
  });
226
216
 
227
217
  it('should remove zero-score cards after filtering', async () => {
228
- const cards = [
229
- createCard('keep', 0.8),
230
- createCard('block', 0.9),
231
- ];
218
+ const cards = [createCard('keep', 0.8), createCard('block', 0.9)];
232
219
  const generator = new MockGenerator(cards);
233
220
  const filter = createBlockingFilter('Blocker', ['block']);
234
221
  const pipeline = new Pipeline(generator, [filter], mockUser, mockCourse);
@@ -325,59 +312,6 @@ describe('Pipeline', () => {
325
312
  });
326
313
  });
327
314
 
328
- describe('legacy API compatibility', () => {
329
- it('should delegate getNewCards to generator', async () => {
330
- const mockNewCards: StudySessionNewItem[] = [
331
- {
332
- cardID: 'new-1',
333
- courseID: 'test-course',
334
- contentSourceType: 'course',
335
- contentSourceID: 'test-course',
336
- status: 'new',
337
- },
338
- ];
339
-
340
- const generator = new MockGenerator([]);
341
- generator.getNewCards = vi.fn().mockResolvedValue(mockNewCards);
342
-
343
- const pipeline = new Pipeline(generator, [], mockUser, mockCourse);
344
- const result = await pipeline.getNewCards(10);
345
-
346
- expect(generator.getNewCards).toHaveBeenCalledWith(10);
347
- expect(result).toEqual(mockNewCards);
348
- });
349
-
350
- it('should delegate getPendingReviews to generator', async () => {
351
- const mockReviews: (StudySessionReviewItem & ScheduledCard)[] = [
352
- {
353
- cardID: 'review-1',
354
- courseID: 'test-course',
355
- contentSourceType: 'course',
356
- contentSourceID: 'test-course',
357
- status: 'review',
358
- qualifiedID: 'test-course-review-1',
359
- reviewID: 'SCHEDULED_CARD-review-1',
360
- _id: 'SCHEDULED_CARD-review-1',
361
- cardId: 'review-1',
362
- courseId: 'test-course',
363
- reviewTime: '2024-01-15T12:00:00Z',
364
- scheduledAt: '2024-01-14T12:00:00Z',
365
- scheduledFor: 'course',
366
- schedulingAgentId: 'test-course',
367
- },
368
- ];
369
-
370
- const generator = new MockGenerator([]);
371
- generator.getPendingReviews = vi.fn().mockResolvedValue(mockReviews);
372
-
373
- const pipeline = new Pipeline(generator, [], mockUser, mockCourse);
374
- const result = await pipeline.getPendingReviews();
375
-
376
- expect(generator.getPendingReviews).toHaveBeenCalled();
377
- expect(result).toEqual(mockReviews);
378
- });
379
- });
380
-
381
315
  describe('over-fetching', () => {
382
316
  it('should over-fetch from generator based on filter count', async () => {
383
317
  const manyCards = Array.from({ length: 20 }, (_, i) =>
@@ -8,7 +8,7 @@ import { DocType } from '../../../src/core';
8
8
  import { Pipeline } from '../../../src/core/navigators/Pipeline';
9
9
 
10
10
  // Mock the dynamic import in ContentNavigator.create
11
- vi.mock('../../../src/core/navigators/elo', () => ({
11
+ vi.mock('../../../src/core/navigators/generators/elo', () => ({
12
12
  default: class MockELONavigator {
13
13
  name = 'ELO';
14
14
  constructor(
@@ -19,16 +19,10 @@ vi.mock('../../../src/core/navigators/elo', () => ({
19
19
  async getWeightedCards() {
20
20
  return [];
21
21
  }
22
- async getNewCards() {
23
- return [];
24
- }
25
- async getPendingReviews() {
26
- return [];
27
- }
28
22
  },
29
23
  }));
30
24
 
31
- vi.mock('../../../src/core/navigators/srs', () => ({
25
+ vi.mock('../../../src/core/navigators/generators/srs', () => ({
32
26
  default: class MockSRSNavigator {
33
27
  name = 'SRS';
34
28
  constructor(
@@ -39,36 +33,10 @@ vi.mock('../../../src/core/navigators/srs', () => ({
39
33
  async getWeightedCards() {
40
34
  return [];
41
35
  }
42
- async getNewCards() {
43
- return [];
44
- }
45
- async getPendingReviews() {
46
- return [];
47
- }
48
- },
49
- }));
50
-
51
- vi.mock('../../../src/core/navigators/hardcodedOrder', () => ({
52
- default: class MockHardcodedOrderNavigator {
53
- name = 'Hardcoded Order';
54
- constructor(
55
- public user: any,
56
- public course: any,
57
- public strategyData: any
58
- ) {}
59
- async getWeightedCards() {
60
- return [];
61
- }
62
- async getNewCards() {
63
- return [];
64
- }
65
- async getPendingReviews() {
66
- return [];
67
- }
68
36
  },
69
37
  }));
70
38
 
71
- vi.mock('../../../src/core/navigators/hierarchyDefinition', () => ({
39
+ vi.mock('../../../src/core/navigators/filters/hierarchyDefinition', () => ({
72
40
  default: class MockHierarchyDefinitionNavigator {
73
41
  name = 'Hierarchy Definition';
74
42
  constructor(
@@ -84,16 +52,10 @@ vi.mock('../../../src/core/navigators/hierarchyDefinition', () => ({
84
52
  async getWeightedCards() {
85
53
  throw new Error('Filter should not be used as generator');
86
54
  }
87
- async getNewCards() {
88
- return [];
89
- }
90
- async getPendingReviews() {
91
- return [];
92
- }
93
55
  },
94
56
  }));
95
57
 
96
- vi.mock('../../../src/core/navigators/interferenceMitigator', () => ({
58
+ vi.mock('../../../src/core/navigators/filters/interferenceMitigator', () => ({
97
59
  default: class MockInterferenceMitigatorNavigator {
98
60
  name = 'Interference Mitigator';
99
61
  constructor(
@@ -109,16 +71,10 @@ vi.mock('../../../src/core/navigators/interferenceMitigator', () => ({
109
71
  async getWeightedCards() {
110
72
  throw new Error('Filter should not be used as generator');
111
73
  }
112
- async getNewCards() {
113
- return [];
114
- }
115
- async getPendingReviews() {
116
- return [];
117
- }
118
74
  },
119
75
  }));
120
76
 
121
- vi.mock('../../../src/core/navigators/relativePriority', () => ({
77
+ vi.mock('../../../src/core/navigators/filters/relativePriority', () => ({
122
78
  default: class MockRelativePriorityNavigator {
123
79
  name = 'Relative Priority';
124
80
  constructor(
@@ -134,12 +90,6 @@ vi.mock('../../../src/core/navigators/relativePriority', () => ({
134
90
  async getWeightedCards() {
135
91
  throw new Error('Filter should not be used as generator');
136
92
  }
137
- async getNewCards() {
138
- return [];
139
- }
140
- async getPendingReviews() {
141
- return [];
142
- }
143
93
  },
144
94
  }));
145
95
 
@@ -157,6 +107,7 @@ describe('PipelineAssembler', () => {
157
107
 
158
108
  const mockCourse = {
159
109
  getCourseID: vi.fn().mockReturnValue('test-course'),
110
+ getCourseConfig: vi.fn().mockResolvedValue({ name: 'Test Course', id: 'test-course' }),
160
111
  getCardEloData: vi.fn().mockResolvedValue([]),
161
112
  getAppliedTags: vi.fn().mockResolvedValue({ rows: [] }),
162
113
  };
@@ -305,15 +256,14 @@ describe('PipelineAssembler', () => {
305
256
  describe('complex scenarios', () => {
306
257
  it('handles multiple generators and multiple filters', async () => {
307
258
  const elo = createStrategy('elo', 'elo');
308
- const hardcoded = createStrategy('hardcoded', 'hardcodedOrder');
309
259
  const hierarchy = createStrategy('hierarchy', 'hierarchyDefinition');
310
260
  const relativePriority = createStrategy('priority', 'relativePriority');
311
261
 
312
- const input = createInput([elo, hardcoded, hierarchy, relativePriority]);
262
+ const input = createInput([elo, hierarchy, relativePriority]);
313
263
  const result = await assembler.assemble(input);
314
264
 
315
265
  // Should have both generators
316
- expect(result.generatorStrategies).toEqual([elo, hardcoded]);
266
+ expect(result.generatorStrategies).toEqual([elo]);
317
267
 
318
268
  // Should have both filters (sorted alphabetically)
319
269
  expect(result.filterStrategies.map((f) => f.name)).toEqual(['hierarchy', 'priority']);