@vue-skuilder/db 0.1.11 → 0.1.12

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 (53) hide show
  1. package/dist/core/index.d.mts +7 -6
  2. package/dist/core/index.d.ts +7 -6
  3. package/dist/core/index.js +146 -37
  4. package/dist/core/index.js.map +1 -1
  5. package/dist/core/index.mjs +146 -37
  6. package/dist/core/index.mjs.map +1 -1
  7. package/dist/{dataLayerProvider-VieuAAkV.d.mts → dataLayerProvider-BiP3kWix.d.mts} +1 -1
  8. package/dist/{dataLayerProvider-juuqUHOP.d.ts → dataLayerProvider-DSdeyRT3.d.ts} +1 -1
  9. package/dist/impl/couch/index.d.mts +3 -3
  10. package/dist/impl/couch/index.d.ts +3 -3
  11. package/dist/impl/couch/index.js +146 -37
  12. package/dist/impl/couch/index.js.map +1 -1
  13. package/dist/impl/couch/index.mjs +146 -37
  14. package/dist/impl/couch/index.mjs.map +1 -1
  15. package/dist/impl/static/index.d.mts +14 -6
  16. package/dist/impl/static/index.d.ts +14 -6
  17. package/dist/impl/static/index.js +147 -39
  18. package/dist/impl/static/index.js.map +1 -1
  19. package/dist/impl/static/index.mjs +147 -39
  20. package/dist/impl/static/index.mjs.map +1 -1
  21. package/dist/{index-DZyxHCcf.d.mts → index-Bmll7Xse.d.mts} +1 -1
  22. package/dist/{index-CWY6yhkV.d.ts → index-CD8BZz2k.d.ts} +1 -1
  23. package/dist/index.d.mts +119 -24
  24. package/dist/index.d.ts +119 -24
  25. package/dist/index.js +785 -261
  26. package/dist/index.js.map +1 -1
  27. package/dist/index.mjs +789 -265
  28. package/dist/index.mjs.map +1 -1
  29. package/dist/{types-DtoI27Xh.d.ts → types-CewsN87z.d.ts} +1 -1
  30. package/dist/{types-Che4wTwA.d.mts → types-Dbp5DaRR.d.mts} +1 -1
  31. package/dist/{types-legacy-B8ahaCbj.d.mts → types-legacy-6ettoclI.d.mts} +13 -2
  32. package/dist/{types-legacy-B8ahaCbj.d.ts → types-legacy-6ettoclI.d.ts} +13 -2
  33. package/dist/{userDB-DJ8HMw83.d.mts → userDB-C4yyAnpp.d.mts} +3 -3
  34. package/dist/{userDB-B7zTQ123.d.ts → userDB-CD6s6ZCp.d.ts} +3 -3
  35. package/dist/util/packer/index.d.mts +3 -3
  36. package/dist/util/packer/index.d.ts +3 -3
  37. package/package.json +3 -3
  38. package/src/core/navigators/hardcodedOrder.ts +64 -0
  39. package/src/core/navigators/index.ts +1 -0
  40. package/src/core/types/contentNavigationStrategy.ts +2 -1
  41. package/src/core/types/types-legacy.ts +2 -2
  42. package/src/impl/common/BaseUserDB.ts +15 -11
  43. package/src/impl/couch/courseDB.ts +74 -27
  44. package/src/impl/couch/updateQueue.ts +8 -3
  45. package/src/impl/static/StaticDataLayerProvider.ts +57 -17
  46. package/src/impl/static/courseDB.ts +17 -12
  47. package/src/impl/static/coursesDB.ts +10 -6
  48. package/src/study/ItemQueue.ts +58 -0
  49. package/src/study/SessionController.ts +132 -178
  50. package/src/study/services/CardHydrationService.ts +153 -0
  51. package/src/study/services/EloService.ts +85 -0
  52. package/src/study/services/ResponseProcessor.ts +224 -0
  53. package/src/study/services/SrsService.ts +44 -0
@@ -1,5 +1,5 @@
1
1
  import { CourseConfig } from '@vue-skuilder/common';
2
- import { D as DocType } from './types-legacy-B8ahaCbj.js';
2
+ import { D as DocType } from './types-legacy-6ettoclI.js';
3
3
 
4
4
  interface StaticCourseManifest {
5
5
  version: string;
@@ -1,5 +1,5 @@
1
1
  import { CourseConfig } from '@vue-skuilder/common';
2
- import { D as DocType } from './types-legacy-B8ahaCbj.mjs';
2
+ import { D as DocType } from './types-legacy-6ettoclI.mjs';
3
3
 
4
4
  interface StaticCourseManifest {
5
5
  version: string;
@@ -78,7 +78,18 @@ interface QuestionData extends SkuilderCourseData {
78
78
  viewList: string[];
79
79
  dataShapeList: PouchDB.Core.DocumentId[];
80
80
  }
81
- declare const DocTypePrefixes: Record<string, string>;
81
+ declare const DocTypePrefixes: {
82
+ readonly CARD: "c";
83
+ readonly DISPLAYABLE_DATA: "dd";
84
+ readonly TAG: "TAG";
85
+ readonly CARDRECORD: "cardH";
86
+ readonly SCHEDULED_CARD: "card_review_";
87
+ readonly DATASHAPE: "DATASHAPE";
88
+ readonly QUESTION: "QUESTION";
89
+ readonly VIEW: "VIEW";
90
+ readonly PEDAGOGY: "PEDAGOGY";
91
+ readonly NAVIGATION_STRATEGY: "NAVIGATION_STRATEGY";
92
+ };
82
93
  interface CardHistory<T extends CardRecord> {
83
94
  _id: PouchDB.Core.DocumentId;
84
95
  /**
@@ -140,4 +151,4 @@ interface QuestionRecord extends CardRecord, Evaluation {
140
151
  priorAttemps: number;
141
152
  }
142
153
 
143
- export { type CardRecord as C, DocType as D, type Field as F, GuestUsername as G, type QualifiedCardID as Q, type SkuilderCourseData as S, type TagStub as T, type Tag as a, type CardData as b, type CourseListData as c, type DisplayableData as d, type DataShapeData as e, type QuestionData as f, DocTypePrefixes as g, type CardHistory as h, type QuestionRecord as i, log as l };
154
+ export { type CardHistory as C, DocType as D, type Field as F, GuestUsername as G, type QualifiedCardID as Q, type SkuilderCourseData as S, type TagStub as T, type Tag as a, DocTypePrefixes as b, type CardRecord as c, type CardData as d, type CourseListData as e, type DisplayableData as f, type DataShapeData as g, type QuestionData as h, type QuestionRecord as i, log as l };
@@ -78,7 +78,18 @@ interface QuestionData extends SkuilderCourseData {
78
78
  viewList: string[];
79
79
  dataShapeList: PouchDB.Core.DocumentId[];
80
80
  }
81
- declare const DocTypePrefixes: Record<string, string>;
81
+ declare const DocTypePrefixes: {
82
+ readonly CARD: "c";
83
+ readonly DISPLAYABLE_DATA: "dd";
84
+ readonly TAG: "TAG";
85
+ readonly CARDRECORD: "cardH";
86
+ readonly SCHEDULED_CARD: "card_review_";
87
+ readonly DATASHAPE: "DATASHAPE";
88
+ readonly QUESTION: "QUESTION";
89
+ readonly VIEW: "VIEW";
90
+ readonly PEDAGOGY: "PEDAGOGY";
91
+ readonly NAVIGATION_STRATEGY: "NAVIGATION_STRATEGY";
92
+ };
82
93
  interface CardHistory<T extends CardRecord> {
83
94
  _id: PouchDB.Core.DocumentId;
84
95
  /**
@@ -140,4 +151,4 @@ interface QuestionRecord extends CardRecord, Evaluation {
140
151
  priorAttemps: number;
141
152
  }
142
153
 
143
- export { type CardRecord as C, DocType as D, type Field as F, GuestUsername as G, type QualifiedCardID as Q, type SkuilderCourseData as S, type TagStub as T, type Tag as a, type CardData as b, type CourseListData as c, type DisplayableData as d, type DataShapeData as e, type QuestionData as f, DocTypePrefixes as g, type CardHistory as h, type QuestionRecord as i, log as l };
154
+ export { type CardHistory as C, DocType as D, type Field as F, GuestUsername as G, type QualifiedCardID as Q, type SkuilderCourseData as S, type TagStub as T, type Tag as a, DocTypePrefixes as b, type CardRecord as c, type CardData as d, type CourseListData as e, type DisplayableData as f, type DataShapeData as g, type QuestionData as h, type QuestionRecord as i, log as l };
@@ -1,6 +1,6 @@
1
1
  import { CourseConfig, ClassroomConfig, CourseElo, Status, SkuilderCourseData as SkuilderCourseData$1, DataShape } from '@vue-skuilder/common';
2
2
  import { Moment } from 'moment';
3
- import { S as SkuilderCourseData, D as DocType, Q as QualifiedCardID, T as TagStub, a as Tag, h as CardHistory, C as CardRecord } from './types-legacy-B8ahaCbj.mjs';
3
+ import { S as SkuilderCourseData, b as DocTypePrefixes, D as DocType, Q as QualifiedCardID, T as TagStub, a as Tag, C as CardHistory, c as CardRecord } from './types-legacy-6ettoclI.mjs';
4
4
 
5
5
  /**
6
6
  * Admin functionality
@@ -199,7 +199,7 @@ interface DataLayerResult {
199
199
  *
200
200
  */
201
201
  interface ContentNavigationStrategyData extends SkuilderCourseData {
202
- id: string;
202
+ _id: `${typeof DocTypePrefixes[DocType.NAVIGATION_STRATEGY]}-${string}`;
203
203
  docType: DocType.NAVIGATION_STRATEGY;
204
204
  name: string;
205
205
  description: string;
@@ -530,4 +530,4 @@ interface ClassroomRegistrationDoc {
530
530
  registrations: ClassroomRegistration[];
531
531
  }
532
532
 
533
- export { type AdminDBInterface as A, type ClassroomRegistration as B, type CourseDBInterface as C, type DataLayerResult as D, type ClassroomRegistrationDoc as E, type UserConfig as F, type ActivityRecord as G, type CourseRegistration as H, type CourseRegistrationDoc as I, type DocumentUpdater as J, newInterval as K, type StudySessionNewItem as S, type TeacherClassroomDBInterface as T, type UserDBInterface as U, type UserDBReader as a, type CoursesDBInterface as b, type ClassroomDBInterface as c, type CourseInfo as d, type ContentNavigationStrategyData as e, type StudySessionReviewItem as f, type ScheduledCard as g, type AssignedContent as h, type StudyContentSource as i, type StudentClassroomDBInterface as j, type StudySessionItem as k, type StudySessionFailedItem as l, type StudySessionFailedNewItem as m, type StudySessionFailedReviewItem as n, isReview as o, type ContentSourceID as p, getStudySource as q, type AssignedTag as r, type AssignedCourse as s, type AssignedCard as t, type UserDBWriter as u, type UserDBAuthenticator as v, type UserCourseSettings as w, type UserCourseSetting as x, type UsrCrsDataInterface as y, type ClassroomRegistrationDesignation as z };
533
+ export { type AdminDBInterface as A, type ClassroomRegistrationDesignation as B, type CourseDBInterface as C, type DataLayerResult as D, type ClassroomRegistration as E, type ClassroomRegistrationDoc as F, type UserConfig as G, type ActivityRecord as H, type CourseRegistration as I, type DocumentUpdater as J, newInterval as K, type StudySessionNewItem as S, type TeacherClassroomDBInterface as T, type UserDBInterface as U, type UserDBReader as a, type CoursesDBInterface as b, type ClassroomDBInterface as c, type CourseInfo as d, type ContentNavigationStrategyData as e, type StudySessionReviewItem as f, type ScheduledCard as g, type AssignedContent as h, type StudyContentSource as i, type StudentClassroomDBInterface as j, type StudySessionItem as k, type StudySessionFailedItem as l, type StudySessionFailedNewItem as m, type StudySessionFailedReviewItem as n, isReview as o, type ContentSourceID as p, getStudySource as q, type CourseRegistrationDoc as r, type AssignedTag as s, type AssignedCourse as t, type AssignedCard as u, type UserDBWriter as v, type UserDBAuthenticator as w, type UserCourseSettings as x, type UserCourseSetting as y, type UsrCrsDataInterface as z };
@@ -1,6 +1,6 @@
1
1
  import { CourseConfig, ClassroomConfig, CourseElo, Status, SkuilderCourseData as SkuilderCourseData$1, DataShape } from '@vue-skuilder/common';
2
2
  import { Moment } from 'moment';
3
- import { S as SkuilderCourseData, D as DocType, Q as QualifiedCardID, T as TagStub, a as Tag, h as CardHistory, C as CardRecord } from './types-legacy-B8ahaCbj.js';
3
+ import { S as SkuilderCourseData, b as DocTypePrefixes, D as DocType, Q as QualifiedCardID, T as TagStub, a as Tag, C as CardHistory, c as CardRecord } from './types-legacy-6ettoclI.js';
4
4
 
5
5
  /**
6
6
  * Admin functionality
@@ -199,7 +199,7 @@ interface DataLayerResult {
199
199
  *
200
200
  */
201
201
  interface ContentNavigationStrategyData extends SkuilderCourseData {
202
- id: string;
202
+ _id: `${typeof DocTypePrefixes[DocType.NAVIGATION_STRATEGY]}-${string}`;
203
203
  docType: DocType.NAVIGATION_STRATEGY;
204
204
  name: string;
205
205
  description: string;
@@ -530,4 +530,4 @@ interface ClassroomRegistrationDoc {
530
530
  registrations: ClassroomRegistration[];
531
531
  }
532
532
 
533
- export { type AdminDBInterface as A, type ClassroomRegistration as B, type CourseDBInterface as C, type DataLayerResult as D, type ClassroomRegistrationDoc as E, type UserConfig as F, type ActivityRecord as G, type CourseRegistration as H, type CourseRegistrationDoc as I, type DocumentUpdater as J, newInterval as K, type StudySessionNewItem as S, type TeacherClassroomDBInterface as T, type UserDBInterface as U, type UserDBReader as a, type CoursesDBInterface as b, type ClassroomDBInterface as c, type CourseInfo as d, type ContentNavigationStrategyData as e, type StudySessionReviewItem as f, type ScheduledCard as g, type AssignedContent as h, type StudyContentSource as i, type StudentClassroomDBInterface as j, type StudySessionItem as k, type StudySessionFailedItem as l, type StudySessionFailedNewItem as m, type StudySessionFailedReviewItem as n, isReview as o, type ContentSourceID as p, getStudySource as q, type AssignedTag as r, type AssignedCourse as s, type AssignedCard as t, type UserDBWriter as u, type UserDBAuthenticator as v, type UserCourseSettings as w, type UserCourseSetting as x, type UsrCrsDataInterface as y, type ClassroomRegistrationDesignation as z };
533
+ export { type AdminDBInterface as A, type ClassroomRegistrationDesignation as B, type CourseDBInterface as C, type DataLayerResult as D, type ClassroomRegistration as E, type ClassroomRegistrationDoc as F, type UserConfig as G, type ActivityRecord as H, type CourseRegistration as I, type DocumentUpdater as J, newInterval as K, type StudySessionNewItem as S, type TeacherClassroomDBInterface as T, type UserDBInterface as U, type UserDBReader as a, type CoursesDBInterface as b, type ClassroomDBInterface as c, type CourseInfo as d, type ContentNavigationStrategyData as e, type StudySessionReviewItem as f, type ScheduledCard as g, type AssignedContent as h, type StudyContentSource as i, type StudentClassroomDBInterface as j, type StudySessionItem as k, type StudySessionFailedItem as l, type StudySessionFailedNewItem as m, type StudySessionFailedReviewItem as n, isReview as o, type ContentSourceID as p, getStudySource as q, type CourseRegistrationDoc as r, type AssignedTag as s, type AssignedCourse as t, type AssignedCard as u, type UserDBWriter as v, type UserDBAuthenticator as w, type UserCourseSettings as x, type UserCourseSetting as y, type UsrCrsDataInterface as z };
@@ -1,5 +1,5 @@
1
- export { A as AttachmentData, C as ChunkMetadata, D as DesignDocument, I as IndexMetadata, a as PackedCourseData, P as PackerConfig, S as StaticCourseManifest } from '../../types-Che4wTwA.mjs';
2
- export { C as CouchDBToStaticPacker } from '../../index-DZyxHCcf.mjs';
1
+ export { A as AttachmentData, C as ChunkMetadata, D as DesignDocument, I as IndexMetadata, a as PackedCourseData, P as PackerConfig, S as StaticCourseManifest } from '../../types-Dbp5DaRR.mjs';
2
+ export { C as CouchDBToStaticPacker } from '../../index-Bmll7Xse.mjs';
3
3
  import '@vue-skuilder/common';
4
- import '../../types-legacy-B8ahaCbj.mjs';
4
+ import '../../types-legacy-6ettoclI.mjs';
5
5
  import 'moment';
@@ -1,5 +1,5 @@
1
- export { A as AttachmentData, C as ChunkMetadata, D as DesignDocument, I as IndexMetadata, a as PackedCourseData, P as PackerConfig, S as StaticCourseManifest } from '../../types-DtoI27Xh.js';
2
- export { C as CouchDBToStaticPacker } from '../../index-CWY6yhkV.js';
1
+ export { A as AttachmentData, C as ChunkMetadata, D as DesignDocument, I as IndexMetadata, a as PackedCourseData, P as PackerConfig, S as StaticCourseManifest } from '../../types-CewsN87z.js';
2
+ export { C as CouchDBToStaticPacker } from '../../index-CD8BZz2k.js';
3
3
  import '@vue-skuilder/common';
4
- import '../../types-legacy-B8ahaCbj.js';
4
+ import '../../types-legacy-6ettoclI.js';
5
5
  import 'moment';
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "0.1.11",
6
+ "version": "0.1.12",
7
7
  "description": "Database layer for vue-skuilder",
8
8
  "main": "dist/index.js",
9
9
  "module": "dist/index.mjs",
@@ -45,7 +45,7 @@
45
45
  },
46
46
  "dependencies": {
47
47
  "@nilock2/pouchdb-authentication": "^1.0.2",
48
- "@vue-skuilder/common": "0.1.11",
48
+ "@vue-skuilder/common": "0.1.12",
49
49
  "cross-fetch": "^4.1.0",
50
50
  "moment": "^2.29.4",
51
51
  "pouchdb": "^9.0.0",
@@ -57,5 +57,5 @@
57
57
  "tsup": "^8.0.2",
58
58
  "typescript": "~5.7.2"
59
59
  },
60
- "stableVersion": "0.1.11"
60
+ "stableVersion": "0.1.12"
61
61
  }
@@ -0,0 +1,64 @@
1
+ import { CourseDBInterface, QualifiedCardID, StudySessionNewItem, StudySessionReviewItem, UserDBInterface } from '..';
2
+ import { ContentNavigationStrategyData } from '../types/contentNavigationStrategy';
3
+ import { ScheduledCard } from '../types/user';
4
+ import { ContentNavigator } from './index';
5
+ import { logger } from '../../util/logger';
6
+
7
+ export default class HardcodedOrderNavigator extends ContentNavigator {
8
+ private orderedCardIds: string[] = [];
9
+ private user: UserDBInterface;
10
+ private course: CourseDBInterface;
11
+
12
+ constructor(
13
+ user: UserDBInterface,
14
+ course: CourseDBInterface,
15
+ strategyData: ContentNavigationStrategyData
16
+ ) {
17
+ super();
18
+ this.user = user;
19
+ this.course = course;
20
+
21
+ if (strategyData.serializedData) {
22
+ try {
23
+ this.orderedCardIds = JSON.parse(strategyData.serializedData);
24
+ } catch (e) {
25
+ logger.error('Failed to parse serializedData for HardcodedOrderNavigator', e);
26
+ }
27
+ }
28
+ }
29
+
30
+ async getPendingReviews(): Promise<(StudySessionReviewItem & ScheduledCard)[]> {
31
+ const reviews = await this.user.getPendingReviews(this.course.getCourseID());
32
+ return reviews.map((r) => {
33
+ return {
34
+ ...r,
35
+ contentSourceType: 'course',
36
+ contentSourceID: this.course.getCourseID(),
37
+ cardID: r.cardId,
38
+ courseID: r.courseId,
39
+ reviewID: r._id,
40
+ status: 'review',
41
+ };
42
+ });
43
+ }
44
+
45
+ async getNewCards(limit: number = 99): Promise<StudySessionNewItem[]> {
46
+ const activeCardIds = (await this.user.getActiveCards()).map((c: QualifiedCardID) => c.cardID);
47
+
48
+ const newCardIds = this.orderedCardIds.filter(
49
+ (cardId) => !activeCardIds.includes(cardId)
50
+ );
51
+
52
+ const cardsToReturn = newCardIds.slice(0, limit);
53
+
54
+ return cardsToReturn.map((cardId) => {
55
+ return {
56
+ cardID: cardId,
57
+ courseID: this.course.getCourseID(),
58
+ contentSourceType: 'course',
59
+ contentSourceID: this.course.getCourseID(),
60
+ status: 'new',
61
+ };
62
+ });
63
+ }
64
+ }
@@ -11,6 +11,7 @@ import { logger } from '../../util/logger';
11
11
 
12
12
  export enum Navigators {
13
13
  ELO = 'elo',
14
+ HARDCODED = 'hardcodedOrder',
14
15
  }
15
16
 
16
17
  /**
@@ -1,10 +1,11 @@
1
1
  import { DocType, SkuilderCourseData } from './types-legacy';
2
+ import type { DocTypePrefixes } from './types-legacy';
2
3
 
3
4
  /**
4
5
  *
5
6
  */
6
7
  export interface ContentNavigationStrategyData extends SkuilderCourseData {
7
- id: string;
8
+ _id: `${typeof DocTypePrefixes[DocType.NAVIGATION_STRATEGY]}-${string}`;
8
9
  docType: DocType.NAVIGATION_STRATEGY;
9
10
  name: string;
10
11
  description: string;
@@ -91,7 +91,7 @@ export interface QuestionData extends SkuilderCourseData {
91
91
  dataShapeList: PouchDB.Core.DocumentId[];
92
92
  }
93
93
 
94
- export const DocTypePrefixes: Record<string, string> = {
94
+ export const DocTypePrefixes = {
95
95
  [DocType.CARD]: 'c',
96
96
  [DocType.DISPLAYABLE_DATA]: 'dd',
97
97
  [DocType.TAG]: 'TAG',
@@ -103,7 +103,7 @@ export const DocTypePrefixes: Record<string, string> = {
103
103
  [DocType.VIEW]: 'VIEW',
104
104
  [DocType.PEDAGOGY]: 'PEDAGOGY',
105
105
  [DocType.NAVIGATION_STRATEGY]: 'NAVIGATION_STRATEGY',
106
- };
106
+ } as const;
107
107
 
108
108
  export interface CardHistory<T extends CardRecord> {
109
109
  _id: PouchDB.Core.DocumentId;
@@ -761,17 +761,21 @@ Currently logged-in as ${this._username}.`
761
761
  } catch (e) {
762
762
  const reason = e as PouchError;
763
763
  if (reason.status === 404) {
764
- const initCardHistory: CardHistory<T> = {
765
- _id: cardHistoryID,
766
- cardID: record.cardID,
767
- courseID: record.courseID,
768
- records: [record],
769
- lapses: 0,
770
- streak: 0,
771
- bestInterval: 0,
772
- };
773
- const putResult = await this.writeDB.put<CardHistory<T>>(initCardHistory);
774
- return { ...initCardHistory, _rev: putResult.rev };
764
+ try {
765
+ const initCardHistory: CardHistory<T> = {
766
+ _id: cardHistoryID,
767
+ cardID: record.cardID,
768
+ courseID: record.courseID,
769
+ records: [record],
770
+ lapses: 0,
771
+ streak: 0,
772
+ bestInterval: 0,
773
+ };
774
+ const putResult = await this.writeDB.put<CardHistory<T>>(initCardHistory);
775
+ return { ...initCardHistory, _rev: putResult.rev };
776
+ } catch (creationError) {
777
+ throw new Error(`Failed to create CardHistory for ${cardHistoryID}. Reason: ${creationError}`);
778
+ }
775
779
  } else {
776
780
  throw new Error(`putCardRecord failed because of:
777
781
  name:${reason.name}
@@ -25,6 +25,7 @@ import {
25
25
  SkuilderCourseData,
26
26
  Tag,
27
27
  TagStub,
28
+ DocTypePrefixes,
28
29
  } from '../../core/types/types-legacy';
29
30
  import { logger } from '../../util/logger';
30
31
  import { GET_CACHED } from './clientCache';
@@ -224,7 +225,29 @@ export class CourseDB implements StudyContentSource, CourseDBInterface {
224
225
  if (!doc.docType || !(doc.docType === DocType.CARD)) {
225
226
  throw new Error(`failed to remove ${id} from course ${this.id}. id does not point to a card`);
226
227
  }
227
- // TODO: remove card from tags lists (getTagsByCards)
228
+
229
+ // Remove card from all associated tags before deleting the card
230
+ try {
231
+ const appliedTags = await this.getAppliedTags(id);
232
+ const results = await Promise.allSettled(
233
+ appliedTags.rows.map(async (tagRow) => {
234
+ const tagId = tagRow.id;
235
+ await this.removeTagFromCard(id, tagId);
236
+ })
237
+ );
238
+
239
+ // Log any individual tag cleanup failures
240
+ results.forEach((result, index) => {
241
+ if (result.status === 'rejected') {
242
+ const tagId = appliedTags.rows[index].id;
243
+ logger.error(`Failed to remove card ${id} from tag ${tagId}: ${result.reason}`);
244
+ }
245
+ });
246
+ } catch (error) {
247
+ logger.error(`Error removing card ${id} from tags: ${error}`);
248
+ // Continue with card deletion even if tag cleanup fails
249
+ }
250
+
228
251
  return this.db.remove(doc);
229
252
  }
230
253
 
@@ -456,40 +479,38 @@ above:\n${above.rows.map((r) => `\t${r.id}-${r.key}\n`)}`;
456
479
 
457
480
  getNavigationStrategy(id: string): Promise<ContentNavigationStrategyData> {
458
481
  logger.debug(`[courseDB] Getting navigation strategy: ${id}`);
459
- // For now, just return the ELO strategy regardless of the ID
460
- const strategy: ContentNavigationStrategyData = {
461
- id: 'ELO',
462
- docType: DocType.NAVIGATION_STRATEGY,
463
- name: 'ELO',
464
- description: 'ELO-based navigation strategy for ordering content by difficulty',
465
- implementingClass: Navigators.ELO,
466
- course: this.id,
467
- serializedData: '', // serde is a noop for ELO navigator.
468
- };
469
- return Promise.resolve(strategy);
470
- }
471
482
 
472
- getAllNavigationStrategies(): Promise<ContentNavigationStrategyData[]> {
473
- logger.debug('[courseDB] Returning hard-coded navigation strategies');
474
- const strategies: ContentNavigationStrategyData[] = [
475
- {
476
- id: 'ELO',
483
+ if (id == '') {
484
+ const strategy: ContentNavigationStrategyData = {
485
+ _id: 'NAVIGATION_STRATEGY-ELO',
477
486
  docType: DocType.NAVIGATION_STRATEGY,
478
487
  name: 'ELO',
479
488
  description: 'ELO-based navigation strategy for ordering content by difficulty',
480
489
  implementingClass: Navigators.ELO,
481
490
  course: this.id,
482
491
  serializedData: '', // serde is a noop for ELO navigator.
483
- },
484
- ];
485
- return Promise.resolve(strategies);
492
+ };
493
+ return Promise.resolve(strategy);
494
+ } else {
495
+ return this.db.get(id);
496
+ }
486
497
  }
487
498
 
488
- addNavigationStrategy(data: ContentNavigationStrategyData): Promise<void> {
489
- logger.debug(`[courseDB] Adding navigation strategy: ${data.id}`);
490
- // For now, just log the data and return success
491
- logger.debug(JSON.stringify(data));
492
- return Promise.resolve();
499
+ async getAllNavigationStrategies(): Promise<ContentNavigationStrategyData[]> {
500
+ const prefix = DocTypePrefixes[DocType.NAVIGATION_STRATEGY];
501
+ const result = await this.db.allDocs<ContentNavigationStrategyData>({
502
+ startkey: prefix,
503
+ endkey: `${prefix}\ufff0`,
504
+ include_docs: true,
505
+ });
506
+ return result.rows.map((row) => row.doc!);
507
+ }
508
+
509
+ async addNavigationStrategy(data: ContentNavigationStrategyData): Promise<void> {
510
+ logger.debug(`[courseDB] Adding navigation strategy: ${data._id}`);
511
+ // // For now, just log the data and return success
512
+ // logger.debug(JSON.stringify(data));
513
+ return this.db.put(data).then(() => {});
493
514
  }
494
515
  updateNavigationStrategy(id: string, data: ContentNavigationStrategyData): Promise<void> {
495
516
  logger.debug(`[courseDB] Updating navigation strategy: ${id}`);
@@ -499,9 +520,35 @@ above:\n${above.rows.map((r) => `\t${r.id}-${r.key}\n`)}`;
499
520
  }
500
521
 
501
522
  async surfaceNavigationStrategy(): Promise<ContentNavigationStrategyData> {
523
+ try {
524
+ const config = await this.getCourseConfig();
525
+ // @ts-expect-error tmp: defaultNavigationStrategyId property does not yet exist
526
+ if (config.defaultNavigationStrategyId) {
527
+ try {
528
+ // @ts-expect-error tmp: defaultNavigationStrategyId property does not yet exist
529
+ const strategy = await this.getNavigationStrategy(config.defaultNavigationStrategyId);
530
+ if (strategy) {
531
+ logger.debug(`Surfacing strategy ${strategy.name} from course config`);
532
+ return strategy;
533
+ }
534
+ } catch (e) {
535
+ logger.warn(
536
+ // @ts-expect-error tmp: defaultNavigationStrategyId property does not yet exist
537
+ `Failed to load strategy '${config.defaultNavigationStrategyId}' specified in course config. Falling back to ELO.`,
538
+ e
539
+ );
540
+ }
541
+ }
542
+ } catch (e) {
543
+ logger.warn(
544
+ 'Could not retrieve course config to determine navigation strategy. Falling back to ELO.',
545
+ e
546
+ );
547
+ }
548
+
502
549
  logger.warn(`Returning hard-coded default ELO navigator`);
503
550
  const ret: ContentNavigationStrategyData = {
504
- id: 'ELO',
551
+ _id: 'NAVIGATION_STRATEGY-ELO',
505
552
  docType: DocType.NAVIGATION_STRATEGY,
506
553
  name: 'ELO',
507
554
  description: 'ELO-based navigation strategy',
@@ -44,9 +44,10 @@ export default class UpdateQueue extends Loggable {
44
44
  ): Promise<T & PouchDB.Core.GetMeta & PouchDB.Core.RevisionIdMeta> {
45
45
  logger.debug(`Applying updates on doc: ${id}`);
46
46
  if (this.inprogressUpdates[id]) {
47
- // console.log(`Updates in progress...`);
48
- await this.readDB.info(); // stall for a round trip
49
- // console.log(`Retrying...`);
47
+ // Poll instead of recursing to avoid infinite recursion
48
+ while (this.inprogressUpdates[id]) {
49
+ await new Promise(resolve => setTimeout(resolve, Math.random() * 50));
50
+ }
50
51
  return this.applyUpdates<T>(id);
51
52
  } else {
52
53
  if (this.pendingUpdates[id] && this.pendingUpdates[id].length > 0) {
@@ -94,6 +95,10 @@ export default class UpdateQueue extends Loggable {
94
95
  logger.warn(`Conflict on update for doc ${id}, retry #${i + 1}`);
95
96
  await new Promise((res) => setTimeout(res, 50 * Math.random()));
96
97
  // continue to next iteration of the loop
98
+ } else if (e.name === 'not_found' && i === 0) {
99
+ // Document not present - throw to caller for initialization
100
+ logger.warn(`Update failed for ${id} - does not exist. Throwing to caller.`);
101
+ throw e; // Let caller handle
97
102
  } else {
98
103
  // Max retries reached or a non-conflict error
99
104
  delete this.inprogressUpdates[id];
@@ -1,3 +1,4 @@
1
+
1
2
  // packages/db/src/impl/static/StaticDataLayerProvider.ts
2
3
 
3
4
  import {
@@ -17,39 +18,78 @@ import { StaticCoursesDB } from './coursesDB';
17
18
  import { BaseUser } from '../common';
18
19
  import { NoOpSyncStrategy } from './NoOpSyncStrategy';
19
20
 
21
+
22
+ interface SkuilderManifest {
23
+ name?: string;
24
+ version?: string;
25
+ description?: string;
26
+ dependencies?: Record<string, string>;
27
+ }
28
+
20
29
  interface StaticDataLayerConfig {
21
- staticContentPath: string;
22
30
  localStoragePrefix?: string;
23
- manifests: Record<string, StaticCourseManifest>; // courseId -> manifest
31
+ rootManifest: SkuilderManifest; // The parsed root skuilder.json object
32
+ rootManifestUrl: string; // The absolute URL where the root manifest was found
24
33
  }
25
34
 
26
35
  export class StaticDataLayerProvider implements DataLayerProvider {
27
36
  private config: StaticDataLayerConfig;
28
37
  private initialized: boolean = false;
29
38
  private courseUnpackers: Map<string, StaticDataUnpacker> = new Map();
39
+ private manifests: Record<string, StaticCourseManifest> = {};
30
40
 
31
41
  constructor(config: Partial<StaticDataLayerConfig>) {
32
42
  this.config = {
33
- staticContentPath: config.staticContentPath || '/static-courses',
34
43
  localStoragePrefix: config.localStoragePrefix || 'skuilder-static',
35
- manifests: config.manifests || {},
44
+ rootManifest: config.rootManifest || { dependencies: {} },
45
+ rootManifestUrl: config.rootManifestUrl || '/',
36
46
  };
37
47
  }
38
48
 
49
+ private async resolveCourseDependencies(): Promise<void> {
50
+ logger.info('[StaticDataLayerProvider] Starting course dependency resolution...');
51
+ const rootManifest = this.config.rootManifest;
52
+
53
+ for (const [courseName, courseUrl] of Object.entries(rootManifest.dependencies || {})) {
54
+ try {
55
+ logger.debug(`[StaticDataLayerProvider] Resolving dependency: ${courseName} from ${courseUrl}`);
56
+
57
+ const courseManifestUrl = new URL(courseUrl as string, this.config.rootManifestUrl).href;
58
+ const courseJsonResponse = await fetch(courseManifestUrl);
59
+ if (!courseJsonResponse.ok) {
60
+ throw new Error(`Failed to fetch course manifest for ${courseName}`);
61
+ }
62
+ const courseJson = await courseJsonResponse.json();
63
+
64
+ if (courseJson.content && courseJson.content.manifest) {
65
+ const baseUrl = new URL('.', courseManifestUrl).href;
66
+ const finalManifestUrl = new URL(courseJson.content.manifest, courseManifestUrl).href;
67
+
68
+ const finalManifestResponse = await fetch(finalManifestUrl);
69
+ if (!finalManifestResponse.ok) {
70
+ throw new Error(`Failed to fetch final content manifest for ${courseName} at ${finalManifestUrl}`);
71
+ }
72
+ const finalManifest = await finalManifestResponse.json();
73
+
74
+ this.manifests[courseName] = finalManifest;
75
+ const unpacker = new StaticDataUnpacker(finalManifest, baseUrl);
76
+ this.courseUnpackers.set(courseName, unpacker);
77
+
78
+ logger.info(`[StaticDataLayerProvider] Successfully resolved and prepared course: ${courseName}`);
79
+ }
80
+ } catch (e) {
81
+ logger.error(`[StaticDataLayerProvider] Failed to resolve dependency ${courseName}:`, e);
82
+ // Continue to next dependency
83
+ }
84
+ }
85
+ logger.info('[StaticDataLayerProvider] Course dependency resolution complete.');
86
+ }
87
+
39
88
  async initialize(): Promise<void> {
40
89
  if (this.initialized) return;
41
90
 
42
91
  logger.info('Initializing static data layer provider');
43
-
44
- // Load manifests for all courses
45
- for (const [courseId, manifest] of Object.entries(this.config.manifests)) {
46
- const unpacker = new StaticDataUnpacker(
47
- manifest,
48
- `${this.config.staticContentPath}/${courseId}`
49
- );
50
- this.courseUnpackers.set(courseId, unpacker);
51
- }
52
-
92
+ await this.resolveCourseDependencies();
53
93
  this.initialized = true;
54
94
  }
55
95
 
@@ -67,14 +107,14 @@ export class StaticDataLayerProvider implements DataLayerProvider {
67
107
  getCourseDB(courseId: string): CourseDBInterface {
68
108
  const unpacker = this.courseUnpackers.get(courseId);
69
109
  if (!unpacker) {
70
- throw new Error(`Course ${courseId} not found in static data`);
110
+ throw new Error(`Course ${courseId} not found or failed to initialize in static data layer.`);
71
111
  }
72
- const manifest = this.config.manifests[courseId];
112
+ const manifest = this.manifests[courseId];
73
113
  return new StaticCourseDB(courseId, unpacker, this.getUserDB(), manifest);
74
114
  }
75
115
 
76
116
  getCoursesDB(): CoursesDBInterface {
77
- return new StaticCoursesDB(this.config.manifests);
117
+ return new StaticCoursesDB(this.manifests);
78
118
  }
79
119
 
80
120
  async getClassroomDB(
@@ -132,17 +132,22 @@ export class StaticCourseDB implements CourseDBInterface {
132
132
  return { ok: true, id: cardId, rev: '1-static' };
133
133
  }
134
134
 
135
- async getNewCards(limit?: number): Promise<StudySessionNewItem[]> {
136
- // Simplified implementation - would need proper navigation strategy
137
- const cardIds = await this.unpacker.queryByElo(1000, limit || 10);
138
- return cardIds.map((cardId) => ({
139
- status: 'new' as const,
140
- qualifiedID: `${this.courseId}-${cardId}`,
141
- cardID: cardId,
142
- contentSourceType: 'course' as const,
143
- contentSourceID: this.courseId,
144
- courseID: this.courseId,
145
- }));
135
+ async getNewCards(limit: number = 99): Promise<StudySessionNewItem[]> {
136
+ const activeCards = await this.userDB.getActiveCards();
137
+ return (
138
+ await this.getCardsCenteredAtELO({ limit: limit, elo: 'user' }, (c: QualifiedCardID) => {
139
+ if (activeCards.some((ac) => c.cardID === ac.cardID)) {
140
+ return false;
141
+ } else {
142
+ return true;
143
+ }
144
+ })
145
+ ).map((c) => {
146
+ return {
147
+ ...c,
148
+ status: 'new',
149
+ };
150
+ });
146
151
  }
147
152
 
148
153
  async getCardsCenteredAtELO(
@@ -364,7 +369,7 @@ export class StaticCourseDB implements CourseDBInterface {
364
369
  // Navigation Strategy Manager implementation
365
370
  async getNavigationStrategy(_id: string): Promise<ContentNavigationStrategyData> {
366
371
  return {
367
- id: 'ELO',
372
+ _id: 'NAVIGATION_STRATEGY-ELO',
368
373
  docType: DocType.NAVIGATION_STRATEGY,
369
374
  name: 'ELO',
370
375
  description: 'ELO-based navigation strategy',