@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.
- package/CLAUDE.md +2 -2
- package/dist/{classroomDB-BgfrVb8d.d.ts → contentSource-BP9hznNV.d.ts} +220 -197
- package/dist/{classroomDB-CTOenngH.d.cts → contentSource-DsJadoBU.d.cts} +220 -197
- package/dist/core/index.d.cts +80 -6
- package/dist/core/index.d.ts +80 -6
- package/dist/core/index.js +735 -1560
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.mjs +708 -1539
- package/dist/core/index.mjs.map +1 -1
- package/dist/{dataLayerProvider-D6PoCwS6.d.cts → dataLayerProvider-CHYrQ5pB.d.cts} +1 -1
- package/dist/{dataLayerProvider-CZxC9GtB.d.ts → dataLayerProvider-MDTxXq2l.d.ts} +1 -1
- package/dist/impl/couch/index.d.cts +8 -23
- package/dist/impl/couch/index.d.ts +8 -23
- package/dist/impl/couch/index.js +723 -1578
- package/dist/impl/couch/index.js.map +1 -1
- package/dist/impl/couch/index.mjs +692 -1552
- package/dist/impl/couch/index.mjs.map +1 -1
- package/dist/impl/static/index.d.cts +25 -8
- package/dist/impl/static/index.d.ts +25 -8
- package/dist/impl/static/index.js +700 -1400
- package/dist/impl/static/index.js.map +1 -1
- package/dist/impl/static/index.mjs +688 -1393
- package/dist/impl/static/index.mjs.map +1 -1
- package/dist/{index-D-Fa4Smt.d.cts → index-B_j6u5E4.d.cts} +1 -1
- package/dist/{index-CD8BZz2k.d.ts → index-Dj0SEgk3.d.ts} +1 -1
- package/dist/index.d.cts +71 -63
- package/dist/index.d.ts +71 -63
- package/dist/index.js +1162 -1996
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1124 -1955
- package/dist/index.mjs.map +1 -1
- package/dist/pouch/index.js +3 -0
- package/dist/pouch/index.js.map +1 -1
- package/dist/pouch/index.mjs +3 -0
- package/dist/pouch/index.mjs.map +1 -1
- package/dist/{types-CzPDLAK6.d.cts → types-Bn0itutr.d.cts} +1 -1
- package/dist/{types-CewsN87z.d.ts → types-DQaXnuoc.d.ts} +1 -1
- package/dist/{types-legacy-6ettoclI.d.cts → types-legacy-DDY4N-Uq.d.cts} +3 -1
- package/dist/{types-legacy-6ettoclI.d.ts → types-legacy-DDY4N-Uq.d.ts} +3 -1
- package/dist/util/packer/index.d.cts +3 -3
- package/dist/util/packer/index.d.ts +3 -3
- package/docs/navigators-architecture.md +115 -17
- package/package.json +4 -4
- package/src/core/index.ts +1 -0
- package/src/core/interfaces/classroomDB.ts +5 -13
- package/src/core/interfaces/contentSource.ts +6 -66
- package/src/core/interfaces/courseDB.ts +15 -7
- package/src/core/interfaces/userDB.ts +32 -0
- package/src/core/navigators/Pipeline.ts +136 -52
- package/src/core/navigators/PipelineAssembler.ts +1 -1
- package/src/core/navigators/defaults.ts +84 -0
- package/src/core/navigators/{hierarchyDefinition.ts → filters/hierarchyDefinition.ts} +15 -29
- package/src/core/navigators/filters/index.ts +3 -0
- package/src/core/navigators/filters/inferredPreferenceStub.ts +107 -0
- package/src/core/navigators/{interferenceMitigator.ts → filters/interferenceMitigator.ts} +11 -37
- package/src/core/navigators/{relativePriority.ts → filters/relativePriority.ts} +12 -38
- package/src/core/navigators/filters/userGoalStub.ts +136 -0
- package/src/core/navigators/filters/userTagPreference.ts +217 -0
- package/src/core/navigators/{CompositeGenerator.ts → generators/CompositeGenerator.ts} +15 -64
- package/src/core/navigators/{elo.ts → generators/elo.ts} +13 -63
- package/src/core/navigators/{srs.ts → generators/srs.ts} +11 -40
- package/src/core/navigators/generators/types.ts +1 -1
- package/src/core/navigators/index.ts +95 -91
- package/src/core/types/strategyState.ts +84 -0
- package/src/core/types/types-legacy.ts +2 -0
- package/src/impl/common/BaseUserDB.ts +74 -7
- package/src/impl/couch/adminDB.ts +1 -2
- package/src/impl/couch/classroomDB.ts +100 -103
- package/src/impl/couch/courseDB.ts +35 -91
- package/src/impl/couch/pouchdb-setup.ts +7 -0
- package/src/impl/static/StaticDataUnpacker.ts +50 -1
- package/src/impl/static/courseDB.ts +87 -37
- package/src/study/SessionController.ts +122 -202
- package/src/study/SourceMixer.ts +65 -0
- package/src/study/TagFilteredContentSource.ts +49 -92
- package/src/study/index.ts +1 -0
- package/src/study/services/CardHydrationService.ts +165 -81
- package/src/util/dataDirectory.ts +1 -1
- package/src/util/index.ts +0 -1
- package/tests/core/navigators/CompositeGenerator.test.ts +44 -168
- package/tests/core/navigators/Pipeline.test.ts +6 -72
- package/tests/core/navigators/PipelineAssembler.test.ts +8 -58
- package/tests/core/navigators/navigators.test.ts +118 -151
- package/docs/todo-pipeline-optimization.md +0 -117
- package/docs/todo-strategy-state-storage.md +0 -278
- package/src/core/navigators/hardcodedOrder.ts +0 -163
- package/src/util/tuiLogger.ts +0 -139
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/core/interfaces/adminDB.ts","../src/core/interfaces/classroomDB.ts","../src/impl/common/SyncStrategy.ts","../src/util/logger.ts","../src/core/types/types-legacy.ts","../src/core/util/index.ts","../src/impl/couch/pouchdb-setup.ts","../src/util/tuiLogger.ts","../src/util/dataDirectory.ts","../src/impl/common/userDBHelpers.ts","../src/util/Loggable.ts","../src/impl/couch/updateQueue.ts","../src/impl/couch/user-course-relDB.ts","../src/impl/couch/clientCache.ts","../src/impl/couch/courseAPI.ts","../src/impl/couch/courseLookupDB.ts","../src/core/navigators/CompositeGenerator.ts","../src/core/navigators/Pipeline.ts","../src/core/navigators/PipelineAssembler.ts","../src/core/navigators/elo.ts","../src/core/navigators/filters/eloDistance.ts","../src/core/navigators/filters/index.ts","../src/core/navigators/filters/types.ts","../src/core/navigators/generators/index.ts","../src/core/navigators/generators/types.ts","../src/core/navigators/hardcodedOrder.ts","../src/core/navigators/hierarchyDefinition.ts","../src/core/navigators/interferenceMitigator.ts","../src/core/navigators/relativePriority.ts","../src/core/navigators/srs.ts","../src/core/navigators/index.ts","../src/impl/couch/courseDB.ts","../src/impl/couch/classroomDB.ts","../src/impl/couch/adminDB.ts","../src/impl/couch/auth.ts","../src/impl/couch/CouchDBSyncStrategy.ts","../src/impl/couch/index.ts","../src/impl/common/BaseUserDB.ts","../src/impl/common/index.ts","../src/impl/couch/PouchDataLayerProvider.ts","../src/impl/static/StaticDataUnpacker.ts","../src/impl/static/courseDB.ts","../src/impl/static/coursesDB.ts","../src/impl/static/NoOpSyncStrategy.ts","../src/impl/static/StaticDataLayerProvider.ts","../src/factory.ts","../src/study/TagFilteredContentSource.ts","../src/core/interfaces/contentSource.ts","../src/core/interfaces/courseDB.ts","../src/core/interfaces/dataLayerProvider.ts","../src/core/interfaces/userDB.ts","../src/core/interfaces/index.ts","../src/core/types/user.ts","../src/core/bulkImport/cardProcessor.ts","../src/core/bulkImport/types.ts","../src/core/bulkImport/index.ts","../src/core/index.ts","../src/index.ts","../src/study/services/SrsService.ts","../src/study/SpacedRepetition.ts","../src/study/services/EloService.ts","../src/study/services/ResponseProcessor.ts","../src/study/services/CardHydrationService.ts","../src/study/ItemQueue.ts","../src/study/SessionController.ts","../src/util/index.ts","../src/util/packer/CouchDBToStaticPacker.ts","../src/util/migrator/StaticToCouchDBMigrator.ts","../src/util/migrator/types.ts","../src/util/migrator/validation.ts","../src/util/migrator/FileSystemAdapter.ts","../src/study/index.ts"],"sourcesContent":["import { ClassroomConfig, CourseConfig } from '@vue-skuilder/common';\n\n/**\n * Admin functionality\n */\nexport interface AdminDBInterface {\n /**\n * Get all users\n */\n // eslint-disable-next-line @typescript-eslint/no-empty-object-type\n getUsers(): Promise<PouchDB.Core.Document<{}>[]>;\n\n /**\n * Get all courses\n */\n getCourses(): Promise<CourseConfig[]>;\n\n /**\n * Remove a course\n */\n removeCourse(id: string): Promise<PouchDB.Core.Response>;\n\n /**\n * Get all classrooms\n */\n getClassrooms(): Promise<(ClassroomConfig & { _id: string })[]>;\n}\n","import { ClassroomConfig } from '@vue-skuilder/common';\nimport { ScheduledCard } from '../types/user';\nimport { StudySessionNewItem, StudySessionReviewItem } from './contentSource';\n\n/**\n * Classroom management\n */\nexport interface ClassroomDBInterface {\n /**\n * Get classroom config\n */\n getConfig(): ClassroomConfig;\n\n /**\n * Get assigned content\n */\n getAssignedContent(): Promise<AssignedContent[]>;\n}\n\nexport interface TeacherClassroomDBInterface extends ClassroomDBInterface {\n /**\n * For teacher interfaces: assign content\n */\n assignContent?(content: AssignedContent): Promise<boolean>;\n\n /**\n * For teacher interfaces: remove content\n */\n removeContent?(content: AssignedContent): Promise<void>;\n}\n\nexport interface StudentClassroomDBInterface extends ClassroomDBInterface {\n /**\n * For student interfaces: get pending reviews\n */\n getPendingReviews?(): Promise<(StudySessionReviewItem & ScheduledCard)[]>;\n\n /**\n * For student interfaces: get new cards\n */\n getNewCards?(limit?: number): Promise<StudySessionNewItem[]>;\n}\n\nexport type AssignedContent = AssignedCourse | AssignedTag | AssignedCard;\n\nexport interface AssignedTag extends ContentBase {\n type: 'tag';\n tagID: string;\n}\nexport interface AssignedCourse extends ContentBase {\n type: 'course';\n}\nexport interface AssignedCard extends ContentBase {\n type: 'card';\n cardID: string;\n}\n\ninterface ContentBase {\n type: 'course' | 'tag' | 'card';\n /**\n * Username of the assigning teacher.\n */\n assignedBy: string;\n /**\n * Date the content was assigned.\n */\n assignedOn: moment.Moment;\n /**\n * A 'due' date for this assigned content, for scheduling content\n * in advance. Content will not be actively pushed to students until\n * this date.\n */\n activeOn: moment.Moment;\n courseID: string;\n}\n","// packages/db/src/impl/common/SyncStrategy.ts\n\nimport type { AccountCreationResult, AuthenticationResult } from './types';\n\n/**\n * Strategy interface for handling user data synchronization\n * Different implementations handle remote sync vs local-only storage\n */\nexport interface SyncStrategy {\n /**\n * Set up the remote database for a user\n * @param username The username to set up remote DB for\n * @returns PouchDB database instance (may be same as local for no-op)\n */\n setupRemoteDB(username: string): PouchDB.Database;\n\n /**\n * Get the database to use for write operations (local-first approach)\n * @param username The username to get write DB for\n * @returns PouchDB database instance for write operations\n */\n getWriteDB?(username: string): PouchDB.Database;\n\n /**\n * Start synchronization between local and remote databases\n * @param localDB The local PouchDB instance\n * @param remoteDB The remote PouchDB instance\n */\n startSync(localDB: PouchDB.Database, remoteDB: PouchDB.Database): void;\n\n /**\n * Stop synchronization (optional - for cleanup)\n */\n stopSync?(): void;\n\n /**\n * Whether this strategy supports account creation\n */\n canCreateAccount(): boolean;\n\n /**\n * Whether this strategy supports authentication\n */\n canAuthenticate(): boolean;\n\n /**\n * Create a new user account (if supported)\n * @param username The username for the new account\n * @param password The password for the new account\n */\n createAccount?(username: string, password: string): Promise<AccountCreationResult>;\n\n /**\n * Authenticate a user (if supported)\n * @param username The username to authenticate\n * @param password The password to authenticate with\n */\n authenticate?(username: string, password: string): Promise<AuthenticationResult>;\n\n /**\n * Log out the current user (if supported)\n */\n logout?(): Promise<AuthenticationResult>;\n\n /**\n * Get the current logged-in username\n * Returns the username if logged in, or a default guest username\n */\n getCurrentUsername(): Promise<string>;\n}\n\n/**\n * Base class for sync strategies with common functionality\n */\nexport abstract class BaseSyncStrategy implements SyncStrategy {\n abstract setupRemoteDB(username: string): PouchDB.Database;\n abstract startSync(localDB: PouchDB.Database, remoteDB: PouchDB.Database): void;\n abstract canCreateAccount(): boolean;\n abstract canAuthenticate(): boolean;\n abstract getCurrentUsername(): Promise<string>;\n\n stopSync?(): void {\n // Default no-op implementation\n }\n\n async createAccount(_username: string, _password: string): Promise<AccountCreationResult> {\n throw new Error('Account creation not supported by this sync strategy');\n }\n\n async authenticate(_username: string, _password: string): Promise<AuthenticationResult> {\n throw new Error('Authentication not supported by this sync strategy');\n }\n\n async logout(): Promise<AuthenticationResult> {\n throw new Error('Logout not supported by this sync strategy');\n }\n}\n","/**\n * Simple logging utility for @vue-skuilder/db package\n *\n * This utility provides environment-aware logging with ESLint suppressions\n * to resolve console statement violations while maintaining logging functionality.\n */\n\nconst isDevelopment = typeof process !== 'undefined' && process.env.NODE_ENV === 'development';\n\nexport const logger = {\n /**\n * Debug-level logging - only shown in development\n */\n debug: (message: string, ...args: any[]): void => {\n if (isDevelopment) {\n // eslint-disable-next-line no-console\n console.debug(`[DB:DEBUG] ${message}`, ...args);\n }\n },\n\n /**\n * Info-level logging - general information\n */\n info: (message: string, ...args: any[]): void => {\n // eslint-disable-next-line no-console\n console.info(`[DB:INFO] ${message}`, ...args);\n },\n\n /**\n * Warning-level logging - potential issues\n */\n warn: (message: string, ...args: any[]): void => {\n // eslint-disable-next-line no-console\n console.warn(`[DB:WARN] ${message}`, ...args);\n },\n\n /**\n * Error-level logging - serious problems\n */\n error: (message: string, ...args: any[]): void => {\n // eslint-disable-next-line no-console\n console.error(`[DB:ERROR] ${message}`, ...args);\n },\n\n /**\n * Log function for backward compatibility with existing log() usage\n */\n log: (message: string, ...args: any[]): void => {\n if (isDevelopment) {\n // eslint-disable-next-line no-console\n console.log(`[DB:LOG] ${message}`, ...args);\n }\n },\n};\n","import { CourseElo, Answer, Evaluation } from '@vue-skuilder/common';\nimport { Moment } from 'moment';\nimport { logger } from '../../util/logger';\n\nexport const GuestUsername: string = 'sk-guest-';\n\nexport const log = (message: string): void => {\n logger.log(message);\n};\n\nexport enum DocType {\n DISPLAYABLE_DATA = 'DISPLAYABLE_DATA',\n CARD = 'CARD',\n DATASHAPE = 'DATASHAPE',\n QUESTIONTYPE = 'QUESTION',\n VIEW = 'VIEW',\n PEDAGOGY = 'PEDAGOGY',\n CARDRECORD = 'CARDRECORD',\n SCHEDULED_CARD = 'SCHEDULED_CARD',\n TAG = 'TAG',\n NAVIGATION_STRATEGY = 'NAVIGATION_STRATEGY',\n}\n\nexport interface QualifiedCardID {\n courseID: string;\n cardID: string;\n}\n\n/**\n * Interface for all data on course content and pedagogy stored\n * in the c/pouch database.\n */\nexport interface SkuilderCourseData {\n course: string;\n docType: DocType;\n}\n\nexport interface Tag extends SkuilderCourseData {\n docType: DocType.TAG;\n name: string;\n snippet: string; // 200 char description of the tag\n wiki: string; // 3000 char md-friendly description\n taggedCards: PouchDB.Core.DocumentId[];\n author: string;\n}\nexport interface TagStub {\n name: string;\n snippet: string;\n count: number; // the number of cards that have this tag applied\n}\n\nexport interface CardData extends SkuilderCourseData {\n docType: DocType.CARD;\n id_displayable_data: PouchDB.Core.DocumentId[];\n id_view: PouchDB.Core.DocumentId;\n elo: CourseElo;\n author: string;\n}\n\n/** A list of populated courses in the DB */\nexport interface CourseListData extends PouchDB.Core.Response {\n courses: string[];\n}\n\n/**\n * The data used to hydrate viewable components (questions, info, etc)\n */\nexport interface DisplayableData extends SkuilderCourseData {\n docType: DocType.DISPLAYABLE_DATA;\n author?: string;\n id_datashape: PouchDB.Core.DocumentId;\n data: Field[];\n _attachments?: { [index: string]: PouchDB.Core.FullAttachment };\n}\n\nexport interface Field {\n data: unknown;\n name: string;\n}\n\nexport interface DataShapeData extends SkuilderCourseData {\n docType: DocType.DATASHAPE;\n _id: PouchDB.Core.DocumentId;\n questionTypes: PouchDB.Core.DocumentId[];\n}\n\nexport interface QuestionData extends SkuilderCourseData {\n docType: DocType.QUESTIONTYPE;\n _id: PouchDB.Core.DocumentId;\n viewList: string[];\n dataShapeList: PouchDB.Core.DocumentId[];\n}\n\nexport const DocTypePrefixes = {\n [DocType.CARD]: 'c',\n [DocType.DISPLAYABLE_DATA]: 'dd',\n [DocType.TAG]: 'TAG',\n [DocType.CARDRECORD]: 'cardH',\n [DocType.SCHEDULED_CARD]: 'card_review_',\n // Add other doctypes here as they get prefixed IDs\n [DocType.DATASHAPE]: 'DATASHAPE',\n [DocType.QUESTIONTYPE]: 'QUESTION',\n [DocType.VIEW]: 'VIEW',\n [DocType.PEDAGOGY]: 'PEDAGOGY',\n [DocType.NAVIGATION_STRATEGY]: 'NAVIGATION_STRATEGY',\n} as const;\n\nexport interface CardHistory<T extends CardRecord> {\n _id: PouchDB.Core.DocumentId;\n /**\n * The CouchDB id of the card\n */\n cardID: PouchDB.Core.DocumentId;\n\n /**\n * The ID of the course\n */\n courseID: string;\n\n /**\n * The to-date largest interval between successful\n * card reviews. `0` indicates no successful reviews.\n */\n bestInterval: number;\n\n /**\n * The number of times that a card has been\n * failed in review\n */\n lapses: number;\n\n /**\n * The number of consecutive successful impressions\n * on this card\n */\n streak: number;\n\n records: T[];\n}\n\nexport interface CardRecord {\n /**\n * The CouchDB id of the card\n */\n cardID: string;\n /**\n * The ID of the course\n */\n courseID: string;\n /**\n * Number of milliseconds that the user spent before dismissing\n * the card (ie, \"I've read this\" or \"here is my answer\")\n *\n * //TODO: this (sometimes?) wants to be replaced with a rich\n * recording of user activity in working the question\n */\n timeSpent: number;\n /**\n * The date-time that the card was rendered. timeStamp + timeSpent will give the\n * time of user submission.\n */\n timeStamp: Moment;\n}\n\nexport interface QuestionRecord extends CardRecord, Evaluation {\n userAnswer: Answer;\n /**\n * The number of incorrect user submissions prededing this submisstion.\n *\n * eg, if a user is asked 7*6=__, submitting 46, 48, 42 will result in three\n * records being created having 0, 1, and 2 as their recorded 'priorAttempts' values\n */\n priorAttemps: number;\n}\n","import { DocType, DocTypePrefixes, CardHistory, CardRecord, QuestionRecord } from '../types/types-legacy';\n\nexport function areQuestionRecords(h: CardHistory<CardRecord>): h is CardHistory<QuestionRecord> {\n return isQuestionRecord(h.records[0]);\n}\n\nexport function isQuestionRecord(c: CardRecord): c is QuestionRecord {\n return (c as QuestionRecord).userAnswer !== undefined;\n}\n\nexport function getCardHistoryID(courseID: string, cardID: string): PouchDB.Core.DocumentId {\n return `${DocTypePrefixes[DocType.CARDRECORD]}-${courseID}-${cardID}`;\n}\n\nexport function parseCardHistoryID(id: string): {\n courseID: string;\n cardID: string;\n} {\n const split = id.split('-');\n let error: string = '';\n error += split.length === 3 ? '' : `\\n\\tgiven ID has incorrect number of '-' characters`;\n error +=\n split[0] === DocTypePrefixes[DocType.CARDRECORD] ? '' : `\n\tgiven ID does not start with ${DocTypePrefixes[DocType.CARDRECORD]}`;\n\n if (split.length === 3 && split[0] === DocTypePrefixes[DocType.CARDRECORD]) {\n return {\n courseID: split[1],\n cardID: split[2],\n };\n } else {\n throw new Error('parseCardHistory Error:' + error);\n }\n}\n\ninterface PouchDBError extends Error {\n error?: string;\n reason?: string;\n}\n\nexport function docIsDeleted(e: PouchDBError): boolean {\n return Boolean(e?.error === 'not_found' && e?.reason === 'deleted');\n}\n","import PouchDB from 'pouchdb';\nimport PouchDBFind from 'pouchdb-find';\nimport PouchDBAuth from '@nilock2/pouchdb-authentication';\n\n// Register plugins\nPouchDB.plugin(PouchDBFind);\nPouchDB.plugin(PouchDBAuth);\n\n// Configure PouchDB globally\nPouchDB.defaults({\n // ajax: {\n // timeout: 60000,\n // },\n});\n\nexport default PouchDB;\n","// TUI-aware logging utility that redirects logs to file in Node.js\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport { getAppDataDirectory } from './dataDirectory';\n\nlet logFile: string | null = null;\nlet isNodeEnvironment = false;\n\n/**\n * Initialize TUI logging - redirect console logs to file in Node.js\n */\nexport function initializeTuiLogging(): void {\n // Detect Node.js environment\n isNodeEnvironment = typeof window === 'undefined' && typeof process !== 'undefined';\n \n if (!isNodeEnvironment) {\n return; // Browser environment - keep normal console logging\n }\n\n try {\n // Set up log file path\n logFile = path.join(getAppDataDirectory(), 'lastrun.log');\n \n // Clear previous log file\n if (fs.existsSync(logFile)) {\n fs.unlinkSync(logFile);\n }\n \n // Create initial log entry\n const startTime = new Date().toISOString();\n fs.writeFileSync(logFile, `=== TUI Session Started: ${startTime} ===\\n`);\n \n // Redirect console methods to file\n const originalConsole = {\n // eslint-disable-next-line no-console\n log: console.log,\n // eslint-disable-next-line no-console\n error: console.error,\n // eslint-disable-next-line no-console\n warn: console.warn,\n // eslint-disable-next-line no-console\n info: console.info\n };\n \n const writeToLog = (level: string, args: any[]) => {\n const timestamp = new Date().toISOString();\n const message = args.map(arg => \n typeof arg === 'object' ? JSON.stringify(arg, null, 2) : String(arg)\n ).join(' ');\n \n const logEntry = `[${timestamp}] ${level}: ${message}\\n`;\n \n try {\n fs.appendFileSync(logFile!, logEntry);\n } catch (err) {\n // Fallback to original console if file write fails\n originalConsole.error('Failed to write to log file:', err);\n originalConsole[level.toLowerCase() as keyof typeof originalConsole](...args);\n }\n };\n \n // Override console methods\n // eslint-disable-next-line no-console\n console.log = (...args) => writeToLog('INFO', args);\n // eslint-disable-next-line no-console\n console.info = (...args) => writeToLog('INFO', args);\n // eslint-disable-next-line no-console\n console.warn = (...args) => writeToLog('WARN', args);\n // eslint-disable-next-line no-console\n console.error = (...args) => writeToLog('ERROR', args);\n \n // Store original methods for potential restoration\n (console as any)._originalMethods = originalConsole;\n \n // eslint-disable-next-line no-console\n console.log('TUI logging initialized - logs redirected to', logFile);\n \n } catch (err) {\n // eslint-disable-next-line no-console\n console.error('Failed to initialize TUI logging:', err);\n }\n}\n\n/**\n * Get the current log file path (for debugging)\n */\nexport function getLogFilePath(): string | null {\n return logFile;\n}\n\n/**\n * Show user-facing message (always visible in TUI)\n */\nexport function showUserMessage(message: string): void {\n if (isNodeEnvironment) {\n // In Node.js, write directly to stdout to bypass log redirection\n process.stdout.write(message + '\\n');\n } else {\n // In browser, use normal console\n // eslint-disable-next-line no-console\n console.log(message);\n }\n}\n\n/**\n * Show user-facing error (always visible in TUI)\n */\nexport function showUserError(message: string): void {\n if (isNodeEnvironment) {\n // In Node.js, write directly to stderr to bypass log redirection\n process.stderr.write('Error: ' + message + '\\n');\n } else {\n // In browser, use normal console\n // eslint-disable-next-line no-console\n console.error(message);\n }\n}\n\n/**\n * Logger object with standard log levels\n */\nexport const logger = {\n debug: (message: string, ...args: any[]) => {\n // eslint-disable-next-line no-console\n console.log(`[DEBUG] ${message}`, ...args);\n },\n info: (message: string, ...args: any[]) => {\n // eslint-disable-next-line no-console\n console.info(`[INFO] ${message}`, ...args);\n },\n warn: (message: string, ...args: any[]) => {\n // eslint-disable-next-line no-console\n console.warn(`[WARN] ${message}`, ...args);\n },\n error: (message: string, ...args: any[]) => {\n // eslint-disable-next-line no-console\n console.error(`[ERROR] ${message}`, ...args);\n },\n};","// Cross-platform data directory utilities for PouchDB\n// Provides OS-appropriate application data directories\n\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport * as os from 'os';\nimport { logger } from './tuiLogger';\nimport { ENV } from '@db/factory';\n\n/**\n * Get the application data directory for the current platform\n * Uses ~/.tuilder as requested by user for simplicity\n */\nexport function getAppDataDirectory(): string {\n if (ENV.LOCAL_STORAGE_PREFIX) {\n return path.join(os.homedir(), `.tuilder`, ENV.LOCAL_STORAGE_PREFIX);\n } else {\n return path.join(os.homedir(), '.tuilder');\n }\n}\n\n/**\n * Ensure the application data directory exists\n * Creates directory recursively if it doesn't exist\n */\nexport async function ensureAppDataDirectory(): Promise<string> {\n const appDataDir = getAppDataDirectory();\n \n try {\n await fs.promises.mkdir(appDataDir, { recursive: true });\n } catch (err: any) {\n if (err.code !== 'EEXIST') {\n throw new Error(`Failed to create app data directory ${appDataDir}: ${err.message}`);\n }\n }\n \n return appDataDir;\n}\n\n/**\n * Get the full path for a PouchDB database file\n * @param dbName - The database name (e.g., 'userdb-Colin')\n */\nexport function getDbPath(dbName: string): string {\n return path.join(getAppDataDirectory(), dbName);\n}\n\n/**\n * Initialize data directory for PouchDB usage\n * Should be called once at application startup\n */\nexport async function initializeDataDirectory(): Promise<void> {\n await ensureAppDataDirectory();\n \n // Log initialization\n logger.info(`PouchDB data directory initialized: ${getAppDataDirectory()}`);\n}","// packages/db/src/impl/common/userDBHelpers.ts\n\nimport moment from 'moment';\nimport { DocType, DocTypePrefixes } from '@db/core';\nimport { logger } from '../../util/logger';\nimport { ScheduledCard } from '@db/core/types/user';\n\nexport const REVIEW_TIME_FORMAT: string = 'YYYY-MM-DD--kk:mm:ss-SSS';\n\nimport pouch from '../couch/pouchdb-setup';\nimport { getDbPath } from '../../util/dataDirectory';\n\nconst log = (s: any) => {\n logger.info(s);\n};\n\nexport function hexEncode(str: string): string {\n let hex: string;\n let returnStr: string = '';\n\n for (let i = 0; i < str.length; i++) {\n hex = str.charCodeAt(i).toString(16);\n returnStr += ('000' + hex).slice(3);\n }\n\n return returnStr;\n}\n\nexport function filterAllDocsByPrefix<T>(\n db: PouchDB.Database,\n prefix: string,\n opts?: PouchDB.Core.AllDocsOptions\n) {\n // see couchdb docs 6.2.2:\n // Guide to Views -> Views Collation -> String Ranges\n const options: PouchDB.Core.AllDocsWithinRangeOptions = {\n startkey: prefix,\n endkey: prefix + '\\ufff0',\n include_docs: true,\n };\n\n if (opts) {\n Object.assign(options, opts);\n }\n return db.allDocs<T>(options);\n}\n\nexport function getStartAndEndKeys(key: string): {\n startkey: string;\n endkey: string;\n} {\n return {\n startkey: key,\n endkey: key + '\\ufff0',\n };\n}\n\nexport function updateGuestAccountExpirationDate(guestDB: PouchDB.Database<object>) {\n const currentTime = moment.utc();\n const expirationDate: string = currentTime.add(2, 'months').toISOString();\n const expiryDocID: string = 'GuestAccountExpirationDate';\n\n void guestDB\n .get(expiryDocID)\n .then((doc) => {\n return guestDB.put({\n _id: expiryDocID,\n _rev: doc._rev,\n date: expirationDate,\n });\n })\n .catch(() => {\n return guestDB.put({\n _id: expiryDocID,\n date: expirationDate,\n });\n });\n}\n\n/**\n * Get local user database with appropriate adapter for environment\n */\nexport function getLocalUserDB(username: string): PouchDB.Database {\n // // Choose adapter based on environment\n //\n // Not certain of this is required. Let's let pouch's auto detection\n // handle it until we specifically know we need to intervene.\n //\n // let adapter: string;\n // if (typeof window !== 'undefined') {\n // // Browser environment - use IndexedDB\n // adapter = 'idb';\n // } else {\n // // Node.js environment (tests) - use memory adapter\n // adapter = 'memory';\n // }\n\n const dbName = `userdb-${username}`;\n \n // Use proper data directory in Node.js, browser will use IndexedDB\n if (typeof window === 'undefined') {\n // Node.js environment - use filesystem with proper app data directory\n return new pouch(getDbPath(dbName), {});\n } else {\n // Browser environment - use default (IndexedDB)\n return new pouch(dbName, {});\n }\n}\n\n/**\n * Schedule a card review (strategy-agnostic version)\n */\nexport function scheduleCardReviewLocal(\n userDB: PouchDB.Database,\n review: {\n card_id: PouchDB.Core.DocumentId;\n time: moment.Moment;\n course_id: string;\n scheduledFor: ScheduledCard['scheduledFor'];\n schedulingAgentId: ScheduledCard['schedulingAgentId'];\n }\n) {\n const now = moment.utc();\n logger.info(`Scheduling for review in: ${review.time.diff(now, 'h') / 24} days`);\n void userDB.put<ScheduledCard>({\n _id: DocTypePrefixes[DocType.SCHEDULED_CARD] + review.time.format(REVIEW_TIME_FORMAT),\n cardId: review.card_id,\n reviewTime: review.time.toISOString(),\n courseId: review.course_id,\n scheduledAt: now.toISOString(),\n scheduledFor: review.scheduledFor,\n schedulingAgentId: review.schedulingAgentId,\n });\n}\n\n/**\n * Remove a scheduled card review (strategy-agnostic version)\n */\nexport async function removeScheduledCardReviewLocal(\n userDB: PouchDB.Database,\n reviewDocID: string\n) {\n const reviewDoc = await userDB.get(reviewDocID);\n userDB\n .remove(reviewDoc)\n .then((res) => {\n if (res.ok) {\n log(`Removed Review Doc: ${reviewDocID}`);\n }\n })\n .catch((err) => {\n log(`Failed to remove Review Doc: ${reviewDocID},\\n${JSON.stringify(err)}`);\n });\n}\n","export abstract class Loggable {\n protected abstract readonly _className: string;\n protected log(...args: unknown[]): void {\n // eslint-disable-next-line no-console\n console.log(`LOG-${this._className}@${new Date()}:`, ...args);\n }\n protected error(...args: unknown[]): void {\n // eslint-disable-next-line no-console\n console.error(`ERROR-${this._className}@${new Date()}:`, ...args);\n }\n}\n","import { Loggable } from '../../util/Loggable';\nimport { logger } from '../../util/logger';\n\nexport type Update<T> = Partial<T> | ((x: T) => T);\n\nexport default class UpdateQueue extends Loggable {\n _className: string = 'UpdateQueue';\n private pendingUpdates: {\n [index: string]: Update<unknown>[];\n } = {};\n private inprogressUpdates: {\n [index: string]: boolean;\n } = {};\n\n private readDB: PouchDB.Database; // Database for read operations\n private writeDB: PouchDB.Database; // Database for write operations (local-first)\n\n /**\n * Queues an update for a document and applies it with conflict resolution.\n *\n * @param id - Document ID to update\n * @param update - Partial object or function that transforms the document\n * @returns Promise resolving to the updated document\n *\n * @throws {PouchError} with status 404 if document doesn't exist\n *\n * @remarks\n * **Error Handling Pattern:**\n * - This method does NOT create documents if they don't exist\n * - Callers are responsible for handling 404 errors and creating documents\n * - This design maintains separation of concerns (UpdateQueue handles conflicts, callers handle lifecycle)\n *\n * @example\n * ```typescript\n * try {\n * await updateQueue.update(docId, (doc) => ({ ...doc, field: newValue }));\n * } catch (e) {\n * if ((e as PouchError).status === 404) {\n * // Create the document with initial values\n * await db.put({ _id: docId, field: newValue, ...initialFields });\n * }\n * }\n * ```\n */\n public update<T extends PouchDB.Core.Document<object>>(\n id: PouchDB.Core.DocumentId,\n update: Update<T>\n ) {\n logger.debug(`Update requested on doc: ${id}`);\n if (this.pendingUpdates[id]) {\n this.pendingUpdates[id].push(update);\n } else {\n this.pendingUpdates[id] = [update];\n }\n return this.applyUpdates<T>(id);\n }\n\n constructor(readDB: PouchDB.Database, writeDB?: PouchDB.Database) {\n super();\n // PouchDB.debug.enable('*');\n this.readDB = readDB;\n this.writeDB = writeDB || readDB; // Default to readDB if writeDB not provided\n logger.debug(`UpdateQ initialized...`);\n void this.readDB.info().then((i) => {\n logger.debug(`db info: ${JSON.stringify(i)}`);\n });\n }\n\n private async applyUpdates<T extends PouchDB.Core.Document<object>>(\n id: string\n ): Promise<T & PouchDB.Core.GetMeta & PouchDB.Core.RevisionIdMeta> {\n logger.debug(`Applying updates on doc: ${id}`);\n if (this.inprogressUpdates[id]) {\n // Poll instead of recursing to avoid infinite recursion\n while (this.inprogressUpdates[id]) {\n await new Promise(resolve => setTimeout(resolve, Math.random() * 50));\n }\n return this.applyUpdates<T>(id);\n } else {\n if (this.pendingUpdates[id] && this.pendingUpdates[id].length > 0) {\n this.inprogressUpdates[id] = true;\n\n const MAX_RETRIES = 5;\n for (let i = 0; i < MAX_RETRIES; i++) {\n try {\n const doc = await this.readDB.get<T>(id);\n\n // Create a new doc object to apply updates to for this attempt\n let updatedDoc = { ...doc };\n\n // Note: This loop is not fully safe if updates are functions that depend on a specific doc state\n // that might change between retries. But for simple object merges, it's okay.\n const updatesToApply = [...this.pendingUpdates[id]];\n for (const update of updatesToApply) {\n if (typeof update === 'function') {\n updatedDoc = { ...updatedDoc, ...update(updatedDoc) };\n } else {\n updatedDoc = {\n ...updatedDoc,\n ...update,\n };\n }\n }\n\n await this.writeDB.put<T>(updatedDoc);\n\n // Success! Remove the updates we just applied.\n this.pendingUpdates[id].splice(0, updatesToApply.length);\n\n if (this.pendingUpdates[id].length === 0) {\n this.inprogressUpdates[id] = false;\n delete this.inprogressUpdates[id];\n } else {\n // More updates came in, run again.\n return this.applyUpdates<T>(id);\n }\n return updatedDoc as any; // success, exit loop and function\n } catch (e: any) {\n if (e.name === 'conflict' && i < MAX_RETRIES - 1) {\n logger.warn(`Conflict on update for doc ${id}, retry #${i + 1}`);\n await new Promise((res) => setTimeout(res, 50 * Math.random()));\n // continue to next iteration of the loop\n } else if (e.name === 'not_found' && i === 0) {\n // Document not present - throw to caller for initialization\n logger.warn(`Update failed for ${id} - does not exist. Throwing to caller.`);\n delete this.inprogressUpdates[id];\n throw e; // Let caller handle\n } else {\n // Max retries reached or a non-conflict error\n delete this.inprogressUpdates[id];\n if (this.pendingUpdates[id]) {\n delete this.pendingUpdates[id];\n }\n logger.error(`Error on attemped update (retry ${i}): ${JSON.stringify(e)}`);\n throw e; // Let caller handle\n }\n }\n }\n // This should be unreachable, but it satisfies the compiler that a value is always returned or an error thrown.\n throw new Error(`UpdateQueue failed for doc ${id} after ${MAX_RETRIES} retries.`);\n } else {\n throw new Error(`Empty Updates Queue Triggered`);\n }\n }\n }\n}\n","import {\n ScheduledCard,\n UserCourseSetting,\n UserCourseSettings,\n UsrCrsDataInterface,\n} from '@db/core';\n\nimport moment, { Moment } from 'moment';\n\nimport { UserDBInterface } from '@db/core';\nimport { logger } from '../../util/logger';\n\nexport class UsrCrsData implements UsrCrsDataInterface {\n private user: UserDBInterface;\n private _courseId: string;\n\n constructor(user: UserDBInterface, courseId: string) {\n this.user = user;\n this._courseId = courseId;\n }\n\n public async getReviewsForcast(daysCount: number) {\n const time = moment.utc().add(daysCount, 'days');\n return this.getReviewstoDate(time);\n }\n\n public async getPendingReviews() {\n const now = moment.utc();\n return this.getReviewstoDate(now);\n }\n\n public async getScheduledReviewCount(): Promise<number> {\n return (await this.getPendingReviews()).length;\n }\n\n public async getCourseSettings(): Promise<UserCourseSettings> {\n const regDoc = await this.user.getCourseRegistrationsDoc();\n const crsDoc = regDoc.courses.find((c) => c.courseID === this._courseId);\n\n if (crsDoc && crsDoc.settings) {\n return crsDoc.settings;\n } else {\n logger.warn(`no settings found during lookup on course ${this._courseId}`);\n return {};\n }\n }\n public updateCourseSettings(updates: UserCourseSetting[]): void {\n // TODO: Add updateCourseSettings method to UserDBInterface\n // For now, we'll need to cast to access the concrete implementation\n if ('updateCourseSettings' in this.user) {\n void (this.user as any).updateCourseSettings(this._courseId, updates);\n }\n }\n\n private async getReviewstoDate(targetDate: Moment) {\n // Use the interface method instead of direct database access\n const allReviews = await this.user.getPendingReviews(this._courseId);\n\n logger.debug(\n `Fetching ${this.user.getUsername()}'s scheduled reviews for course ${this._courseId}.`\n );\n\n return allReviews.filter((review: ScheduledCard) => {\n const reviewTime = moment.utc(review.reviewTime);\n return targetDate.isAfter(reviewTime);\n });\n }\n}\n","// todo: something good here instead\n\nconst CLIENT_CACHE: {\n [k: string]: unknown;\n} = {};\n\nexport async function GET_CACHED<K>(k: string, f?: (x: string) => Promise<K>): Promise<K> {\n if (CLIENT_CACHE[k]) {\n // console.log('returning a cached item');\n return CLIENT_CACHE[k] as K;\n }\n\n CLIENT_CACHE[k] = f ? await f(k) : await GET_ITEM(k);\n return GET_CACHED(k);\n}\n\nasync function GET_ITEM(k: string): Promise<unknown> {\n throw new Error(`No implementation found for GET_CACHED(${k})`);\n}\n","import pouch from './pouchdb-setup';\nimport { createPouchDBConfig } from '.';\nimport { ENV } from '@db/factory';\n// import { DataShape } from '../..base-course/Interfaces/DataShape';\nimport { NameSpacer, ShapeDescriptor } from '@vue-skuilder/common';\nimport { CourseConfig, DataShape } from '@vue-skuilder/common';\nimport { CourseElo, blankCourseElo, toCourseElo } from '@vue-skuilder/common';\nimport { CourseDB, createTag } from './courseDB';\nimport { CardData, DisplayableData, DocType, Tag, DocTypePrefixes } from '../../core/types/types-legacy';\nimport { prepareNote55 } from '@vue-skuilder/common';\nimport { BaseUser } from '../common';\nimport { logger } from '@db/util/logger';\nimport { v4 as uuidv4 } from 'uuid';\n\n/**\n *\n * @param courseID id of the course (quilt) being added to\n * @param codeCourse\n * @param shape\n * @param data the datashape data - data required for this shape\n * @param author\n * @param uploads optional additional media uploads: img0, img1, ..., aud0, aud1,...\n * @returns\n */\nexport async function addNote55(\n courseID: string,\n codeCourse: string,\n shape: DataShape,\n data: unknown,\n author: string,\n tags: string[],\n uploads?: { [x: string]: PouchDB.Core.FullAttachment },\n elo: CourseElo = blankCourseElo()\n): Promise<PouchDB.Core.Response> {\n const db = getCourseDB(courseID);\n const payload = prepareNote55(courseID, codeCourse, shape, data, author, tags, uploads);\n const _id = `${DocTypePrefixes[DocType.DISPLAYABLE_DATA]}-${uuidv4()}`;\n const result = await db.put<DisplayableData>({ ...payload, _id });\n\n const dataShapeId = NameSpacer.getDataShapeString({\n course: codeCourse,\n dataShape: shape.name,\n });\n\n if (result.ok) {\n try {\n // create cards\n await createCards(courseID, dataShapeId, result.id, tags, elo, author);\n } catch (error) {\n // Handle CouchDB errors which often have a 'reason' property\n let errorMessage = 'Unknown error';\n if (error instanceof Error) {\n errorMessage = error.message;\n } else if (error && typeof error === 'object' && 'reason' in error) {\n errorMessage = error.reason as string;\n } else if (error && typeof error === 'object' && 'message' in error) {\n errorMessage = error.message as string;\n } else {\n errorMessage = String(error);\n }\n\n logger.error(`[addNote55] Failed to create cards for note ${result.id}: ${errorMessage}`);\n // Add info to result to indicate card creation failed\n (result as any).cardCreationFailed = true;\n (result as any).cardCreationError = errorMessage;\n }\n } else {\n logger.error(`[addNote55] Error adding note. Result: ${JSON.stringify(result)}`);\n }\n\n return result;\n}\n\nasync function createCards(\n courseID: string,\n datashapeID: PouchDB.Core.DocumentId,\n noteID: PouchDB.Core.DocumentId,\n tags: string[],\n elo: CourseElo = blankCourseElo(),\n author: string\n): Promise<void> {\n const cfg = await getCredentialledCourseConfig(courseID);\n const dsDescriptor = NameSpacer.getDataShapeDescriptor(datashapeID);\n let questionViewTypes: string[] = [];\n\n for (const ds of cfg.dataShapes) {\n if (ds.name === datashapeID) {\n questionViewTypes = ds.questionTypes;\n }\n }\n\n if (questionViewTypes.length === 0) {\n const errorMsg = `No questionViewTypes found for datashapeID: ${datashapeID} in course config. Cards cannot be created.`;\n logger.error(errorMsg);\n throw new Error(errorMsg);\n }\n\n for (const questionView of questionViewTypes) {\n await createCard(questionView, courseID, dsDescriptor, noteID, tags, elo, author);\n }\n}\n\nasync function createCard(\n questionViewName: string,\n courseID: string,\n dsDescriptor: ShapeDescriptor,\n noteID: string,\n tags: string[],\n elo: CourseElo = blankCourseElo(),\n author: string\n): Promise<void> {\n const qDescriptor = NameSpacer.getQuestionDescriptor(questionViewName);\n const cfg = await getCredentialledCourseConfig(courseID);\n\n for (const rQ of cfg.questionTypes) {\n if (rQ.name === questionViewName) {\n for (const view of rQ.viewList) {\n await addCard(\n courseID,\n dsDescriptor.course,\n [noteID],\n NameSpacer.getViewString({\n course: qDescriptor.course,\n questionType: qDescriptor.questionType,\n view,\n }),\n elo,\n tags,\n author\n );\n }\n }\n }\n}\n\n/**\n *\n * Adds a card to the DB. This function is called\n * as a side effect of adding either a View or\n * DisplayableData item.\n * @param course The name of the course that the card belongs to\n * @param id_displayable_data C/PouchDB ID of the data used to hydrate the view\n * @param id_view C/PouchDB ID of the view used to display the card\n *\n * @package\n */\nasync function addCard(\n courseID: string,\n course: string,\n id_displayable_data: PouchDB.Core.DocumentId[],\n id_view: PouchDB.Core.DocumentId,\n elo: CourseElo,\n tags: string[],\n author: string\n): Promise<PouchDB.Core.Response> {\n const db = getCourseDB(courseID);\n const _id = `${DocTypePrefixes[DocType.CARD]}-${uuidv4()}`;\n const card = await db.put<CardData>({\n _id,\n course,\n id_displayable_data,\n id_view,\n docType: DocType.CARD,\n elo: elo || toCourseElo(990 + Math.round(20 * Math.random())),\n author,\n });\n for (const tag of tags) {\n logger.info(`adding tag: ${tag} to card ${card.id}`);\n await addTagToCard(courseID, card.id, tag, author, false);\n }\n return card;\n}\n\nexport async function getCredentialledCourseConfig(courseID: string): Promise<CourseConfig> {\n try {\n const db = getCourseDB(courseID);\n const ret = await db.get<CourseConfig>('CourseConfig');\n ret.courseID = courseID;\n logger.info(`Returning course config: ${JSON.stringify(ret)}`);\n return ret;\n } catch (e) {\n logger.error(`Error fetching config for ${courseID}:`, e);\n throw e;\n }\n}\n\n/**\n Assciates a tag with a card.\n\n NB: DB stores tags as separate documents, with a list of card IDs.\n Consider renaming to `addCardToTag` to reflect this.\n\n NB: tags are created if they don't already exist\n\n @param updateELO whether to update the ELO of the card with the new tag. Default true.\n @package\n*/\nexport async function addTagToCard(\n courseID: string,\n cardID: string,\n tagID: string,\n author: string,\n updateELO: boolean = true\n): Promise<PouchDB.Core.Response> {\n // todo: possible future perf. hit if tags have large #s of taggedCards.\n // In this case, should be converted to a server-request\n const prefixedTagID = getTagID(tagID);\n const courseDB = getCourseDB(courseID);\n const courseApi = new CourseDB(courseID, async () => {\n const dummySyncStrategy = {\n setupRemoteDB: () => null as any,\n startSync: () => {},\n canCreateAccount: () => false,\n canAuthenticate: () => false,\n getCurrentUsername: async () => 'DummyUser',\n };\n return BaseUser.Dummy(dummySyncStrategy);\n });\n try {\n logger.info(`Applying tag ${tagID} to card ${courseID + '-' + cardID}...`);\n const tag = await courseDB.get<Tag>(prefixedTagID);\n if (!tag.taggedCards.includes(cardID)) {\n tag.taggedCards.push(cardID);\n\n if (updateELO) {\n try {\n const eloData = await courseApi.getCardEloData([cardID]);\n const elo = eloData[0];\n elo.tags[tagID] = {\n count: 0,\n score: elo.global.score, // todo: or 1000?\n };\n await updateCardElo(courseID, cardID, elo);\n } catch (error) {\n logger.error('Failed to update ELO data for card:', cardID, error);\n }\n }\n\n return courseDB.put<Tag>(tag);\n } else throw new AlreadyTaggedErr(`Card ${cardID} is already tagged with ${tagID}`);\n } catch (e) {\n if (e instanceof AlreadyTaggedErr) {\n throw e;\n }\n\n await createTag(courseID, tagID, author);\n return addTagToCard(courseID, cardID, tagID, author, updateELO);\n }\n}\n\nasync function updateCardElo(courseID: string, cardID: string, elo: CourseElo) {\n if (elo) {\n // checking against null, undefined, NaN\n const cDB = getCourseDB(courseID);\n const card = await cDB.get<CardData>(cardID);\n logger.debug(`Replacing ${JSON.stringify(card.elo)} with ${JSON.stringify(elo)}`);\n card.elo = elo;\n return cDB.put(card); // race conditions - is it important? probably not (net-zero effect)\n }\n}\n\nclass AlreadyTaggedErr extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'AlreadyTaggedErr';\n }\n}\n\nexport function getTagID(tagName: string): string {\n const tagPrefix = DocType.TAG.valueOf() + '-';\n if (tagName.indexOf(tagPrefix) === 0) {\n return tagName;\n } else {\n return tagPrefix + tagName;\n }\n}\n\nexport function getCourseDB(courseID: string): PouchDB.Database {\n const dbName = `coursedb-${courseID}`;\n return new pouch(\n ENV.COUCHDB_SERVER_PROTOCOL + '://' + ENV.COUCHDB_SERVER_URL + dbName,\n createPouchDBConfig()\n );\n}\n","import pouch from './pouchdb-setup';\nimport { ENV } from '@db/factory';\nimport { logger } from '../../util/logger';\n\nconst courseLookupDBTitle = 'coursedb-lookup';\n\ninterface CourseLookupDoc {\n _id: string;\n _rev: string;\n name: string;\n disambiguator?: string;\n}\n\nlogger.debug(`COURSELOOKUP FILE RUNNING`);\n\n/**\n * A Lookup table of existant courses. Each docID in this DB correspondes to a\n * course database whose name is `coursedb-{docID}`\n */\nexport default class CourseLookup {\n // [ ] this db should be read only for public, admin-only for write\n // Cache for the PouchDB instance\n private static _dbInstance: PouchDB.Database | null = null;\n\n /**\n * Static getter for the PouchDB database instance.\n * Connects using ENV variables and caches the instance.\n * Throws an error if required ENV variables are not set.\n */\n private static get _db(): PouchDB.Database {\n // Return cached instance if available\n if (this._dbInstance) {\n return this._dbInstance;\n }\n\n // --- Check required environment variables ---\n if (ENV.COUCHDB_SERVER_URL === 'NOT_SET' || !ENV.COUCHDB_SERVER_URL) {\n throw new Error(\n 'CourseLookup.db: COUCHDB_SERVER_URL is not set. Ensure initializeDataLayer has been called with valid configuration.'\n );\n }\n if (ENV.COUCHDB_SERVER_PROTOCOL === 'NOT_SET' || !ENV.COUCHDB_SERVER_PROTOCOL) {\n throw new Error(\n 'CourseLookup.db: COUCHDB_SERVER_PROTOCOL is not set. Ensure initializeDataLayer has been called with valid configuration.'\n );\n }\n\n // --- Construct connection options ---\n const dbUrl = `${ENV.COUCHDB_SERVER_PROTOCOL}://${ENV.COUCHDB_SERVER_URL}/${courseLookupDBTitle}`;\n const options: PouchDB.Configuration.RemoteDatabaseConfiguration = {\n // fetch: (url, opts) => { // Optional: Add for debugging network requests\n // console.log('PouchDB fetch:', url, opts);\n // return pouch.fetch(url, opts);\n // }\n };\n\n // Add authentication if both username and password are provided\n if (ENV.COUCHDB_USERNAME && ENV.COUCHDB_PASSWORD) {\n options.auth = {\n username: ENV.COUCHDB_USERNAME,\n password: ENV.COUCHDB_PASSWORD,\n };\n logger.info(`CourseLookup: Connecting to ${dbUrl} with authentication.`);\n } else {\n logger.info(`CourseLookup: Connecting to ${dbUrl} without authentication.`);\n }\n\n // --- Create and cache the PouchDB instance ---\n try {\n this._dbInstance = new pouch(dbUrl, options);\n logger.info(`CourseLookup: Database instance created for ${courseLookupDBTitle}.`);\n return this._dbInstance;\n } catch (error) {\n logger.error(`CourseLookup: Failed to create PouchDB instance for ${dbUrl}`, error);\n // Reset cache attempt on failure\n this._dbInstance = null;\n // Re-throw the error to indicate connection failure\n throw new Error(\n `CourseLookup: Failed to initialize database connection: ${\n error instanceof Error ? error.message : String(error)\n }`\n );\n }\n }\n\n /**\n * Adds a new course to the lookup database, and returns the courseID\n * @param courseName\n * @returns\n */\n static async add(courseName: string): Promise<string> {\n const resp = await CourseLookup._db.post({\n name: courseName,\n });\n\n return resp.id;\n }\n\n /**\n * Adds a new course to the lookup database with a specific courseID\n * @param courseId The specific course ID to use\n * @param courseName The course name\n * @param disambiguator Optional disambiguator\n * @returns Promise<void>\n */\n static async addWithId(\n courseId: string,\n courseName: string,\n disambiguator?: string\n ): Promise<void> {\n const doc: Omit<CourseLookupDoc, '_rev'> = {\n _id: courseId,\n name: courseName,\n };\n\n if (disambiguator) {\n doc.disambiguator = disambiguator;\n }\n\n await CourseLookup._db.put(doc);\n }\n\n /**\n * Removes a course from the index\n * @param courseID\n */\n static async delete(courseID: string): Promise<PouchDB.Core.Response> {\n const doc = await CourseLookup._db.get(courseID);\n return await CourseLookup._db.remove(doc);\n }\n\n // [ ] rename to allCourses()\n static async allCourseWare(): Promise<CourseLookupDoc[]> {\n const resp = await CourseLookup._db.allDocs<CourseLookupDoc>({\n include_docs: true,\n });\n\n return resp.rows.map((row) => row.doc!);\n }\n\n static async updateDisambiguator(\n courseID: string,\n disambiguator?: string\n ): Promise<PouchDB.Core.Response> {\n const doc = await CourseLookup._db.get<CourseLookupDoc>(courseID);\n doc.disambiguator = disambiguator;\n return await CourseLookup._db.put(doc);\n }\n\n static async isCourse(courseID: string): Promise<boolean> {\n try {\n await CourseLookup._db.get(courseID);\n return true;\n } catch (error) {\n logger.info(`Courselookup failed:`, error);\n return false;\n }\n }\n}\n","import { ContentNavigator } from './index';\nimport type { WeightedCard } from './index';\nimport type { ContentNavigationStrategyData } from '../types/contentNavigationStrategy';\nimport type { CourseDBInterface } from '../interfaces/courseDB';\nimport type { UserDBInterface } from '../interfaces/userDB';\nimport type { StudySessionNewItem, StudySessionReviewItem } from '../interfaces/contentSource';\nimport type { ScheduledCard } from '../types/user';\nimport type { CardGenerator, GeneratorContext } from './generators/types';\nimport { logger } from '../../util/logger';\n\n// ============================================================================\n// COMPOSITE GENERATOR\n// ============================================================================\n//\n// Composes multiple generator strategies into a single generator.\n//\n// Use case: When a course has multiple generators (e.g., ELO + SRS), this\n// class merges their outputs into a unified candidate list.\n//\n// Aggregation strategy:\n// - Cards appearing in multiple generators get a frequency boost\n// - Score = average(scores) * (1 + 0.1 * (appearances - 1))\n// - This rewards cards that multiple generators agree on\n//\n// ============================================================================\n\n/**\n * Aggregation modes for combining scores from multiple generators.\n */\nexport enum AggregationMode {\n /** Use the maximum score from any generator */\n MAX = 'max',\n /** Average all scores */\n AVERAGE = 'average',\n /** Average with frequency boost: avg * (1 + 0.1 * (n-1)) */\n FREQUENCY_BOOST = 'frequencyBoost',\n}\n\nconst DEFAULT_AGGREGATION_MODE = AggregationMode.FREQUENCY_BOOST;\nconst FREQUENCY_BOOST_FACTOR = 0.1;\n\n/**\n * Composes multiple generators into a single generator.\n *\n * Implements CardGenerator for use in Pipeline architecture.\n * Also extends ContentNavigator for backward compatibility.\n *\n * Fetches candidates from all generators, deduplicates by cardId,\n * and aggregates scores based on the configured mode.\n */\nexport default class CompositeGenerator extends ContentNavigator implements CardGenerator {\n /** Human-readable name for CardGenerator interface */\n name: string = 'Composite Generator';\n\n private generators: CardGenerator[];\n private aggregationMode: AggregationMode;\n\n constructor(\n generators: CardGenerator[],\n aggregationMode: AggregationMode = DEFAULT_AGGREGATION_MODE\n ) {\n super();\n this.generators = generators;\n this.aggregationMode = aggregationMode;\n\n if (generators.length === 0) {\n throw new Error('CompositeGenerator requires at least one generator');\n }\n\n logger.debug(\n `[CompositeGenerator] Created with ${generators.length} generators, mode: ${aggregationMode}`\n );\n }\n\n /**\n * Creates a CompositeGenerator from strategy data.\n *\n * This is a convenience factory for use by PipelineAssembler.\n */\n static async fromStrategies(\n user: UserDBInterface,\n course: CourseDBInterface,\n strategies: ContentNavigationStrategyData[],\n aggregationMode: AggregationMode = DEFAULT_AGGREGATION_MODE\n ): Promise<CompositeGenerator> {\n const generators = await Promise.all(\n strategies.map((s) => ContentNavigator.create(user, course, s))\n );\n // Cast is safe because we know these are generators\n return new CompositeGenerator(generators as unknown as CardGenerator[], aggregationMode);\n }\n\n /**\n * Get weighted cards from all generators, merge and deduplicate.\n *\n * Cards appearing in multiple generators receive a score boost.\n * Provenance tracks which generators produced each card and how scores were aggregated.\n *\n * This method supports both the legacy signature (limit only) and the\n * CardGenerator interface signature (limit, context).\n *\n * @param limit - Maximum number of cards to return\n * @param context - Optional GeneratorContext passed to child generators\n */\n async getWeightedCards(limit: number, context?: GeneratorContext): Promise<WeightedCard[]> {\n // Fetch from all generators in parallel\n const results = await Promise.all(\n this.generators.map((g) => g.getWeightedCards(limit, context))\n );\n\n // Group by cardId\n const byCardId = new Map<string, WeightedCard[]>();\n for (const cards of results) {\n for (const card of cards) {\n const existing = byCardId.get(card.cardId) || [];\n existing.push(card);\n byCardId.set(card.cardId, existing);\n }\n }\n\n // Aggregate scores\n const merged: WeightedCard[] = [];\n for (const [, cards] of byCardId) {\n const aggregatedScore = this.aggregateScores(cards);\n const finalScore = Math.min(1.0, aggregatedScore); // Clamp to [0, 1]\n\n // Merge provenance from all generators that produced this card\n const mergedProvenance = cards.flatMap((c) => c.provenance);\n\n // Determine action based on whether score changed\n const initialScore = cards[0].score;\n const action =\n finalScore > initialScore ? 'boosted' : finalScore < initialScore ? 'penalized' : 'passed';\n\n // Build reason explaining the aggregation\n const reason = this.buildAggregationReason(cards, finalScore);\n\n // Append composite provenance entry\n merged.push({\n ...cards[0],\n score: finalScore,\n provenance: [\n ...mergedProvenance,\n {\n strategy: 'composite',\n strategyName: 'Composite Generator',\n strategyId: 'COMPOSITE_GENERATOR',\n action,\n score: finalScore,\n reason,\n },\n ],\n });\n }\n\n // Sort by score descending and limit\n return merged.sort((a, b) => b.score - a.score).slice(0, limit);\n }\n\n /**\n * Build human-readable reason for score aggregation.\n */\n private buildAggregationReason(cards: WeightedCard[], finalScore: number): string {\n const count = cards.length;\n const scores = cards.map((c) => c.score.toFixed(2)).join(', ');\n\n if (count === 1) {\n return `Single generator, score ${finalScore.toFixed(2)}`;\n }\n\n const strategies = cards.map((c) => c.provenance[0]?.strategy || 'unknown').join(', ');\n\n switch (this.aggregationMode) {\n case AggregationMode.MAX:\n return `Max of ${count} generators (${strategies}): scores [${scores}] → ${finalScore.toFixed(2)}`;\n\n case AggregationMode.AVERAGE:\n return `Average of ${count} generators (${strategies}): scores [${scores}] → ${finalScore.toFixed(2)}`;\n\n case AggregationMode.FREQUENCY_BOOST: {\n const avg = cards.reduce((sum, c) => sum + c.score, 0) / count;\n const boost = 1 + FREQUENCY_BOOST_FACTOR * (count - 1);\n return `Frequency boost from ${count} generators (${strategies}): avg ${avg.toFixed(2)} × ${boost.toFixed(2)} → ${finalScore.toFixed(2)}`;\n }\n\n default:\n return `Aggregated from ${count} generators: ${finalScore.toFixed(2)}`;\n }\n }\n\n /**\n * Aggregate scores from multiple generators for the same card.\n */\n private aggregateScores(cards: WeightedCard[]): number {\n const scores = cards.map((c) => c.score);\n\n switch (this.aggregationMode) {\n case AggregationMode.MAX:\n return Math.max(...scores);\n\n case AggregationMode.AVERAGE:\n return scores.reduce((sum, s) => sum + s, 0) / scores.length;\n\n case AggregationMode.FREQUENCY_BOOST: {\n const avg = scores.reduce((sum, s) => sum + s, 0) / scores.length;\n const frequencyBoost = 1 + FREQUENCY_BOOST_FACTOR * (cards.length - 1);\n return avg * frequencyBoost;\n }\n\n default:\n return scores[0];\n }\n }\n\n /**\n * Get new cards from all generators, merged and deduplicated.\n */\n async getNewCards(n?: number): Promise<StudySessionNewItem[]> {\n // For legacy method, need to filter to generators that have getNewCards\n const legacyGenerators = this.generators.filter(\n (g): g is CardGenerator & ContentNavigator => g instanceof ContentNavigator\n );\n\n const results = await Promise.all(legacyGenerators.map((g) => g.getNewCards(n)));\n\n // Deduplicate by cardID\n const seen = new Set<string>();\n const merged: StudySessionNewItem[] = [];\n\n for (const cards of results) {\n for (const card of cards) {\n if (!seen.has(card.cardID)) {\n seen.add(card.cardID);\n merged.push(card);\n }\n }\n }\n\n return n ? merged.slice(0, n) : merged;\n }\n\n /**\n * Get pending reviews from all generators, merged and deduplicated.\n */\n async getPendingReviews(): Promise<(StudySessionReviewItem & ScheduledCard)[]> {\n // For legacy method, need to filter to generators that have getPendingReviews\n const legacyGenerators = this.generators.filter(\n (g): g is CardGenerator & ContentNavigator => g instanceof ContentNavigator\n );\n\n const results = await Promise.all(legacyGenerators.map((g) => g.getPendingReviews()));\n\n // Deduplicate by cardID\n const seen = new Set<string>();\n const merged: (StudySessionReviewItem & ScheduledCard)[] = [];\n\n for (const reviews of results) {\n for (const review of reviews) {\n if (!seen.has(review.cardID)) {\n seen.add(review.cardID);\n merged.push(review);\n }\n }\n }\n\n return merged;\n }\n}\n","import { toCourseElo } from '@vue-skuilder/common';\nimport type { CourseDBInterface } from '../interfaces/courseDB';\nimport type { UserDBInterface } from '../interfaces/userDB';\nimport type { ScheduledCard } from '../types/user';\nimport { ContentNavigator } from './index';\nimport type { WeightedCard } from './index';\nimport type { CardFilter, FilterContext } from './filters/types';\nimport type { CardGenerator, GeneratorContext } from './generators/types';\nimport type { StudySessionNewItem, StudySessionReviewItem } from '../interfaces/contentSource';\nimport { logger } from '../../util/logger';\n\n// ============================================================================\n// PIPELINE\n// ============================================================================\n//\n// Executes a navigation pipeline: generator → filters → sorted results.\n//\n// Architecture:\n// cards = generator.getWeightedCards(limit, context)\n// cards = filter1.transform(cards, context)\n// cards = filter2.transform(cards, context)\n// cards = filter3.transform(cards, context)\n// return sorted(cards).slice(0, limit)\n//\n// Benefits:\n// - Clear separation: generators produce, filters transform\n// - No nested instantiation complexity\n// - Filters don't need to know about each other\n// - Shared context built once, passed to all stages\n//\n// ============================================================================\n\n/**\n * A navigation pipeline that runs a generator and applies filters sequentially.\n *\n * Implements StudyContentSource for backward compatibility with SessionController.\n *\n * ## Usage\n *\n * ```typescript\n * const pipeline = new Pipeline(\n * compositeGenerator, // or single generator\n * [eloDistanceFilter, interferenceFilter],\n * user,\n * course\n * );\n *\n * const cards = await pipeline.getWeightedCards(20);\n * ```\n */\nexport class Pipeline extends ContentNavigator {\n private generator: CardGenerator;\n private filters: CardFilter[];\n\n /**\n * Create a new pipeline.\n *\n * @param generator - The generator (or CompositeGenerator) that produces candidates\n * @param filters - Filters to apply sequentially (order doesn't matter for multipliers)\n * @param user - User database interface\n * @param course - Course database interface\n */\n constructor(\n generator: CardGenerator,\n filters: CardFilter[],\n user: UserDBInterface,\n course: CourseDBInterface\n ) {\n super();\n this.generator = generator;\n this.filters = filters;\n this.user = user;\n this.course = course;\n\n logger.debug(\n `[Pipeline] Created with generator '${generator.name}' and ${filters.length} filters: ${filters.map((f) => f.name).join(', ')}`\n );\n }\n\n /**\n * Get weighted cards by running generator and applying filters.\n *\n * 1. Build shared context (user ELO, etc.)\n * 2. Get candidates from generator (passing context)\n * 3. Apply each filter sequentially\n * 4. Remove zero-score cards\n * 5. Sort by score descending\n * 6. Return top N\n *\n * @param limit - Maximum number of cards to return\n * @returns Cards sorted by score descending\n */\n async getWeightedCards(limit: number): Promise<WeightedCard[]> {\n // Build shared context once\n const context = await this.buildContext();\n\n // Over-fetch from generator to account for filtering\n const overFetchMultiplier = 2 + this.filters.length * 0.5;\n const fetchLimit = Math.ceil(limit * overFetchMultiplier);\n\n logger.debug(\n `[Pipeline] Fetching ${fetchLimit} candidates from generator '${this.generator.name}'`\n );\n\n // Get candidates from generator, passing context\n let cards = await this.generator.getWeightedCards(fetchLimit, context);\n\n logger.debug(`[Pipeline] Generator returned ${cards.length} candidates`);\n\n // Apply filters sequentially\n for (const filter of this.filters) {\n const beforeCount = cards.length;\n cards = await filter.transform(cards, context);\n logger.debug(`[Pipeline] Filter '${filter.name}': ${beforeCount} → ${cards.length} cards`);\n }\n\n // Remove zero-score cards (hard filtered)\n cards = cards.filter((c) => c.score > 0);\n\n // Sort by score descending\n cards.sort((a, b) => b.score - a.score);\n\n // Return top N\n const result = cards.slice(0, limit);\n\n logger.debug(\n `[Pipeline] Returning ${result.length} cards (top scores: ${result\n .slice(0, 3)\n .map((c) => c.score.toFixed(2))\n .join(', ')}...)`\n );\n\n return result;\n }\n\n /**\n * Build shared context for generator and filters.\n *\n * Called once per getWeightedCards() invocation.\n * Contains data that the generator and multiple filters might need.\n *\n * The context satisfies both GeneratorContext and FilterContext interfaces.\n */\n private async buildContext(): Promise<GeneratorContext & FilterContext> {\n let userElo = 1000; // Default ELO\n\n try {\n const courseReg = await this.user!.getCourseRegDoc(this.course!.getCourseID());\n const courseElo = toCourseElo(courseReg.elo);\n userElo = courseElo.global.score;\n } catch (e) {\n logger.debug(`[Pipeline] Could not get user ELO, using default: ${e}`);\n }\n\n return {\n user: this.user!,\n course: this.course!,\n userElo,\n };\n }\n\n // ===========================================================================\n // Legacy StudyContentSource methods\n // ===========================================================================\n //\n // These delegate to the generator for backward compatibility.\n // Eventually SessionController will use getWeightedCards() exclusively.\n //\n\n /**\n * Get new cards via legacy API.\n * Delegates to the generator if it supports the legacy interface.\n */\n async getNewCards(n?: number): Promise<StudySessionNewItem[]> {\n // Check if generator has legacy method (ContentNavigator-based generators do)\n if ('getNewCards' in this.generator && typeof this.generator.getNewCards === 'function') {\n return (this.generator as ContentNavigator).getNewCards(n);\n }\n // Pure CardGenerator without legacy support - return empty\n return [];\n }\n\n /**\n * Get pending reviews via legacy API.\n * Delegates to the generator if it supports the legacy interface.\n */\n async getPendingReviews(): Promise<(StudySessionReviewItem & ScheduledCard)[]> {\n // Check if generator has legacy method (ContentNavigator-based generators do)\n if (\n 'getPendingReviews' in this.generator &&\n typeof this.generator.getPendingReviews === 'function'\n ) {\n return (this.generator as ContentNavigator).getPendingReviews();\n }\n // Pure CardGenerator without legacy support - return empty\n return [];\n }\n\n /**\n * Get the course ID for this pipeline.\n */\n getCourseID(): string {\n return this.course!.getCourseID();\n }\n}\n","import type { ContentNavigationStrategyData } from '../types/contentNavigationStrategy';\nimport { ContentNavigator, isGenerator, isFilter, Navigators } from './index';\nimport type { CardFilter } from './filters/types';\nimport type { CardGenerator } from './generators/types';\nimport { Pipeline } from './Pipeline';\nimport { DocType } from '../types/types-legacy';\nimport { logger } from '../../util/logger';\nimport type { CourseDBInterface } from '../interfaces/courseDB';\nimport type { UserDBInterface } from '../interfaces/userDB';\nimport CompositeGenerator from './CompositeGenerator';\n\n// ============================================================================\n// PIPELINE ASSEMBLER\n// ============================================================================\n//\n// Assembles navigation strategies into a Pipeline instance.\n//\n// This class is DB-agnostic: it receives strategy documents and returns an\n// assembled, ready-to-use Pipeline. This separation enables:\n// 1. Use with different DB implementations (Couch, Static, etc.)\n// 2. Future dynamic/evolutionary strategy selection\n// 3. Easy unit testing without DB mocking\n//\n// Pipeline assembly:\n// 1. Separate strategies into generators and filters by role\n// 2. Instantiate generator(s) - wrap multiple in CompositeGenerator\n// 3. Instantiate filters\n// 4. Return Pipeline(generator, filters)\n//\n// ============================================================================\n\n/**\n * Input for pipeline assembly.\n */\nexport interface PipelineAssemblerInput {\n /** All strategy documents to assemble into a pipeline */\n strategies: ContentNavigationStrategyData[];\n /** User database interface (required for instantiation) */\n user: UserDBInterface;\n /** Course database interface (required for instantiation) */\n course: CourseDBInterface;\n}\n\n/**\n * Result of pipeline assembly.\n */\nexport interface PipelineAssemblyResult {\n /** The assembled pipeline, or null if assembly failed */\n pipeline: Pipeline | null;\n /** Generator strategies found (for informational purposes) */\n generatorStrategies: ContentNavigationStrategyData[];\n /** Filter strategies found (for informational purposes) */\n filterStrategies: ContentNavigationStrategyData[];\n /** Warnings encountered during assembly (logged but non-fatal) */\n warnings: string[];\n}\n\n/**\n * Assembles navigation strategies into a Pipeline.\n *\n * Instantiates generators and filters from strategy documents and\n * composes them into a ready-to-use Pipeline instance.\n */\nexport class PipelineAssembler {\n /**\n * Assembles a navigation pipeline from strategy documents.\n *\n * 1. Separates into generators and filters by role\n * 2. Validates at least one generator exists (or creates default ELO)\n * 3. Instantiates generators - wraps multiple in CompositeGenerator\n * 4. Instantiates filters\n * 5. Returns Pipeline(generator, filters)\n *\n * @param input - Strategy documents plus user/course interfaces\n * @returns Assembled pipeline and any warnings\n */\n async assemble(input: PipelineAssemblerInput): Promise<PipelineAssemblyResult> {\n const { strategies, user, course } = input;\n const warnings: string[] = [];\n\n if (strategies.length === 0) {\n return {\n pipeline: null,\n generatorStrategies: [],\n filterStrategies: [],\n warnings,\n };\n }\n\n // Separate generators from filters\n const generatorStrategies: ContentNavigationStrategyData[] = [];\n const filterStrategies: ContentNavigationStrategyData[] = [];\n\n for (const s of strategies) {\n if (isGenerator(s.implementingClass)) {\n generatorStrategies.push(s);\n } else if (isFilter(s.implementingClass)) {\n filterStrategies.push(s);\n } else {\n // Unknown strategy type — skip with warning\n warnings.push(`Unknown strategy type '${s.implementingClass}', skipping: ${s.name}`);\n }\n }\n\n // If no generator but filters exist, use default ELO generator\n if (generatorStrategies.length === 0) {\n if (filterStrategies.length > 0) {\n logger.debug(\n '[PipelineAssembler] No generator found, using default ELO with configured filters'\n );\n generatorStrategies.push(this.makeDefaultEloStrategy(course.getCourseID()));\n } else {\n warnings.push('No generator strategy found');\n return {\n pipeline: null,\n generatorStrategies: [],\n filterStrategies: [],\n warnings,\n };\n }\n }\n\n // Instantiate generators\n let generator: CardGenerator;\n\n if (generatorStrategies.length === 1) {\n // Single generator\n const nav = await ContentNavigator.create(user, course, generatorStrategies[0]);\n generator = nav as unknown as CardGenerator;\n logger.debug(`[PipelineAssembler] Using single generator: ${generatorStrategies[0].name}`);\n } else {\n // Multiple generators - wrap in CompositeGenerator\n logger.debug(\n `[PipelineAssembler] Using CompositeGenerator for ${generatorStrategies.length} generators: ${generatorStrategies.map((g) => g.name).join(', ')}`\n );\n generator = await CompositeGenerator.fromStrategies(user, course, generatorStrategies);\n }\n\n // Instantiate filters\n const filters: CardFilter[] = [];\n\n // Sort filters alphabetically for deterministic ordering\n const sortedFilterStrategies = [...filterStrategies].sort((a, b) =>\n a.name.localeCompare(b.name)\n );\n\n for (const filterStrategy of sortedFilterStrategies) {\n try {\n const nav = await ContentNavigator.create(user, course, filterStrategy);\n // The navigator implements CardFilter\n if ('transform' in nav && typeof nav.transform === 'function') {\n filters.push(nav as unknown as CardFilter);\n logger.debug(`[PipelineAssembler] Added filter: ${filterStrategy.name}`);\n } else {\n warnings.push(\n `Filter '${filterStrategy.name}' does not implement CardFilter.transform(), skipping`\n );\n }\n } catch (e) {\n warnings.push(`Failed to instantiate filter '${filterStrategy.name}': ${e}`);\n }\n }\n\n // Build pipeline\n const pipeline = new Pipeline(generator, filters, user, course);\n\n logger.debug(\n `[PipelineAssembler] Assembled pipeline with ${generatorStrategies.length} generator(s) and ${filters.length} filter(s)`\n );\n\n return {\n pipeline,\n generatorStrategies,\n filterStrategies: sortedFilterStrategies,\n warnings,\n };\n }\n\n /**\n * Creates a default ELO generator strategy.\n * Used when filters are configured but no generator is specified.\n */\n private makeDefaultEloStrategy(courseId: string): ContentNavigationStrategyData {\n return {\n _id: 'NAVIGATION_STRATEGY-ELO-default',\n course: courseId,\n docType: DocType.NAVIGATION_STRATEGY,\n name: 'ELO (default)',\n description: 'Default ELO-based generator',\n implementingClass: Navigators.ELO,\n serializedData: '',\n };\n }\n}\n","import type { ScheduledCard } from '../types/user';\nimport type { CourseDBInterface } from '../interfaces/courseDB';\nimport type { UserDBInterface } from '../interfaces/userDB';\nimport { ContentNavigator } from './index';\nimport type { WeightedCard } from './index';\nimport type { CourseElo } from '@vue-skuilder/common';\nimport { toCourseElo } from '@vue-skuilder/common';\nimport type { StudySessionReviewItem, StudySessionNewItem, QualifiedCardID } from '..';\nimport type { CardGenerator, GeneratorContext } from './generators/types';\n\n// ============================================================================\n// ELO NAVIGATOR\n// ============================================================================\n//\n// A generator strategy that selects new cards based on ELO proximity.\n//\n// Cards closer to the user's skill level (ELO) receive higher scores.\n// This ensures learners see content matched to their current ability.\n//\n// NOTE: This generator only handles NEW cards. Reviews are handled by\n// SRSNavigator. Use CompositeGenerator to combine both.\n//\n// ============================================================================\n\n/**\n * A navigation strategy that scores new cards by ELO proximity.\n *\n * Implements CardGenerator for use in Pipeline architecture.\n * Also extends ContentNavigator for backward compatibility with legacy code.\n *\n * Higher scores indicate better ELO match:\n * - Cards at user's ELO level score highest\n * - Score decreases with ELO distance\n *\n * Only returns new cards - use SRSNavigator for reviews.\n */\nexport default class ELONavigator extends ContentNavigator implements CardGenerator {\n /** Human-readable name for CardGenerator interface */\n name: string;\n\n constructor(\n user: UserDBInterface,\n course: CourseDBInterface,\n strategyData?: { name: string; _id: string }\n // The ELO strategy is non-parameterized.\n //\n // It instead relies on existing meta data from the course and user with respect to\n // ELO scores - it uses those to select cards matched to user skill level.\n ) {\n super(user, course, strategyData as any);\n this.name = strategyData?.name || 'ELO';\n }\n\n async getPendingReviews(): Promise<(StudySessionReviewItem & ScheduledCard)[]> {\n type ratedReview = ScheduledCard & CourseElo;\n\n const reviews = await this.user.getPendingReviews(this.course.getCourseID()); // todo: this adds a db round trip - should be server side\n const elo = await this.course.getCardEloData(reviews.map((r) => r.cardId));\n\n const ratedReviews = reviews.map((r, i) => {\n const ratedR: ratedReview = {\n ...r,\n ...elo[i],\n };\n return ratedR;\n });\n\n ratedReviews.sort((a, b) => {\n return a.global.score - b.global.score;\n });\n\n return ratedReviews.map((r) => {\n return {\n ...r,\n contentSourceType: 'course',\n contentSourceID: this.course.getCourseID(),\n cardID: r.cardId,\n courseID: r.courseId,\n qualifiedID: `${r.courseId}-${r.cardId}`,\n reviewID: r._id,\n status: 'review',\n };\n });\n }\n\n async getNewCards(limit: number = 99): Promise<StudySessionNewItem[]> {\n const activeCards = await this.user.getActiveCards();\n return (\n await this.course.getCardsCenteredAtELO(\n { limit: limit, elo: 'user' },\n (c: QualifiedCardID) => {\n if (activeCards.some((ac) => c.cardID === ac.cardID)) {\n return false;\n } else {\n return true;\n }\n }\n )\n ).map((c) => {\n return {\n ...c,\n status: 'new',\n };\n });\n }\n\n /**\n * Get new cards with suitability scores based on ELO distance.\n *\n * Cards closer to user's ELO get higher scores.\n * Score formula: max(0, 1 - distance / 500)\n *\n * NOTE: This generator only handles NEW cards. Reviews are handled by\n * SRSNavigator. Use CompositeGenerator to combine both.\n *\n * This method supports both the legacy signature (limit only) and the\n * CardGenerator interface signature (limit, context).\n *\n * @param limit - Maximum number of cards to return\n * @param context - Optional GeneratorContext (used when called via Pipeline)\n */\n async getWeightedCards(limit: number, context?: GeneratorContext): Promise<WeightedCard[]> {\n // Determine user ELO - from context if available, otherwise fetch\n let userGlobalElo: number;\n if (context?.userElo !== undefined) {\n userGlobalElo = context.userElo;\n } else {\n const courseReg = await this.user.getCourseRegDoc(this.course.getCourseID());\n const userElo = toCourseElo(courseReg.elo);\n userGlobalElo = userElo.global.score;\n }\n\n // Get new cards (existing logic)\n const newCards = await this.getNewCards(limit);\n\n // Get ELO data for all cards in one batch\n const cardIds = newCards.map((c) => c.cardID);\n const cardEloData = await this.course.getCardEloData(cardIds);\n\n // Score new cards by ELO distance\n const scored: WeightedCard[] = newCards.map((c, i) => {\n const cardElo = cardEloData[i]?.global?.score ?? 1000;\n const distance = Math.abs(cardElo - userGlobalElo);\n const score = Math.max(0, 1 - distance / 500);\n\n return {\n cardId: c.cardID,\n courseId: c.courseID,\n score,\n provenance: [\n {\n strategy: 'elo',\n strategyName: this.strategyName || this.name,\n strategyId: this.strategyId || 'NAVIGATION_STRATEGY-ELO-default',\n action: 'generated',\n score,\n reason: `ELO distance ${Math.round(distance)} (card: ${Math.round(cardElo)}, user: ${Math.round(userGlobalElo)}), new card`,\n },\n ],\n };\n });\n\n // Sort by score descending\n scored.sort((a, b) => b.score - a.score);\n\n return scored.slice(0, limit);\n }\n}\n","import type { WeightedCard } from '../index';\nimport type { CardFilter, FilterContext } from './types';\n\n// ============================================================================\n// ELO DISTANCE FILTER\n// ============================================================================\n//\n// Penalizes cards that are far from the user's current ELO using a smooth curve.\n//\n// This filter addresses cross-strategy coordination:\n// - SRS generates reviews based on scheduling\n// - But some scheduled cards may be \"below\" the user's current level\n// - Or \"above\" (shouldn't happen often, but possible)\n//\n// By applying ELO distance penalties, we can:\n// - Deprioritize reviews the user has \"moved beyond\"\n// - Deprioritize cards that are too hard for current skill level\n//\n// The penalty curve is smooth (no discontinuities) using a Gaussian-like decay.\n//\n// ============================================================================\n\n/**\n * Configuration for the ELO distance filter.\n */\nexport interface EloDistanceConfig {\n /**\n * The ELO distance at which the multiplier is ~0.6 (one standard deviation).\n * Default: 200 ELO points.\n *\n * - At distance 0: multiplier ≈ 1.0\n * - At distance = halfLife: multiplier ≈ 0.6\n * - At distance = 2 * halfLife: multiplier ≈ 0.37\n * - At distance = 3 * halfLife: multiplier ≈ 0.22\n */\n halfLife?: number;\n\n /**\n * Minimum multiplier (floor) to prevent scores from going too low.\n * Default: 0.3\n */\n minMultiplier?: number;\n\n /**\n * Maximum multiplier (ceiling). Usually 1.0 (no boost for close cards).\n * Default: 1.0\n */\n maxMultiplier?: number;\n}\n\nconst DEFAULT_HALF_LIFE = 200;\nconst DEFAULT_MIN_MULTIPLIER = 0.3;\nconst DEFAULT_MAX_MULTIPLIER = 1.0;\n\n/**\n * Compute the multiplier for a given ELO distance using Gaussian decay.\n *\n * Formula: minMultiplier + (maxMultiplier - minMultiplier) * exp(-(distance/halfLife)^2)\n *\n * This produces a smooth bell curve centered at distance=0:\n * - At distance 0: multiplier = maxMultiplier (1.0)\n * - As distance increases: multiplier smoothly decays toward minMultiplier\n * - No discontinuities or sudden jumps\n */\nfunction computeMultiplier(\n distance: number,\n halfLife: number,\n minMultiplier: number,\n maxMultiplier: number\n): number {\n // Gaussian decay: exp(-(d/h)^2)\n const normalizedDistance = distance / halfLife;\n const decay = Math.exp(-(normalizedDistance * normalizedDistance));\n\n // Scale between min and max\n return minMultiplier + (maxMultiplier - minMultiplier) * decay;\n}\n\n/**\n * Create an ELO distance filter.\n *\n * Penalizes cards that are far from the user's current ELO level\n * using a smooth Gaussian decay curve. No discontinuities.\n *\n * @param config - Optional configuration for the decay curve\n * @returns A CardFilter that applies ELO distance penalties\n */\nexport function createEloDistanceFilter(config?: EloDistanceConfig): CardFilter {\n const halfLife = config?.halfLife ?? DEFAULT_HALF_LIFE;\n const minMultiplier = config?.minMultiplier ?? DEFAULT_MIN_MULTIPLIER;\n const maxMultiplier = config?.maxMultiplier ?? DEFAULT_MAX_MULTIPLIER;\n\n return {\n name: 'ELO Distance Filter',\n\n async transform(cards: WeightedCard[], context: FilterContext): Promise<WeightedCard[]> {\n const { course, userElo } = context;\n\n // Batch fetch ELO data for all cards\n const cardIds = cards.map((c) => c.cardId);\n const cardElos = await course.getCardEloData(cardIds);\n\n return cards.map((card, i) => {\n const cardElo = cardElos[i]?.global?.score ?? 1000;\n const distance = Math.abs(cardElo - userElo);\n const multiplier = computeMultiplier(distance, halfLife, minMultiplier, maxMultiplier);\n const newScore = card.score * multiplier;\n\n const action = multiplier < maxMultiplier - 0.01 ? 'penalized' : 'passed';\n\n return {\n ...card,\n score: newScore,\n provenance: [\n ...card.provenance,\n {\n strategy: 'eloDistance',\n strategyName: 'ELO Distance Filter',\n strategyId: 'ELO_DISTANCE_FILTER',\n action,\n score: newScore,\n reason: `ELO distance ${Math.round(distance)} (card: ${Math.round(cardElo)}, user: ${Math.round(userElo)}) → ${multiplier.toFixed(2)}x`,\n },\n ],\n };\n });\n },\n };\n}\n\n// Export defaults for testing\nexport { DEFAULT_HALF_LIFE, DEFAULT_MIN_MULTIPLIER, DEFAULT_MAX_MULTIPLIER };\n","// Filter types and interfaces\nexport type { CardFilter, FilterContext, CardFilterFactory } from './types';\n\n// Filter implementations\nexport { createEloDistanceFilter } from './eloDistance';\nexport type { EloDistanceConfig } from './eloDistance';\n","import type { WeightedCard } from '../index';\nimport type { CourseDBInterface } from '../../interfaces/courseDB';\nimport type { UserDBInterface } from '../../interfaces/userDB';\n\n// ============================================================================\n// CARD FILTER INTERFACE\n// ============================================================================\n//\n// Filters are pure transforms on a list of WeightedCards.\n// They replace the delegate-wrapping pattern with a simpler model:\n//\n// cards = Generator.getWeightedCards()\n// cards = Filter1.transform(cards, context)\n// cards = Filter2.transform(cards, context)\n//\n// Benefits:\n// - No nested instantiation\n// - Filters don't need to know about delegates\n// - Easy to add/remove/reorder filters\n// - Natural place to hydrate shared data before filter pass\n//\n// All filters should be score multipliers (including score: 0 for exclusion).\n// This means filter order doesn't affect final scores.\n//\n// ============================================================================\n\n/**\n * Shared context available to all filters in a pipeline.\n *\n * Built once per getWeightedCards() call and passed to each filter.\n * This avoids repeated lookups for common data like user ELO.\n */\nexport interface FilterContext {\n /** User database interface */\n user: UserDBInterface;\n\n /** Course database interface */\n course: CourseDBInterface;\n\n /** User's global ELO score for this course */\n userElo: number;\n\n // Future extensions:\n // - hydrated tags for all cards (batch lookup)\n // - user's tag-level ELO data\n // - course config\n}\n\n/**\n * A filter that transforms a list of weighted cards.\n *\n * Filters are pure transforms - they receive cards and context,\n * and return a modified list of cards. No delegate wrapping,\n * no side effects beyond provenance tracking.\n *\n * ## Implementation Guidelines\n *\n * 1. **Append provenance**: Every filter should add a StrategyContribution\n * entry documenting its decision for each card.\n *\n * 2. **Use multipliers**: Adjust scores by multiplying, not replacing.\n * This ensures filter order doesn't matter.\n *\n * 3. **Score 0 for exclusion**: To exclude a card, set score to 0.\n * Don't filter it out - let provenance show why it was excluded.\n *\n * 4. **Don't sort**: The Pipeline handles final sorting.\n * Filters just transform scores.\n *\n * ## Example Implementation\n *\n * ```typescript\n * const myFilter: CardFilter = {\n * name: 'My Filter',\n * async transform(cards, context) {\n * return cards.map(card => {\n * const multiplier = computeMultiplier(card, context);\n * const newScore = card.score * multiplier;\n * return {\n * ...card,\n * score: newScore,\n * provenance: [...card.provenance, {\n * strategy: 'myFilter',\n * strategyName: 'My Filter',\n * strategyId: 'MY_FILTER',\n * action: multiplier < 1 ? 'penalized' : 'passed',\n * score: newScore,\n * reason: 'Explanation of decision'\n * }]\n * };\n * });\n * }\n * };\n * ```\n */\nexport interface CardFilter {\n /** Human-readable name for this filter */\n name: string;\n\n /**\n * Transform a list of weighted cards.\n *\n * @param cards - Cards to transform (already scored by generator)\n * @param context - Shared context (user, course, userElo, etc.)\n * @returns Transformed cards with updated scores and provenance\n */\n transform(cards: WeightedCard[], context: FilterContext): Promise<WeightedCard[]>;\n}\n\n/**\n * Factory function type for creating filters from configuration.\n *\n * Used by PipelineAssembler to instantiate filters from strategy documents.\n */\nexport type CardFilterFactory<TConfig = unknown> = (config: TConfig) => CardFilter;\n","// Generator types and interfaces\nexport type { CardGenerator, GeneratorContext, CardGeneratorFactory } from './types';\n","import type { WeightedCard } from '../index';\nimport type { CourseDBInterface } from '../../interfaces/courseDB';\nimport type { UserDBInterface } from '../../interfaces/userDB';\n\n// ============================================================================\n// CARD GENERATOR INTERFACE\n// ============================================================================\n//\n// Generators produce candidate cards with initial scores.\n// They are the \"source\" stage of a navigation pipeline.\n//\n// Examples: ELO (skill proximity), SRS (review scheduling), HardcodedOrder\n//\n// Generators differ from filters:\n// - Generators: produce candidates from DB queries, assign initial scores\n// - Filters: transform existing candidates, adjust scores with multipliers\n//\n// The Pipeline class orchestrates: Generator → Filter₁ → Filter₂ → ... → Results\n//\n// ============================================================================\n\n/**\n * Context available to generators when producing candidates.\n *\n * Built once per getWeightedCards() call by the Pipeline.\n */\nexport interface GeneratorContext {\n /** User database interface */\n user: UserDBInterface;\n\n /** Course database interface */\n course: CourseDBInterface;\n\n /** User's global ELO score for this course */\n userElo: number;\n\n // Future extensions:\n // - user's tag-level ELO data\n // - course config\n // - session state (cards already seen this session)\n}\n\n/**\n * A generator that produces candidate cards with initial scores.\n *\n * Generators are the \"source\" stage of a navigation pipeline.\n * They query the database for eligible cards and assign initial\n * suitability scores based on their strategy (ELO proximity,\n * review urgency, fixed order, etc.).\n *\n * ## Implementation Guidelines\n *\n * 1. **Create provenance**: Each card should have a provenance entry\n * with action='generated' documenting why it was selected.\n *\n * 2. **Score semantics**: Higher scores = more suitable for presentation.\n * Scores should be in [0, 1] range for composability.\n *\n * 3. **Limit handling**: Respect the limit parameter, but may over-fetch\n * internally if needed for scoring accuracy.\n *\n * 4. **Sort before returning**: Return cards sorted by score descending.\n *\n * ## Example Implementation\n *\n * ```typescript\n * const myGenerator: CardGenerator = {\n * name: 'My Generator',\n * async getWeightedCards(limit, context) {\n * const candidates = await fetchCandidates(context.course, limit);\n * return candidates.map(c => ({\n * cardId: c.id,\n * courseId: context.course.getCourseID(),\n * score: computeScore(c, context),\n * provenance: [{\n * strategy: 'myGenerator',\n * strategyName: 'My Generator',\n * strategyId: 'MY_GENERATOR',\n * action: 'generated',\n * score: computeScore(c, context),\n * reason: 'Explanation of selection'\n * }]\n * }));\n * }\n * };\n * ```\n */\nexport interface CardGenerator {\n /** Human-readable name for this generator */\n name: string;\n\n /**\n * Produce candidate cards with initial scores.\n *\n * @param limit - Maximum number of cards to return\n * @param context - Shared context (user, course, userElo, etc.)\n * @returns Cards sorted by score descending, with provenance\n */\n getWeightedCards(limit: number, context: GeneratorContext): Promise<WeightedCard[]>;\n}\n\n/**\n * Factory function type for creating generators from configuration.\n *\n * Used by PipelineAssembler to instantiate generators from strategy documents.\n */\nexport type CardGeneratorFactory<TConfig = unknown> = (config: TConfig) => CardGenerator;\n","import type {\n CourseDBInterface,\n QualifiedCardID,\n StudySessionNewItem,\n StudySessionReviewItem,\n UserDBInterface,\n} from '..';\nimport type { ContentNavigationStrategyData } from '../types/contentNavigationStrategy';\nimport type { ScheduledCard } from '../types/user';\nimport { ContentNavigator } from './index';\nimport type { WeightedCard } from './index';\nimport type { CardGenerator, GeneratorContext } from './generators/types';\nimport { logger } from '../../util/logger';\n\n// ============================================================================\n// HARDCODED ORDER NAVIGATOR\n// ============================================================================\n//\n// A generator strategy that presents cards in a fixed, author-defined order.\n//\n// Use case: When course authors want explicit control over content sequencing,\n// e.g., teaching letters in a specific pedagogical order.\n//\n// The order is defined in serializedData as a JSON array of card IDs.\n// Earlier positions in the array get higher scores.\n//\n// ============================================================================\n\n/**\n * A navigation strategy that presents cards in a fixed order.\n *\n * Implements CardGenerator for use in Pipeline architecture.\n * Also extends ContentNavigator for backward compatibility with legacy code.\n *\n * Scoring:\n * - Earlier cards in the sequence get higher scores\n * - Reviews get score 1.0 (highest priority)\n * - New cards scored by position: 1.0 - (position / total) * 0.5\n */\nexport default class HardcodedOrderNavigator extends ContentNavigator implements CardGenerator {\n /** Human-readable name for CardGenerator interface */\n name: string;\n\n private orderedCardIds: string[] = [];\n\n constructor(\n user: UserDBInterface,\n course: CourseDBInterface,\n strategyData: ContentNavigationStrategyData\n ) {\n super(user, course, strategyData);\n this.name = strategyData.name || 'Hardcoded Order';\n\n if (strategyData.serializedData) {\n try {\n this.orderedCardIds = JSON.parse(strategyData.serializedData);\n } catch (e) {\n logger.error('Failed to parse serializedData for HardcodedOrderNavigator', e);\n }\n }\n }\n\n async getPendingReviews(): Promise<(StudySessionReviewItem & ScheduledCard)[]> {\n const reviews = await this.user.getPendingReviews(this.course.getCourseID());\n return reviews.map((r) => {\n return {\n ...r,\n contentSourceType: 'course',\n contentSourceID: this.course.getCourseID(),\n cardID: r.cardId,\n courseID: r.courseId,\n reviewID: r._id,\n status: 'review',\n };\n });\n }\n\n async getNewCards(limit: number = 99): Promise<StudySessionNewItem[]> {\n const activeCardIds = (await this.user.getActiveCards()).map((c: QualifiedCardID) => c.cardID);\n\n const newCardIds = this.orderedCardIds.filter((cardId) => !activeCardIds.includes(cardId));\n\n const cardsToReturn = newCardIds.slice(0, limit);\n\n return cardsToReturn.map((cardId) => {\n return {\n cardID: cardId,\n courseID: this.course.getCourseID(),\n contentSourceType: 'course',\n contentSourceID: this.course.getCourseID(),\n status: 'new',\n };\n });\n }\n\n /**\n * Get cards in hardcoded order with scores based on position.\n *\n * Earlier cards in the sequence get higher scores.\n * Score formula: 1.0 - (position / totalCards) * 0.5\n * This ensures scores range from 1.0 (first card) to 0.5+ (last card).\n *\n * This method supports both the legacy signature (limit only) and the\n * CardGenerator interface signature (limit, context).\n *\n * @param limit - Maximum number of cards to return\n * @param _context - Optional GeneratorContext (currently unused, but required for interface)\n */\n async getWeightedCards(limit: number, _context?: GeneratorContext): Promise<WeightedCard[]> {\n const activeCardIds = (await this.user.getActiveCards()).map((c: QualifiedCardID) => c.cardID);\n const reviews = await this.getPendingReviews();\n\n // Filter out already-active cards\n const newCardIds = this.orderedCardIds.filter((cardId) => !activeCardIds.includes(cardId));\n\n const totalCards = newCardIds.length;\n\n // Score new cards by position in sequence\n const scoredNew: WeightedCard[] = newCardIds.slice(0, limit).map((cardId, index) => {\n const position = index + 1;\n const score = Math.max(0.5, 1.0 - (index / totalCards) * 0.5);\n\n return {\n cardId,\n courseId: this.course.getCourseID(),\n score,\n provenance: [\n {\n strategy: 'hardcodedOrder',\n strategyName: this.strategyName || this.name,\n strategyId: this.strategyId || 'NAVIGATION_STRATEGY-hardcoded',\n action: 'generated',\n score,\n reason: `Position ${position} of ${totalCards} in fixed sequence, new card`,\n },\n ],\n };\n });\n\n // Score reviews at 1.0 (highest priority)\n const scoredReviews: WeightedCard[] = reviews.map((r) => ({\n cardId: r.cardID,\n courseId: r.courseID,\n score: 1.0,\n provenance: [\n {\n strategy: 'hardcodedOrder',\n strategyName: this.strategyName || this.name,\n strategyId: this.strategyId || 'NAVIGATION_STRATEGY-hardcoded',\n action: 'generated',\n score: 1.0,\n reason: 'Scheduled review, highest priority',\n },\n ],\n }));\n\n // Combine (reviews already sorted at top due to score=1.0)\n const all = [...scoredReviews, ...scoredNew];\n all.sort((a, b) => b.score - a.score);\n\n return all.slice(0, limit);\n }\n}\n","import type { ScheduledCard } from '../types/user';\nimport type { CourseDBInterface } from '../interfaces/courseDB';\nimport type { UserDBInterface } from '../interfaces/userDB';\nimport { ContentNavigator } from './index';\nimport type { WeightedCard } from './index';\nimport type { ContentNavigationStrategyData } from '../types/contentNavigationStrategy';\nimport type { StudySessionReviewItem, StudySessionNewItem } from '..';\nimport type { CardFilter, FilterContext } from './filters/types';\nimport { toCourseElo } from '@vue-skuilder/common';\n\n/**\n * A single prerequisite requirement for a tag.\n * Each prerequisite refers to one tag with its own mastery threshold.\n */\ninterface TagPrerequisite {\n /** The tag that must be mastered */\n tag: string;\n /** Thresholds for considering this prerequisite tag \"mastered\" */\n masteryThreshold?: {\n /** Minimum ELO score for mastery. If not set, uses avgElo comparison */\n minElo?: number;\n /** Minimum interaction count (default: 3) */\n minCount?: number;\n };\n}\n\n/**\n * Configuration for the HierarchyDefinition strategy\n */\nexport interface HierarchyConfig {\n /** Map of tag ID to its list of prerequisites (each with individual thresholds) */\n prerequisites: {\n [tagId: string]: TagPrerequisite[];\n };\n}\n\nconst DEFAULT_MIN_COUNT = 3;\n\n/**\n * A filter strategy that gates cards based on prerequisite mastery.\n *\n * Cards are locked until the user masters all prerequisite tags.\n * Locked cards receive score: 0 (hard filter).\n *\n * Mastery is determined by:\n * - User's ELO for the tag exceeds threshold (or avgElo if not specified)\n * - User has minimum interaction count with the tag\n *\n * Tags with no prerequisites are always unlocked.\n *\n * Implements CardFilter for use in Pipeline architecture.\n * Also extends ContentNavigator for backward compatibility.\n */\nexport default class HierarchyDefinitionNavigator extends ContentNavigator implements CardFilter {\n private config: HierarchyConfig;\n private _strategyData: ContentNavigationStrategyData;\n\n /** Human-readable name for CardFilter interface */\n name: string;\n\n constructor(\n user: UserDBInterface,\n course: CourseDBInterface,\n _strategyData: ContentNavigationStrategyData\n ) {\n super(user, course, _strategyData);\n this._strategyData = _strategyData;\n this.config = this.parseConfig(_strategyData.serializedData);\n this.name = _strategyData.name || 'Hierarchy Definition';\n }\n\n private parseConfig(serializedData: string): HierarchyConfig {\n try {\n const parsed = JSON.parse(serializedData);\n return {\n prerequisites: parsed.prerequisites || {},\n };\n } catch {\n // Return safe defaults if parsing fails\n return {\n prerequisites: {},\n };\n }\n }\n\n /**\n * Check if a specific prerequisite is satisfied\n */\n private isPrerequisiteMet(\n prereq: TagPrerequisite,\n userTagElo: { score: number; count: number } | undefined,\n userGlobalElo: number\n ): boolean {\n if (!userTagElo) return false;\n\n const minCount = prereq.masteryThreshold?.minCount ?? DEFAULT_MIN_COUNT;\n if (userTagElo.count < minCount) return false;\n\n if (prereq.masteryThreshold?.minElo !== undefined) {\n return userTagElo.score >= prereq.masteryThreshold.minElo;\n } else {\n // Default: user ELO for tag > global user ELO (proxy for \"above average\")\n return userTagElo.score >= userGlobalElo;\n }\n }\n\n /**\n * Get the set of tags the user has mastered.\n * A tag is \"mastered\" if it appears as a prerequisite somewhere and meets its threshold.\n */\n private async getMasteredTags(context: FilterContext): Promise<Set<string>> {\n const mastered = new Set<string>();\n\n try {\n const courseReg = await context.user.getCourseRegDoc(context.course.getCourseID());\n const userElo = toCourseElo(courseReg.elo);\n\n // Collect all unique prerequisite tags and check mastery for each\n for (const prereqs of Object.values(this.config.prerequisites)) {\n for (const prereq of prereqs) {\n const tagElo = userElo.tags[prereq.tag];\n if (this.isPrerequisiteMet(prereq, tagElo, userElo.global.score)) {\n mastered.add(prereq.tag);\n }\n }\n }\n } catch {\n // If we can't get user data, return empty set (no tags mastered)\n }\n\n return mastered;\n }\n\n /**\n * Get the set of tags that are unlocked (prerequisites met)\n */\n private getUnlockedTags(masteredTags: Set<string>): Set<string> {\n const unlocked = new Set<string>();\n\n for (const [tagId, prereqs] of Object.entries(this.config.prerequisites)) {\n const allPrereqsMet = prereqs.every((prereq) => masteredTags.has(prereq.tag));\n if (allPrereqsMet) {\n unlocked.add(tagId);\n }\n }\n\n return unlocked;\n }\n\n /**\n * Check if a tag has prerequisites defined in config\n */\n private hasPrerequisites(tagId: string): boolean {\n return tagId in this.config.prerequisites;\n }\n\n /**\n * Check if a card is unlocked and generate reason.\n */\n private async checkCardUnlock(\n cardId: string,\n course: CourseDBInterface,\n unlockedTags: Set<string>,\n masteredTags: Set<string>\n ): Promise<{ isUnlocked: boolean; reason: string }> {\n try {\n const tagResponse = await course.getAppliedTags(cardId);\n const cardTags = tagResponse.rows.map((row) => row.value?.name || row.key);\n\n // Check each tag's prerequisite status\n const lockedTags = cardTags.filter(\n (tag) => this.hasPrerequisites(tag) && !unlockedTags.has(tag)\n );\n\n if (lockedTags.length === 0) {\n const tagList = cardTags.length > 0 ? cardTags.join(', ') : 'none';\n return {\n isUnlocked: true,\n reason: `Prerequisites met, tags: ${tagList}`,\n };\n }\n\n // Find missing prerequisites for locked tags\n const missingPrereqs = lockedTags.flatMap((tag) => {\n const prereqs = this.config.prerequisites[tag] || [];\n return prereqs.filter((p) => !masteredTags.has(p.tag)).map((p) => p.tag);\n });\n\n return {\n isUnlocked: false,\n reason: `Blocked: missing prerequisites ${missingPrereqs.join(', ')} for tags ${lockedTags.join(', ')}`,\n };\n } catch {\n // If we can't get tags, assume unlocked (fail open)\n return {\n isUnlocked: true,\n reason: 'Prerequisites check skipped (tag lookup failed)',\n };\n }\n }\n\n /**\n * CardFilter.transform implementation.\n *\n * Apply prerequisite gating to cards. Cards with locked tags receive score: 0.\n */\n async transform(cards: WeightedCard[], context: FilterContext): Promise<WeightedCard[]> {\n // Get mastery state\n const masteredTags = await this.getMasteredTags(context);\n const unlockedTags = this.getUnlockedTags(masteredTags);\n\n // Apply prerequisite gating as score multiplier\n const gated: WeightedCard[] = [];\n\n for (const card of cards) {\n const { isUnlocked, reason } = await this.checkCardUnlock(\n card.cardId,\n context.course,\n unlockedTags,\n masteredTags\n );\n const finalScore = isUnlocked ? card.score : 0;\n const action = isUnlocked ? 'passed' : 'penalized';\n\n gated.push({\n ...card,\n score: finalScore,\n provenance: [\n ...card.provenance,\n {\n strategy: 'hierarchyDefinition',\n strategyName: this.strategyName || this.name,\n strategyId: this.strategyId || 'NAVIGATION_STRATEGY-hierarchy',\n action,\n score: finalScore,\n reason,\n },\n ],\n });\n }\n\n return gated;\n }\n\n /**\n * Legacy getWeightedCards - now throws as filters should not be used as generators.\n *\n * Use transform() via Pipeline instead.\n */\n async getWeightedCards(_limit: number): Promise<WeightedCard[]> {\n throw new Error(\n 'HierarchyDefinitionNavigator is a filter and should not be used as a generator. ' +\n 'Use Pipeline with a generator and this filter via transform().'\n );\n }\n\n // Legacy methods - stub implementations since filters don't generate cards\n\n async getNewCards(_n?: number): Promise<StudySessionNewItem[]> {\n return [];\n }\n\n async getPendingReviews(): Promise<(StudySessionReviewItem & ScheduledCard)[]> {\n return [];\n }\n}\n","import type { ScheduledCard } from '../types/user';\nimport type { CourseDBInterface } from '../interfaces/courseDB';\nimport type { UserDBInterface } from '../interfaces/userDB';\nimport { ContentNavigator } from './index';\nimport type { WeightedCard } from './index';\nimport type { ContentNavigationStrategyData } from '../types/contentNavigationStrategy';\nimport type { StudySessionReviewItem, StudySessionNewItem } from '..';\nimport type { CardFilter, FilterContext } from './filters/types';\nimport { toCourseElo } from '@vue-skuilder/common';\n\n/**\n * Configuration for the InterferenceMitigator strategy.\n *\n * Course authors define explicit interference relationships between tags.\n * The mitigator discourages introducing new concepts that interfere with\n * currently immature learnings.\n */\n/**\n * A single interference group with its own decay coefficient.\n */\nexport interface InterferenceGroup {\n /** Tags that interfere with each other in this group */\n tags: string[];\n /** How strongly these tags interfere (0-1, default: 0.8). Higher = stronger avoidance. */\n decay?: number;\n}\n\nexport interface InterferenceConfig {\n /**\n * Groups of tags that interfere with each other.\n * Each group can have its own decay coefficient.\n *\n * Example: [\n * { tags: [\"b\", \"d\", \"p\"], decay: 0.9 }, // visual similarity - strong\n * { tags: [\"d\", \"t\"], decay: 0.7 }, // phonetic similarity - moderate\n * { tags: [\"m\", \"n\"], decay: 0.8 }\n * ]\n */\n interferenceSets: InterferenceGroup[];\n\n /**\n * Threshold below which a tag is considered \"immature\" (still being learned).\n * Immature tags trigger interference avoidance for their interference partners.\n */\n maturityThreshold?: {\n /** Minimum interaction count to be considered mature (default: 10) */\n minCount?: number;\n /** Minimum ELO score to be considered mature (optional) */\n minElo?: number;\n /**\n * Minimum elapsed time (in days) since first interaction to be considered mature.\n * Prevents recent cramming success from indicating maturity.\n * The skill should be \"lindy\" — maintained over time.\n */\n minElapsedDays?: number;\n };\n\n /**\n * Default decay for groups that don't specify their own (0-1, default: 0.8).\n */\n defaultDecay?: number;\n}\n\nconst DEFAULT_MIN_COUNT = 10;\nconst DEFAULT_MIN_ELAPSED_DAYS = 3;\nconst DEFAULT_INTERFERENCE_DECAY = 0.8;\n\n/**\n * A filter strategy that avoids introducing confusable concepts simultaneously.\n *\n * When a user is learning a concept (tag is \"immature\"), this strategy reduces\n * scores for cards tagged with interfering concepts. This encourages the system\n * to introduce new content that is maximally distant from current learning focus.\n *\n * Example: While learning 'd', prefer introducing 'x' over 'b' (visual interference)\n * or 't' (phonetic interference).\n *\n * Implements CardFilter for use in Pipeline architecture.\n * Also extends ContentNavigator for backward compatibility.\n */\nexport default class InterferenceMitigatorNavigator extends ContentNavigator implements CardFilter {\n private config: InterferenceConfig;\n private _strategyData: ContentNavigationStrategyData;\n\n /** Human-readable name for CardFilter interface */\n name: string;\n\n /** Precomputed map: tag -> set of { partner, decay } it interferes with */\n private interferenceMap: Map<string, Array<{ partner: string; decay: number }>>;\n\n constructor(\n user: UserDBInterface,\n course: CourseDBInterface,\n _strategyData: ContentNavigationStrategyData\n ) {\n super(user, course, _strategyData);\n this._strategyData = _strategyData;\n this.config = this.parseConfig(_strategyData.serializedData);\n this.interferenceMap = this.buildInterferenceMap();\n this.name = _strategyData.name || 'Interference Mitigator';\n }\n\n private parseConfig(serializedData: string): InterferenceConfig {\n try {\n const parsed = JSON.parse(serializedData);\n // Normalize legacy format (string[][]) to new format (InterferenceGroup[])\n let sets: InterferenceGroup[] = parsed.interferenceSets || [];\n if (sets.length > 0 && Array.isArray(sets[0])) {\n // Legacy format: convert string[][] to InterferenceGroup[]\n sets = (sets as unknown as string[][]).map((tags) => ({ tags }));\n }\n\n return {\n interferenceSets: sets,\n maturityThreshold: {\n minCount: parsed.maturityThreshold?.minCount ?? DEFAULT_MIN_COUNT,\n minElo: parsed.maturityThreshold?.minElo,\n minElapsedDays: parsed.maturityThreshold?.minElapsedDays ?? DEFAULT_MIN_ELAPSED_DAYS,\n },\n defaultDecay: parsed.defaultDecay ?? DEFAULT_INTERFERENCE_DECAY,\n };\n } catch {\n return {\n interferenceSets: [],\n maturityThreshold: {\n minCount: DEFAULT_MIN_COUNT,\n minElapsedDays: DEFAULT_MIN_ELAPSED_DAYS,\n },\n defaultDecay: DEFAULT_INTERFERENCE_DECAY,\n };\n }\n }\n\n /**\n * Build a map from each tag to its interference partners with decay coefficients.\n * If tags A, B, C are in an interference group with decay 0.8, then:\n * - A interferes with B (decay 0.8) and C (decay 0.8)\n * - B interferes with A (decay 0.8) and C (decay 0.8)\n * - etc.\n */\n private buildInterferenceMap(): Map<string, Array<{ partner: string; decay: number }>> {\n const map = new Map<string, Array<{ partner: string; decay: number }>>();\n\n for (const group of this.config.interferenceSets) {\n const decay = group.decay ?? this.config.defaultDecay ?? DEFAULT_INTERFERENCE_DECAY;\n\n for (const tag of group.tags) {\n if (!map.has(tag)) {\n map.set(tag, []);\n }\n const partners = map.get(tag)!;\n for (const other of group.tags) {\n if (other !== tag) {\n // Check if partner already exists (from overlapping groups)\n const existing = partners.find((p) => p.partner === other);\n if (existing) {\n // Use the stronger (higher) decay\n existing.decay = Math.max(existing.decay, decay);\n } else {\n partners.push({ partner: other, decay });\n }\n }\n }\n }\n }\n\n return map;\n }\n\n /**\n * Get the set of tags that are currently immature for this user.\n * A tag is immature if the user has interacted with it but hasn't\n * reached the maturity threshold.\n */\n private async getImmatureTags(context: FilterContext): Promise<Set<string>> {\n const immature = new Set<string>();\n\n try {\n const courseReg = await context.user.getCourseRegDoc(context.course.getCourseID());\n const userElo = toCourseElo(courseReg.elo);\n\n const minCount = this.config.maturityThreshold?.minCount ?? DEFAULT_MIN_COUNT;\n const minElo = this.config.maturityThreshold?.minElo;\n\n // TODO: To properly check elapsed time, we need access to first interaction timestamp.\n // For now, we use count as a proxy (more interactions = more time elapsed).\n // Future: query card history for earliest timestamp per tag.\n const minElapsedDays =\n this.config.maturityThreshold?.minElapsedDays ?? DEFAULT_MIN_ELAPSED_DAYS;\n const minCountForElapsed = minElapsedDays * 2; // Rough proxy: ~2 interactions per day\n\n for (const [tagId, tagElo] of Object.entries(userElo.tags)) {\n // Only consider tags that have been started (count > 0)\n if (tagElo.count === 0) continue;\n\n // Check if below maturity threshold\n const belowCount = tagElo.count < minCount;\n const belowElo = minElo !== undefined && tagElo.score < minElo;\n const belowElapsed = tagElo.count < minCountForElapsed; // Proxy for time\n\n if (belowCount || belowElo || belowElapsed) {\n immature.add(tagId);\n }\n }\n } catch {\n // If we can't get user data, assume no immature tags\n }\n\n return immature;\n }\n\n /**\n * Get all tags that interfere with any immature tag, along with their decay coefficients.\n * These are the tags we want to avoid introducing.\n */\n private getTagsToAvoid(immatureTags: Set<string>): Map<string, number> {\n const avoid = new Map<string, number>();\n\n for (const immatureTag of immatureTags) {\n const partners = this.interferenceMap.get(immatureTag);\n if (partners) {\n for (const { partner, decay } of partners) {\n // Avoid the partner, but not if it's also immature\n // (if both are immature, we're already learning both)\n if (!immatureTags.has(partner)) {\n // Use the strongest (highest) decay if partner appears multiple times\n const existing = avoid.get(partner) ?? 0;\n avoid.set(partner, Math.max(existing, decay));\n }\n }\n }\n }\n\n return avoid;\n }\n\n /**\n * Get tags for a single card\n */\n private async getCardTags(cardId: string, course: CourseDBInterface): Promise<string[]> {\n try {\n const tagResponse = await course.getAppliedTags(cardId);\n return tagResponse.rows.map((row) => row.value?.name || row.key).filter(Boolean);\n } catch {\n return [];\n }\n }\n\n /**\n * Compute interference score reduction for a card.\n * Returns: { multiplier, interfering tags, reason }\n */\n private computeInterferenceEffect(\n cardTags: string[],\n tagsToAvoid: Map<string, number>,\n immatureTags: Set<string>\n ): { multiplier: number; interferingTags: string[]; reason: string } {\n if (tagsToAvoid.size === 0) {\n return {\n multiplier: 1.0,\n interferingTags: [],\n reason: 'No interference detected',\n };\n }\n\n let multiplier = 1.0;\n const interferingTags: string[] = [];\n\n for (const tag of cardTags) {\n const decay = tagsToAvoid.get(tag);\n if (decay !== undefined) {\n interferingTags.push(tag);\n multiplier *= 1.0 - decay;\n }\n }\n\n if (interferingTags.length === 0) {\n return {\n multiplier: 1.0,\n interferingTags: [],\n reason: 'No interference detected',\n };\n }\n\n // Find which immature tags these interfere with\n const causingTags = new Set<string>();\n for (const tag of interferingTags) {\n for (const immatureTag of immatureTags) {\n const partners = this.interferenceMap.get(immatureTag);\n if (partners?.some((p) => p.partner === tag)) {\n causingTags.add(immatureTag);\n }\n }\n }\n\n const reason = `Interferes with immature tags ${Array.from(causingTags).join(', ')} (tags: ${interferingTags.join(', ')}, multiplier: ${multiplier.toFixed(2)})`;\n\n return { multiplier, interferingTags, reason };\n }\n\n /**\n * CardFilter.transform implementation.\n *\n * Apply interference-aware scoring. Cards with tags that interfere with\n * immature learnings get reduced scores.\n */\n async transform(cards: WeightedCard[], context: FilterContext): Promise<WeightedCard[]> {\n // Identify what to avoid\n const immatureTags = await this.getImmatureTags(context);\n const tagsToAvoid = this.getTagsToAvoid(immatureTags);\n\n // Adjust scores based on interference\n const adjusted: WeightedCard[] = [];\n\n for (const card of cards) {\n const cardTags = await this.getCardTags(card.cardId, context.course);\n const { multiplier, reason } = this.computeInterferenceEffect(\n cardTags,\n tagsToAvoid,\n immatureTags\n );\n const finalScore = card.score * multiplier;\n\n const action = multiplier < 1.0 ? 'penalized' : multiplier > 1.0 ? 'boosted' : 'passed';\n\n adjusted.push({\n ...card,\n score: finalScore,\n provenance: [\n ...card.provenance,\n {\n strategy: 'interferenceMitigator',\n strategyName: this.strategyName || this.name,\n strategyId: this.strategyId || 'NAVIGATION_STRATEGY-interference',\n action,\n score: finalScore,\n reason,\n },\n ],\n });\n }\n\n return adjusted;\n }\n\n /**\n * Legacy getWeightedCards - now throws as filters should not be used as generators.\n *\n * Use transform() via Pipeline instead.\n */\n async getWeightedCards(_limit: number): Promise<WeightedCard[]> {\n throw new Error(\n 'InterferenceMitigatorNavigator is a filter and should not be used as a generator. ' +\n 'Use Pipeline with a generator and this filter via transform().'\n );\n }\n\n // Legacy methods - stub implementations since filters don't generate cards\n\n async getNewCards(_n?: number): Promise<StudySessionNewItem[]> {\n return [];\n }\n\n async getPendingReviews(): Promise<(StudySessionReviewItem & ScheduledCard)[]> {\n return [];\n }\n}\n","import type { ScheduledCard } from '../types/user';\nimport type { CourseDBInterface } from '../interfaces/courseDB';\nimport type { UserDBInterface } from '../interfaces/userDB';\nimport { ContentNavigator } from './index';\nimport type { WeightedCard } from './index';\nimport type { ContentNavigationStrategyData } from '../types/contentNavigationStrategy';\nimport type { StudySessionReviewItem, StudySessionNewItem } from '..';\nimport type { CardFilter, FilterContext } from './filters/types';\n\n/**\n * Configuration for the RelativePriority strategy.\n *\n * Course authors define priority weights for tags, allowing the system\n * to prefer high-utility content (common, well-behaved patterns) over\n * lower-utility content (rare, irregular patterns).\n *\n * Example use case: In phonics, prefer teaching 's' (common, consistent)\n * before 'x' or 'z' (rare, sometimes irregular).\n */\nexport interface RelativePriorityConfig {\n /**\n * Map of tag ID to priority weight (0-1).\n *\n * 1.0 = highest priority (present first)\n * 0.5 = neutral\n * 0.0 = lowest priority (defer until later)\n *\n * Example:\n * {\n * \"letter-s\": 0.95,\n * \"letter-t\": 0.90,\n * \"letter-x\": 0.10,\n * \"letter-z\": 0.05\n * }\n */\n tagPriorities: { [tagId: string]: number };\n\n /**\n * Priority for tags not explicitly listed (default: 0.5).\n * 0.5 means unlisted tags have neutral effect on scoring.\n */\n defaultPriority?: number;\n\n /**\n * How to combine priorities when a card has multiple tags.\n *\n * - 'max': Use the highest priority among the card's tags (default)\n * - 'average': Average all tag priorities\n * - 'min': Use the lowest priority (conservative)\n */\n combineMode?: 'max' | 'average' | 'min';\n\n /**\n * How strongly priority influences the final score (0-1, default: 0.5).\n *\n * At 0.0: Priority has no effect (pure delegate scoring)\n * At 0.5: Priority can boost/reduce scores by up to 25%\n * At 1.0: Priority can boost/reduce scores by up to 50%\n *\n * The boost factor formula: 1 + (priority - 0.5) * priorityInfluence\n * - Priority 1.0 with influence 0.5 → boost of 1.25\n * - Priority 0.5 with influence 0.5 → boost of 1.00 (neutral)\n * - Priority 0.0 with influence 0.5 → boost of 0.75\n */\n priorityInfluence?: number;\n}\n\nconst DEFAULT_PRIORITY = 0.5;\nconst DEFAULT_PRIORITY_INFLUENCE = 0.5;\nconst DEFAULT_COMBINE_MODE: 'max' | 'average' | 'min' = 'max';\n\n/**\n * A filter strategy that boosts scores for high-utility content.\n *\n * Course authors assign priority weights to tags. Cards with high-priority\n * tags get boosted scores, making them more likely to be presented first.\n * This allows teaching the most useful, well-behaved concepts before\n * moving on to rarer or more irregular ones.\n *\n * Example: When teaching phonics, prioritize common letters (s, t, a) over\n * rare ones (x, z, q) by assigning higher priority weights to common letters.\n *\n * Implements CardFilter for use in Pipeline architecture.\n * Also extends ContentNavigator for backward compatibility.\n */\nexport default class RelativePriorityNavigator extends ContentNavigator implements CardFilter {\n private config: RelativePriorityConfig;\n private _strategyData: ContentNavigationStrategyData;\n\n /** Human-readable name for CardFilter interface */\n name: string;\n\n constructor(\n user: UserDBInterface,\n course: CourseDBInterface,\n _strategyData: ContentNavigationStrategyData\n ) {\n super(user, course, _strategyData);\n this._strategyData = _strategyData;\n this.config = this.parseConfig(_strategyData.serializedData);\n this.name = _strategyData.name || 'Relative Priority';\n }\n\n private parseConfig(serializedData: string): RelativePriorityConfig {\n try {\n const parsed = JSON.parse(serializedData);\n return {\n tagPriorities: parsed.tagPriorities || {},\n defaultPriority: parsed.defaultPriority ?? DEFAULT_PRIORITY,\n combineMode: parsed.combineMode ?? DEFAULT_COMBINE_MODE,\n priorityInfluence: parsed.priorityInfluence ?? DEFAULT_PRIORITY_INFLUENCE,\n };\n } catch {\n // Return safe defaults if parsing fails\n return {\n tagPriorities: {},\n defaultPriority: DEFAULT_PRIORITY,\n combineMode: DEFAULT_COMBINE_MODE,\n priorityInfluence: DEFAULT_PRIORITY_INFLUENCE,\n };\n }\n }\n\n /**\n * Look up the priority for a tag.\n */\n private getTagPriority(tagId: string): number {\n return this.config.tagPriorities[tagId] ?? this.config.defaultPriority ?? DEFAULT_PRIORITY;\n }\n\n /**\n * Compute combined priority for a card based on its tags.\n */\n private computeCardPriority(cardTags: string[]): number {\n if (cardTags.length === 0) {\n return this.config.defaultPriority ?? DEFAULT_PRIORITY;\n }\n\n const priorities = cardTags.map((tag) => this.getTagPriority(tag));\n\n switch (this.config.combineMode) {\n case 'max':\n return Math.max(...priorities);\n case 'min':\n return Math.min(...priorities);\n case 'average':\n return priorities.reduce((sum, p) => sum + p, 0) / priorities.length;\n default:\n return Math.max(...priorities);\n }\n }\n\n /**\n * Compute boost factor based on priority.\n *\n * The formula: 1 + (priority - 0.5) * priorityInfluence\n *\n * This creates a multiplier centered around 1.0:\n * - Priority 1.0 with influence 0.5 → 1.25 (25% boost)\n * - Priority 0.5 with any influence → 1.00 (neutral)\n * - Priority 0.0 with influence 0.5 → 0.75 (25% reduction)\n */\n private computeBoostFactor(priority: number): number {\n const influence = this.config.priorityInfluence ?? DEFAULT_PRIORITY_INFLUENCE;\n return 1 + (priority - 0.5) * influence;\n }\n\n /**\n * Build human-readable reason for priority adjustment.\n */\n private buildPriorityReason(\n cardTags: string[],\n priority: number,\n boostFactor: number,\n finalScore: number\n ): string {\n if (cardTags.length === 0) {\n return `No tags, neutral priority (${priority.toFixed(2)})`;\n }\n\n const tagList = cardTags.slice(0, 3).join(', ');\n const more = cardTags.length > 3 ? ` (+${cardTags.length - 3} more)` : '';\n\n if (boostFactor === 1.0) {\n return `Neutral priority (${priority.toFixed(2)}) for tags: ${tagList}${more}`;\n } else if (boostFactor > 1.0) {\n return `High-priority tags: ${tagList}${more} (priority ${priority.toFixed(2)} → boost ${boostFactor.toFixed(2)}x → ${finalScore.toFixed(2)})`;\n } else {\n return `Low-priority tags: ${tagList}${more} (priority ${priority.toFixed(2)} → reduce ${boostFactor.toFixed(2)}x → ${finalScore.toFixed(2)})`;\n }\n }\n\n /**\n * Get tags for a single card.\n */\n private async getCardTags(cardId: string, course: CourseDBInterface): Promise<string[]> {\n try {\n const tagResponse = await course.getAppliedTags(cardId);\n return tagResponse.rows.map((r) => r.doc?.name).filter((x): x is string => !!x);\n } catch {\n return [];\n }\n }\n\n /**\n * CardFilter.transform implementation.\n *\n * Apply priority-adjusted scoring. Cards with high-priority tags get boosted,\n * cards with low-priority tags get reduced scores.\n */\n async transform(cards: WeightedCard[], context: FilterContext): Promise<WeightedCard[]> {\n const adjusted: WeightedCard[] = await Promise.all(\n cards.map(async (card) => {\n const cardTags = await this.getCardTags(card.cardId, context.course);\n const priority = this.computeCardPriority(cardTags);\n const boostFactor = this.computeBoostFactor(priority);\n const finalScore = Math.max(0, Math.min(1, card.score * boostFactor));\n\n // Determine action based on boost factor\n const action = boostFactor > 1.0 ? 'boosted' : boostFactor < 1.0 ? 'penalized' : 'passed';\n\n // Build reason explaining priority adjustment\n const reason = this.buildPriorityReason(cardTags, priority, boostFactor, finalScore);\n\n return {\n ...card,\n score: finalScore,\n provenance: [\n ...card.provenance,\n {\n strategy: 'relativePriority',\n strategyName: this.strategyName || this.name,\n strategyId: this.strategyId || 'NAVIGATION_STRATEGY-priority',\n action,\n score: finalScore,\n reason,\n },\n ],\n };\n })\n );\n\n return adjusted;\n }\n\n /**\n * Legacy getWeightedCards - now throws as filters should not be used as generators.\n *\n * Use transform() via Pipeline instead.\n */\n async getWeightedCards(_limit: number): Promise<WeightedCard[]> {\n throw new Error(\n 'RelativePriorityNavigator is a filter and should not be used as a generator. ' +\n 'Use Pipeline with a generator and this filter via transform().'\n );\n }\n\n // Legacy methods - stub implementations since filters don't generate cards\n\n async getNewCards(_n?: number): Promise<StudySessionNewItem[]> {\n return [];\n }\n\n async getPendingReviews(): Promise<(StudySessionReviewItem & ScheduledCard)[]> {\n return [];\n }\n}\n","import moment from 'moment';\nimport type { ScheduledCard } from '../types/user';\nimport type { CourseDBInterface } from '../interfaces/courseDB';\nimport type { UserDBInterface } from '../interfaces/userDB';\nimport { ContentNavigator } from './index';\nimport type { WeightedCard } from './index';\nimport type { ContentNavigationStrategyData } from '../types/contentNavigationStrategy';\nimport type { StudySessionReviewItem, StudySessionNewItem } from '../interfaces/contentSource';\nimport type { CardGenerator, GeneratorContext } from './generators/types';\n\n// ============================================================================\n// SRS NAVIGATOR\n// ============================================================================\n//\n// A generator strategy that scores review cards by urgency.\n//\n// Urgency is determined by two factors:\n// 1. Overdueness - how far past the scheduled review time\n// 2. Interval recency - shorter scheduled intervals indicate \"novel content in progress\"\n//\n// A card with a 3-day interval that's 2 days overdue is more urgent than a card\n// with a 6-month interval that's 2 days overdue. The shorter interval represents\n// active learning at higher resolution.\n//\n// This navigator only handles reviews - it does not generate new cards.\n// For new cards, use ELONavigator or another generator via CompositeGenerator.\n//\n// ============================================================================\n\n/**\n * Configuration for the SRS strategy.\n * Currently minimal - the algorithm is not parameterized.\n */\n// eslint-disable-next-line @typescript-eslint/no-empty-object-type\nexport interface SRSConfig {\n // Future: configurable urgency curves, thresholds, etc.\n}\n\n/**\n * A navigation strategy that scores review cards by urgency.\n *\n * Implements CardGenerator for use in Pipeline architecture.\n * Also extends ContentNavigator for backward compatibility with legacy code.\n *\n * Higher scores indicate more urgent reviews:\n * - Cards that are more overdue (relative to their interval) score higher\n * - Cards with shorter intervals (recent learning) score higher\n *\n * Only returns cards that are actually due (reviewTime has passed).\n * Does not generate new cards - use with CompositeGenerator for mixed content.\n */\nexport default class SRSNavigator extends ContentNavigator implements CardGenerator {\n /** Human-readable name for CardGenerator interface */\n name: string;\n\n constructor(\n user: UserDBInterface,\n course: CourseDBInterface,\n strategyData?: ContentNavigationStrategyData\n ) {\n super(user, course, strategyData as ContentNavigationStrategyData);\n this.name = strategyData?.name || 'SRS';\n }\n\n /**\n * Get review cards scored by urgency.\n *\n * Score formula combines:\n * - Relative overdueness: hoursOverdue / intervalHours\n * - Interval recency: exponential decay favoring shorter intervals\n *\n * Cards not yet due are excluded (not scored as 0).\n *\n * This method supports both the legacy signature (limit only) and the\n * CardGenerator interface signature (limit, context).\n *\n * @param limit - Maximum number of cards to return\n * @param _context - Optional GeneratorContext (currently unused, but required for interface)\n */\n async getWeightedCards(limit: number, _context?: GeneratorContext): Promise<WeightedCard[]> {\n if (!this.user || !this.course) {\n throw new Error('SRSNavigator requires user and course to be set');\n }\n\n const reviews = await this.user.getPendingReviews(this.course.getCourseID());\n const now = moment.utc();\n\n // Filter to only cards that are actually due\n const dueReviews = reviews.filter((r) => now.isAfter(moment.utc(r.reviewTime)));\n\n const scored = dueReviews.map((review) => {\n const { score, reason } = this.computeUrgencyScore(review, now);\n\n return {\n cardId: review.cardId,\n courseId: review.courseId,\n score,\n provenance: [\n {\n strategy: 'srs',\n strategyName: this.strategyName || this.name,\n strategyId: this.strategyId || 'NAVIGATION_STRATEGY-SRS-default',\n action: 'generated' as const,\n score,\n reason,\n },\n ],\n };\n });\n\n // Sort by score descending and limit\n return scored.sort((a, b) => b.score - a.score).slice(0, limit);\n }\n\n /**\n * Compute urgency score for a review card.\n *\n * Two factors:\n * 1. Relative overdueness = hoursOverdue / intervalHours\n * - 2 days overdue on 3-day interval = 0.67 (urgent)\n * - 2 days overdue on 180-day interval = 0.01 (not urgent)\n *\n * 2. Interval recency factor = 0.3 + 0.7 * exp(-intervalHours / 720)\n * - 24h interval → ~1.0 (very recent learning)\n * - 30 days (720h) → ~0.56\n * - 180 days → ~0.30\n *\n * Combined: base 0.5 + weighted average of factors * 0.45\n * Result range: approximately 0.5 to 0.95\n */\n private computeUrgencyScore(\n review: ScheduledCard,\n now: moment.Moment\n ): { score: number; reason: string } {\n const scheduledAt = moment.utc(review.scheduledAt);\n const due = moment.utc(review.reviewTime);\n\n // Interval = time between scheduling and due date (minimum 1 hour to avoid division issues)\n const intervalHours = Math.max(1, due.diff(scheduledAt, 'hours'));\n const hoursOverdue = now.diff(due, 'hours');\n\n // Relative overdueness: how late relative to the interval\n const relativeOverdue = hoursOverdue / intervalHours;\n\n // Interval recency factor: shorter intervals = more urgent\n // Exponential decay with 720h (30 days) as the characteristic time\n const recencyFactor = 0.3 + 0.7 * Math.exp(-intervalHours / 720);\n\n // Combined urgency: weighted average of relative overdue and recency\n // Clamp relative overdue contribution to [0, 1] to avoid runaway scores\n const overdueContribution = Math.min(1.0, Math.max(0, relativeOverdue));\n const urgency = overdueContribution * 0.5 + recencyFactor * 0.5;\n\n // Final score: base 0.5 + urgency contribution, capped at 0.95\n const score = Math.min(0.95, 0.5 + urgency * 0.45);\n\n const reason =\n `${Math.round(hoursOverdue)}h overdue (interval: ${Math.round(intervalHours)}h, ` +\n `relative: ${relativeOverdue.toFixed(2)}), recency: ${recencyFactor.toFixed(2)}, review`;\n\n return { score, reason };\n }\n\n /**\n * Get pending reviews in legacy format.\n *\n * Returns all pending reviews for the course, enriched with session item fields.\n */\n async getPendingReviews(): Promise<(StudySessionReviewItem & ScheduledCard)[]> {\n if (!this.user || !this.course) {\n throw new Error('SRSNavigator requires user and course to be set');\n }\n\n const reviews = await this.user.getPendingReviews(this.course.getCourseID());\n\n return reviews.map((r) => ({\n ...r,\n contentSourceType: 'course' as const,\n contentSourceID: this.course!.getCourseID(),\n cardID: r.cardId,\n courseID: r.courseId,\n qualifiedID: `${r.courseId}-${r.cardId}`,\n reviewID: r._id,\n status: 'review' as const,\n }));\n }\n\n /**\n * SRS does not generate new cards.\n * Use ELONavigator or another generator for new cards.\n */\n async getNewCards(_n?: number): Promise<StudySessionNewItem[]> {\n return [];\n }\n}\n","import {\n StudyContentSource,\n UserDBInterface,\n CourseDBInterface,\n StudySessionReviewItem,\n StudySessionNewItem,\n} from '..';\n\n// Re-export filter types\nexport type { CardFilter, FilterContext, CardFilterFactory } from './filters/types';\n\n// Re-export generator types\nexport type { CardGenerator, GeneratorContext, CardGeneratorFactory } from './generators/types';\n\nimport { ContentNavigationStrategyData } from '../types/contentNavigationStrategy';\nimport { ScheduledCard } from '../types/user';\nimport { logger } from '../../util/logger';\n\n// ============================================================================\n// NAVIGATION STRATEGY API\n// ============================================================================\n//\n// This module defines the ContentNavigator base class and the WeightedCard type,\n// which form the foundation of the pluggable navigation strategy system.\n//\n// KEY CONCEPTS:\n//\n// 1. WeightedCard - A card with a suitability score (0-1) and provenance trail.\n// The provenance tracks how each strategy in the pipeline contributed to\n// the card's final score, ensuring transparency and debuggability.\n//\n// 2. ContentNavigator - Abstract base class for backward compatibility.\n// New code should use CardGenerator or CardFilter interfaces directly.\n//\n// 3. CardGenerator vs CardFilter:\n// - Generators (ELO, SRS, HardcodedOrder) produce candidate cards with scores\n// - Filters (Hierarchy, Interference, Priority, EloDistance) transform scores\n//\n// 4. Pipeline architecture:\n// Pipeline(generator, [filter1, filter2, ...]) executes:\n// cards = generator.getWeightedCards()\n// cards = filter1.transform(cards, context)\n// cards = filter2.transform(cards, context)\n// return sorted(cards)\n//\n// 5. Provenance tracking - Each strategy adds an entry explaining its contribution.\n// This makes the system transparent and debuggable.\n//\n// ============================================================================\n\n/**\n * Tracks a single strategy's contribution to a card's final score.\n *\n * Each strategy in the pipeline adds a StrategyContribution entry to the\n * card's provenance array, creating an audit trail of scoring decisions.\n */\nexport interface StrategyContribution {\n /**\n * Strategy type (implementing class name).\n * Examples: 'elo', 'hierarchyDefinition', 'interferenceMitigator'\n */\n strategy: string;\n\n /**\n * Human-readable name identifying this specific strategy instance.\n * Extracted from ContentNavigationStrategyData.name.\n * Courses may have multiple instances of the same strategy type with\n * different configurations.\n *\n * Examples:\n * - \"ELO (default)\"\n * - \"Interference: b/d/p confusion\"\n * - \"Interference: phonetic confusables\"\n * - \"Priority: Common letters first\"\n */\n strategyName: string;\n\n /**\n * Unique database document ID for this strategy instance.\n * Extracted from ContentNavigationStrategyData._id.\n * Use this to fetch the full strategy configuration document.\n *\n * Examples:\n * - \"NAVIGATION_STRATEGY-ELO-default\"\n * - \"NAVIGATION_STRATEGY-interference-bdp\"\n * - \"NAVIGATION_STRATEGY-priority-common-letters\"\n */\n strategyId: string;\n\n /**\n * What the strategy did:\n * - 'generated': Strategy produced this card (generators only)\n * - 'passed': Strategy evaluated but didn't change score (transparent pass-through)\n * - 'boosted': Strategy increased the score\n * - 'penalized': Strategy decreased the score\n */\n action: 'generated' | 'passed' | 'boosted' | 'penalized';\n\n /** Score after this strategy's processing */\n score: number;\n\n /**\n * Human-readable explanation of the strategy's decision.\n *\n * Examples:\n * - \"ELO distance 75, new card\"\n * - \"Prerequisites met: letter-sounds\"\n * - \"Interferes with immature tag 'd' (decay 0.8)\"\n * - \"High-priority tag 's' (0.95) → boost 1.15x\"\n *\n * Required for transparency - silent adjusters are anti-patterns.\n */\n reason: string;\n}\n\n/**\n * A card with a suitability score and provenance trail.\n *\n * Scores range from 0-1:\n * - 1.0 = fully suitable\n * - 0.0 = hard filter (e.g., prerequisite not met)\n * - 0.5 = neutral\n * - Intermediate values = soft preference\n *\n * Provenance tracks the scoring pipeline:\n * - First entry: Generator that produced the card\n * - Subsequent entries: Filters that transformed the score\n * - Each entry includes action and human-readable reason\n */\nexport interface WeightedCard {\n cardId: string;\n courseId: string;\n /** Suitability score from 0-1 */\n score: number;\n /**\n * Audit trail of strategy contributions.\n * First entry is from the generator, subsequent entries from filters.\n */\n provenance: StrategyContribution[];\n}\n\n/**\n * Extract card origin from provenance trail.\n *\n * The first provenance entry (from the generator) indicates whether\n * this is a new card, review, or failed card. We parse the reason\n * string to extract this information.\n *\n * @param card - Card with provenance trail\n * @returns Card origin ('new', 'review', or 'failed')\n */\nexport function getCardOrigin(card: WeightedCard): 'new' | 'review' | 'failed' {\n if (card.provenance.length === 0) {\n throw new Error('Card has no provenance - cannot determine origin');\n }\n\n const firstEntry = card.provenance[0];\n const reason = firstEntry.reason.toLowerCase();\n\n if (reason.includes('failed')) {\n return 'failed';\n }\n if (reason.includes('review')) {\n return 'review';\n }\n return 'new';\n}\n\nexport enum Navigators {\n ELO = 'elo',\n SRS = 'srs',\n HARDCODED = 'hardcodedOrder',\n HIERARCHY = 'hierarchyDefinition',\n INTERFERENCE = 'interferenceMitigator',\n RELATIVE_PRIORITY = 'relativePriority',\n}\n\n// ============================================================================\n// NAVIGATOR ROLE CLASSIFICATION\n// ============================================================================\n//\n// Navigators are classified as either generators or filters:\n// - Generators: Produce candidate cards (ELO, SRS, HardcodedOrder)\n// - Filters: Transform/score candidates (Hierarchy, Interference, RelativePriority)\n//\n// This classification is used by PipelineAssembler to build pipelines:\n// 1. Instantiate generators (possibly into a CompositeGenerator)\n// 2. Instantiate filters\n// 3. Create Pipeline(generator, filters)\n//\n// ============================================================================\n\n/**\n * Role classification for navigation strategies.\n *\n * - GENERATOR: Produces candidate cards with initial scores\n * - FILTER: Transforms cards with score multipliers\n */\nexport enum NavigatorRole {\n GENERATOR = 'generator',\n FILTER = 'filter',\n}\n\n/**\n * Registry mapping navigator implementations to their roles.\n */\nexport const NavigatorRoles: Record<Navigators, NavigatorRole> = {\n [Navigators.ELO]: NavigatorRole.GENERATOR,\n [Navigators.SRS]: NavigatorRole.GENERATOR,\n [Navigators.HARDCODED]: NavigatorRole.GENERATOR,\n [Navigators.HIERARCHY]: NavigatorRole.FILTER,\n [Navigators.INTERFERENCE]: NavigatorRole.FILTER,\n [Navigators.RELATIVE_PRIORITY]: NavigatorRole.FILTER,\n};\n\n/**\n * Check if a navigator implementation is a generator.\n *\n * @param impl - Navigator implementation name (e.g., 'elo', 'hierarchyDefinition')\n * @returns true if the navigator is a generator, false otherwise\n */\nexport function isGenerator(impl: string): boolean {\n return NavigatorRoles[impl as Navigators] === NavigatorRole.GENERATOR;\n}\n\n/**\n * Check if a navigator implementation is a filter.\n *\n * @param impl - Navigator implementation name (e.g., 'elo', 'hierarchyDefinition')\n * @returns true if the navigator is a filter, false otherwise\n */\nexport function isFilter(impl: string): boolean {\n return NavigatorRoles[impl as Navigators] === NavigatorRole.FILTER;\n}\n\n/**\n * Abstract base class for navigation strategies.\n *\n * This class exists primarily for backward compatibility with legacy code.\n * New code should use CardGenerator or CardFilter interfaces directly.\n *\n * The class implements StudyContentSource for compatibility with SessionController.\n * Once SessionController migrates to use getWeightedCards() exclusively,\n * the legacy methods can be removed.\n */\nexport abstract class ContentNavigator implements StudyContentSource {\n /** User interface for this navigation session */\n protected user?: UserDBInterface;\n\n /** Course interface for this navigation session */\n protected course?: CourseDBInterface;\n\n /** Human-readable name for this strategy instance (from ContentNavigationStrategyData.name) */\n protected strategyName?: string;\n\n /** Unique document ID for this strategy instance (from ContentNavigationStrategyData._id) */\n protected strategyId?: string;\n\n /**\n * Constructor for standard navigators.\n * Call this from subclass constructors to initialize common fields.\n *\n * Note: CompositeGenerator doesn't use this pattern and should call super() without args.\n */\n constructor(\n user?: UserDBInterface,\n course?: CourseDBInterface,\n strategyData?: ContentNavigationStrategyData\n ) {\n if (user && course && strategyData) {\n this.user = user;\n this.course = course;\n this.strategyName = strategyData.name;\n this.strategyId = strategyData._id;\n }\n }\n\n /**\n * Factory method to create navigator instances dynamically.\n *\n * @param user - User interface\n * @param course - Course interface\n * @param strategyData - Strategy configuration document\n * @returns the runtime object used to steer a study session.\n */\n static async create(\n user: UserDBInterface,\n course: CourseDBInterface,\n strategyData: ContentNavigationStrategyData\n ): Promise<ContentNavigator> {\n const implementingClass = strategyData.implementingClass;\n let NavigatorImpl;\n\n // Try different extension variations\n const variations = ['.ts', '.js', ''];\n\n for (const ext of variations) {\n try {\n const module = await import(`./${implementingClass}${ext}`);\n NavigatorImpl = module.default;\n break; // Break the loop if loading succeeds\n } catch (e) {\n // Continue to next variation if this one fails\n logger.debug(`Failed to load with extension ${ext}:`, e);\n }\n }\n\n if (!NavigatorImpl) {\n throw new Error(`Could not load navigator implementation for: ${implementingClass}`);\n }\n\n return new NavigatorImpl(user, course, strategyData);\n }\n\n /**\n * Get cards scheduled for review.\n *\n * @deprecated This method is part of the legacy StudyContentSource interface.\n * New strategies should focus on implementing CardGenerator.getWeightedCards() instead.\n */\n abstract getPendingReviews(): Promise<(StudySessionReviewItem & ScheduledCard)[]>;\n\n /**\n * Get new cards for introduction.\n *\n * @deprecated This method is part of the legacy StudyContentSource interface.\n * New strategies should focus on implementing CardGenerator.getWeightedCards() instead.\n *\n * @param n - Maximum number of new cards to return\n */\n abstract getNewCards(n?: number): Promise<StudySessionNewItem[]>;\n\n /**\n * Get cards with suitability scores and provenance trails.\n *\n * **This is the PRIMARY API for navigation strategies.**\n *\n * Returns cards ranked by suitability score (0-1). Higher scores indicate\n * better candidates for presentation. Each card includes a provenance trail\n * documenting how strategies contributed to the final score.\n *\n * ## For Generators\n * Override this method to generate candidates and compute scores based on\n * your strategy's logic (e.g., ELO proximity, review urgency). Create the\n * initial provenance entry with action='generated'.\n *\n * ## Default Implementation\n * The base class provides a backward-compatible default that:\n * 1. Calls legacy getNewCards() and getPendingReviews()\n * 2. Assigns score=1.0 to all cards\n * 3. Creates minimal provenance from legacy methods\n * 4. Returns combined results up to limit\n *\n * This allows existing strategies to work without modification while\n * new strategies can override with proper scoring and provenance.\n *\n * @param limit - Maximum cards to return\n * @returns Cards sorted by score descending, with provenance trails\n */\n async getWeightedCards(limit: number): Promise<WeightedCard[]> {\n // Default implementation: delegate to legacy methods, assign score=1.0\n const newCards = await this.getNewCards(limit);\n const reviews = await this.getPendingReviews();\n\n const weighted: WeightedCard[] = [\n ...newCards.map((c) => ({\n cardId: c.cardID,\n courseId: c.courseID,\n score: 1.0,\n provenance: [\n {\n strategy: 'legacy',\n strategyName: this.strategyName || 'Legacy API',\n strategyId: this.strategyId || 'legacy-fallback',\n action: 'generated' as const,\n score: 1.0,\n reason: 'Generated via legacy getNewCards(), new card',\n },\n ],\n })),\n ...reviews.map((r) => ({\n cardId: r.cardID,\n courseId: r.courseID,\n score: 1.0,\n provenance: [\n {\n strategy: 'legacy',\n strategyName: this.strategyName || 'Legacy API',\n strategyId: this.strategyId || 'legacy-fallback',\n action: 'generated' as const,\n score: 1.0,\n reason: 'Generated via legacy getPendingReviews(), review',\n },\n ],\n })),\n ];\n\n return weighted.slice(0, limit);\n }\n}\n","import { CourseDBInterface, CourseInfo, CoursesDBInterface, UserDBInterface } from '@db/core';\nimport { ScheduledCard } from '@db/core/types/user';\nimport {\n CourseConfig,\n CourseElo,\n DataShape,\n EloToNumber,\n Status,\n blankCourseElo,\n toCourseElo,\n} from '@vue-skuilder/common';\n\nimport { filterAllDocsByPrefix, getCourseDB, getCourseDoc, getCourseDocs } from '.';\nimport UpdateQueue from './updateQueue';\nimport {\n StudyContentSource,\n StudySessionItem,\n StudySessionNewItem,\n StudySessionReviewItem,\n} from '../../core/interfaces/contentSource';\nimport {\n CardData,\n DocType,\n QualifiedCardID,\n SkuilderCourseData,\n Tag,\n TagStub,\n DocTypePrefixes,\n} from '../../core/types/types-legacy';\nimport { logger } from '../../util/logger';\nimport { GET_CACHED } from './clientCache';\nimport { addNote55, addTagToCard, getCredentialledCourseConfig, getTagID } from './courseAPI';\nimport { DataLayerResult } from '@db/core/types/db';\nimport { PouchError } from './types';\nimport CourseLookup from './courseLookupDB';\nimport { ContentNavigationStrategyData } from '@db/core/types/contentNavigationStrategy';\nimport { ContentNavigator, Navigators, WeightedCard } from '@db/core/navigators';\nimport { Pipeline } from '@db/core/navigators/Pipeline';\nimport { PipelineAssembler } from '@db/core/navigators/PipelineAssembler';\nimport CompositeGenerator from '@db/core/navigators/CompositeGenerator';\nimport ELONavigator from '@db/core/navigators/elo';\nimport SRSNavigator from '@db/core/navigators/srs';\nimport { createEloDistanceFilter } from '@db/core/navigators/filters/eloDistance';\n\nexport class CoursesDB implements CoursesDBInterface {\n _courseIDs: string[] | undefined;\n\n constructor(courseIDs?: string[]) {\n if (courseIDs && courseIDs.length > 0) {\n this._courseIDs = courseIDs;\n } else {\n this._courseIDs = undefined;\n }\n }\n\n public async getCourseList(): Promise<CourseConfig[]> {\n let crsList = await CourseLookup.allCourseWare();\n logger.debug(`AllCourses: ${crsList.map((c) => c.name + ', ' + c._id + '\\n\\t')}`);\n if (this._courseIDs) {\n crsList = crsList.filter((c) => this._courseIDs!.includes(c._id));\n }\n\n logger.debug(`AllCourses.filtered: ${crsList.map((c) => c.name + ', ' + c._id + '\\n\\t')}`);\n\n const cfgs = await Promise.all(\n crsList.map(async (c) => {\n try {\n const cfg = await getCredentialledCourseConfig(c._id);\n logger.debug(`Found cfg: ${JSON.stringify(cfg)}`);\n return cfg;\n } catch (e) {\n logger.warn(`Error fetching cfg for course ${c.name}, ${c._id}: ${e}`);\n return undefined;\n }\n })\n );\n return cfgs.filter((c) => !!c);\n }\n\n async getCourseConfig(courseId: string): Promise<CourseConfig> {\n if (this._courseIDs && this._courseIDs.length && !this._courseIDs.includes(courseId)) {\n throw new Error(`Course ${courseId} not in course list`);\n }\n\n const cfg = await getCredentialledCourseConfig(courseId);\n if (cfg === undefined) {\n throw new Error(`Error fetching cfg for course ${courseId}`);\n } else {\n return cfg;\n }\n }\n\n public async disambiguateCourse(courseId: string, disambiguator: string): Promise<void> {\n await CourseLookup.updateDisambiguator(courseId, disambiguator);\n }\n}\n\nfunction randIntWeightedTowardZero(n: number) {\n return Math.floor(Math.random() * Math.random() * Math.random() * n);\n}\n\nexport class CourseDB implements StudyContentSource, CourseDBInterface {\n // private log(msg: string): void {\n // log(`CourseLog: ${this.id}\\n ${msg}`);\n // }\n\n private db: PouchDB.Database;\n private id: string;\n private _getCurrentUser: () => Promise<UserDBInterface>;\n private updateQueue: UpdateQueue;\n\n constructor(id: string, userLookup: () => Promise<UserDBInterface>) {\n this.id = id;\n this.db = getCourseDB(this.id);\n this._getCurrentUser = userLookup;\n this.updateQueue = new UpdateQueue(this.db);\n }\n\n public getCourseID(): string {\n return this.id;\n }\n\n public async getCourseInfo(): Promise<CourseInfo> {\n const cardCount = (\n await this.db.find({\n selector: {\n docType: DocType.CARD,\n },\n limit: 1000,\n })\n ).docs.length;\n\n return {\n cardCount,\n registeredUsers: 0,\n };\n }\n\n public async getInexperiencedCards(limit: number = 2) {\n return (\n await this.db.query('cardsByInexperience', {\n limit,\n })\n ).rows.map((r) => {\n const ret = {\n courseId: this.id,\n cardId: r.id,\n count: r.key,\n elo: r.value,\n };\n return ret;\n });\n }\n\n public async getCardsByEloLimits(\n options: {\n low: number;\n high: number;\n limit: number;\n page: number;\n } = {\n low: 0,\n high: Number.MIN_SAFE_INTEGER,\n limit: 25,\n page: 0,\n }\n ) {\n return (\n await this.db.query('elo', {\n startkey: options.low,\n endkey: options.high,\n limit: options.limit,\n skip: options.limit * options.page,\n })\n ).rows.map((r) => {\n return `${this.id}-${r.id}-${r.key}`;\n });\n }\n public async getCardEloData(id: string[]): Promise<CourseElo[]> {\n const docs = await this.db.allDocs<CardData>({\n keys: id,\n include_docs: true,\n });\n const ret: CourseElo[] = [];\n docs.rows.forEach((r) => {\n // [ ] remove these ts-ignore directives.\n if (isSuccessRow(r)) {\n if (r.doc && r.doc.elo) {\n ret.push(toCourseElo(r.doc.elo));\n } else {\n logger.warn('no elo data for card: ' + r.id);\n ret.push(blankCourseElo());\n }\n } else {\n logger.warn('no elo data for card: ' + JSON.stringify(r));\n ret.push(blankCourseElo());\n }\n });\n return ret;\n }\n\n /**\n * Returns the lowest and highest `global` ELO ratings in the course\n */\n public async getELOBounds() {\n const [low, high] = await Promise.all([\n (\n await this.db.query('elo', {\n startkey: 0,\n limit: 1,\n include_docs: false,\n })\n ).rows[0].key,\n (\n await this.db.query('elo', {\n limit: 1,\n descending: true,\n startkey: 100_000,\n })\n ).rows[0].key,\n ]);\n\n return {\n low: low,\n high: high,\n };\n }\n\n public async removeCard(id: string) {\n const doc = await this.db.get<CardData>(id);\n if (!doc.docType || !(doc.docType === DocType.CARD)) {\n throw new Error(`failed to remove ${id} from course ${this.id}. id does not point to a card`);\n }\n\n // Remove card from all associated tags before deleting the card\n try {\n const appliedTags = await this.getAppliedTags(id);\n const results = await Promise.allSettled(\n appliedTags.rows.map(async (tagRow) => {\n const tagId = tagRow.id;\n await this.removeTagFromCard(id, tagId);\n })\n );\n\n // Log any individual tag cleanup failures\n results.forEach((result, index) => {\n if (result.status === 'rejected') {\n const tagId = appliedTags.rows[index].id;\n logger.error(`Failed to remove card ${id} from tag ${tagId}: ${result.reason}`);\n }\n });\n } catch (error) {\n logger.error(`Error removing card ${id} from tags: ${error}`);\n // Continue with card deletion even if tag cleanup fails\n }\n\n return this.db.remove(doc);\n }\n\n public async getCardDisplayableDataIDs(id: string[]) {\n logger.debug(id.join(', '));\n const cards = await this.db.allDocs<CardData>({\n keys: id,\n include_docs: true,\n });\n const ret: { [card: string]: string[] } = {};\n cards.rows.forEach((r) => {\n if (isSuccessRow(r)) {\n ret[r.id] = r.doc!.id_displayable_data;\n }\n });\n\n await Promise.all(\n cards.rows.map((r) => {\n return async () => {\n if (isSuccessRow(r)) {\n ret[r.id] = r.doc!.id_displayable_data;\n }\n };\n })\n );\n\n return ret;\n }\n\n async getCardsByELO(elo: number, cardLimit?: number) {\n elo = parseInt(elo as any);\n const limit = cardLimit ? cardLimit : 25;\n\n const below: PouchDB.Query.Response<object> = await this.db.query('elo', {\n limit: Math.ceil(limit / 2),\n startkey: elo,\n descending: true,\n });\n\n const aboveLimit = limit - below.rows.length;\n\n const above: PouchDB.Query.Response<object> = await this.db.query('elo', {\n limit: aboveLimit,\n startkey: elo + 1,\n });\n // logger.log(JSON.stringify(below));\n\n let cards = below.rows;\n cards = cards.concat(above.rows);\n\n const ret = cards\n .sort((a, b) => {\n const s = Math.abs(a.key - elo) - Math.abs(b.key - elo);\n if (s === 0) {\n return Math.random() - 0.5;\n } else {\n return s;\n }\n })\n .map((c) => {\n return {\n courseID: this.id,\n cardID: c.id,\n elo: c.key,\n };\n });\n\n const str = `below:\\n${below.rows.map((r) => `\\t${r.id}-${r.key}\\n`)}\n\nabove:\\n${above.rows.map((r) => `\\t${r.id}-${r.key}\\n`)}`;\n\n logger.debug(`Getting ${limit} cards centered around elo: ${elo}:\\n\\n` + str);\n\n return ret;\n }\n\n async getCourseConfig(): Promise<CourseConfig> {\n const ret = await getCredentialledCourseConfig(this.id);\n if (ret) {\n return ret;\n } else {\n throw new Error(`Course config not found for course ID: ${this.id}`);\n }\n }\n\n async updateCourseConfig(cfg: CourseConfig): Promise<PouchDB.Core.Response> {\n logger.debug(`Updating: ${JSON.stringify(cfg)}`);\n // write both to the course DB:\n try {\n return await updateCredentialledCourseConfig(this.id, cfg);\n } catch (error) {\n logger.error(`Error updating course config in course DB: ${error}`);\n throw error;\n }\n }\n\n async updateCardElo(cardId: string, elo: CourseElo): Promise<PouchDB.Core.Response> {\n if (!elo) {\n throw new Error(`Cannot update card elo with null or undefined value for card ID: ${cardId}`);\n }\n\n try {\n const result = await this.updateQueue.update<\n CardData & PouchDB.Core.GetMeta & PouchDB.Core.IdMeta\n >(cardId, (card) => {\n logger.debug(`Replacing ${JSON.stringify(card.elo)} with ${JSON.stringify(elo)}`);\n card.elo = elo;\n return card;\n });\n return { ok: true, id: cardId, rev: result._rev };\n } catch (error) {\n logger.error(`Failed to update card elo for card ID: ${cardId}`, error);\n throw new Error(`Failed to update card elo for card ID: ${cardId}`);\n }\n }\n\n async getAppliedTags(cardId: string): Promise<PouchDB.Query.Response<TagStub>> {\n const ret = await getAppliedTags(this.id, cardId);\n if (ret) {\n return ret;\n } else {\n throw new Error(`Failed to find tags for card ${this.id}-${cardId}`);\n }\n }\n\n async addTagToCard(\n cardId: string,\n tagId: string,\n updateELO?: boolean\n ): Promise<PouchDB.Core.Response> {\n return await addTagToCard(\n this.id,\n cardId,\n tagId,\n (await this._getCurrentUser()).getUsername(),\n updateELO\n );\n }\n\n async removeTagFromCard(cardId: string, tagId: string): Promise<PouchDB.Core.Response> {\n return await removeTagFromCard(this.id, cardId, tagId);\n }\n\n async createTag(name: string, author: string): Promise<PouchDB.Core.Response> {\n return await createTag(this.id, name, author);\n }\n\n async getTag(tagId: string): Promise<PouchDB.Core.GetMeta & PouchDB.Core.Document<Tag>> {\n return await getTag(this.id, tagId);\n }\n\n async updateTag(tag: Tag): Promise<PouchDB.Core.Response> {\n if (tag.course !== this.id) {\n throw new Error(`Tag ${JSON.stringify(tag)} does not belong to course ${this.id}`);\n }\n\n return await updateTag(tag);\n }\n\n async getCourseTagStubs(): Promise<PouchDB.Core.AllDocsResponse<Tag>> {\n return getCourseTagStubs(this.id);\n }\n\n async addNote(\n codeCourse: string,\n shape: DataShape,\n data: unknown,\n author: string,\n tags: string[],\n uploads?: { [key: string]: PouchDB.Core.FullAttachment },\n elo: CourseElo = blankCourseElo()\n ): Promise<DataLayerResult> {\n try {\n const resp = await addNote55(this.id, codeCourse, shape, data, author, tags, uploads, elo);\n if (resp.ok) {\n // Check if card creation failed (property added by addNote55)\n if ((resp as any).cardCreationFailed) {\n logger.warn(\n `[courseDB.addNote] Note added but card creation failed: ${\n (resp as any).cardCreationError\n }`\n );\n return {\n status: Status.error,\n message: `Note was added but no cards were created: ${(resp as any).cardCreationError}`,\n id: resp.id,\n };\n }\n return {\n status: Status.ok,\n message: '',\n id: resp.id,\n };\n } else {\n return {\n status: Status.error,\n message: 'Unexpected error adding note',\n };\n }\n } catch (e) {\n const err = e as PouchDB.Core.Error;\n logger.error(\n `[addNote] error ${err.name}\\n\\treason: ${err.reason}\\n\\tmessage: ${err.message}`\n );\n return {\n status: Status.error,\n message: `Error adding note to course. ${(e as PouchError).reason || err.message}`,\n };\n }\n }\n\n async getCourseDoc<T extends SkuilderCourseData>(\n id: string,\n options?: PouchDB.Core.GetOptions\n ): Promise<PouchDB.Core.GetMeta & PouchDB.Core.Document<T>> {\n return await getCourseDoc(this.id, id, options);\n }\n\n async getCourseDocs<T extends SkuilderCourseData>(\n ids: string[],\n options: PouchDB.Core.AllDocsOptions = {}\n ): Promise<PouchDB.Core.AllDocsWithKeysResponse<{} & T>> {\n return await getCourseDocs(this.id, ids, options);\n }\n\n ////////////////////////////////////\n // NavigationStrategyManager implementation\n ////////////////////////////////////\n\n getNavigationStrategy(id: string): Promise<ContentNavigationStrategyData> {\n logger.debug(`[courseDB] Getting navigation strategy: ${id}`);\n\n if (id == '') {\n const strategy: ContentNavigationStrategyData = {\n _id: 'NAVIGATION_STRATEGY-ELO',\n docType: DocType.NAVIGATION_STRATEGY,\n name: 'ELO',\n description: 'ELO-based navigation strategy for ordering content by difficulty',\n implementingClass: Navigators.ELO,\n course: this.id,\n serializedData: '', // serde is a noop for ELO navigator.\n };\n return Promise.resolve(strategy);\n } else {\n return this.db.get(id);\n }\n }\n\n async getAllNavigationStrategies(): Promise<ContentNavigationStrategyData[]> {\n const prefix = DocTypePrefixes[DocType.NAVIGATION_STRATEGY];\n const result = await this.db.allDocs<ContentNavigationStrategyData>({\n startkey: prefix,\n endkey: `${prefix}\\ufff0`,\n include_docs: true,\n });\n return result.rows.map((row) => row.doc!);\n }\n\n async addNavigationStrategy(data: ContentNavigationStrategyData): Promise<void> {\n logger.debug(`[courseDB] Adding navigation strategy: ${data._id}`);\n // // For now, just log the data and return success\n // logger.debug(JSON.stringify(data));\n return this.db.put(data).then(() => {});\n }\n updateNavigationStrategy(id: string, data: ContentNavigationStrategyData): Promise<void> {\n logger.debug(`[courseDB] Updating navigation strategy: ${id}`);\n // For now, just log the data and return success\n logger.debug(JSON.stringify(data));\n return Promise.resolve();\n }\n\n /**\n * Creates an instantiated navigator for this course.\n *\n * Handles multiple generators by wrapping them in CompositeGenerator.\n * This is the preferred method for getting a ready-to-use navigator.\n *\n * @param user - User database interface\n * @returns Instantiated ContentNavigator ready for use\n */\n async createNavigator(user: UserDBInterface): Promise<ContentNavigator> {\n try {\n const allStrategies = await this.getAllNavigationStrategies();\n\n if (allStrategies.length === 0) {\n // No strategies configured: use default Pipeline(Composite(ELO, SRS), [eloDistanceFilter])\n logger.debug(\n '[courseDB] No strategy documents found, using default Pipeline(Composite(ELO, SRS), [eloDistanceFilter])'\n );\n return this.createDefaultPipeline(user);\n }\n\n // Use PipelineAssembler to build a Pipeline from strategy documents\n const assembler = new PipelineAssembler();\n const { pipeline, generatorStrategies, filterStrategies, warnings } =\n await assembler.assemble({\n strategies: allStrategies,\n user,\n course: this,\n });\n\n // Log any warnings from assembly\n for (const warning of warnings) {\n logger.warn(`[PipelineAssembler] ${warning}`);\n }\n\n if (!pipeline) {\n // Assembly failed - fall back to default\n logger.debug('[courseDB] Pipeline assembly failed, using default pipeline');\n return this.createDefaultPipeline(user);\n }\n\n logger.debug(\n `[courseDB] Using assembled pipeline with ${generatorStrategies.length} generator(s) and ${filterStrategies.length} filter(s)`\n );\n return pipeline;\n } catch (e) {\n logger.error(`[courseDB] Error creating navigator: ${e}`);\n throw e;\n }\n }\n\n private makeDefaultEloStrategy(): ContentNavigationStrategyData {\n return {\n _id: 'NAVIGATION_STRATEGY-ELO-default',\n docType: DocType.NAVIGATION_STRATEGY,\n name: 'ELO (default)',\n description: 'Default ELO-based navigation strategy for new cards',\n implementingClass: Navigators.ELO,\n course: this.id,\n serializedData: '',\n };\n }\n\n private makeDefaultSrsStrategy(): ContentNavigationStrategyData {\n return {\n _id: 'NAVIGATION_STRATEGY-SRS-default',\n docType: DocType.NAVIGATION_STRATEGY,\n name: 'SRS (default)',\n description: 'Default SRS-based navigation strategy for reviews',\n implementingClass: Navigators.SRS,\n course: this.id,\n serializedData: '',\n };\n }\n\n /**\n * Creates the default navigation pipeline for courses with no configured strategies.\n *\n * Default: Pipeline(Composite(ELO, SRS), [eloDistanceFilter])\n * - ELO generator: scores new cards by skill proximity\n * - SRS generator: scores reviews by overdueness and interval recency\n * - ELO distance filter: penalizes cards far from user's current level\n */\n private createDefaultPipeline(user: UserDBInterface): Pipeline {\n const eloNavigator = new ELONavigator(user, this, this.makeDefaultEloStrategy());\n const srsNavigator = new SRSNavigator(user, this, this.makeDefaultSrsStrategy());\n\n const compositeGenerator = new CompositeGenerator([eloNavigator, srsNavigator]);\n const eloDistanceFilter = createEloDistanceFilter();\n\n return new Pipeline(compositeGenerator, [eloDistanceFilter], user, this);\n }\n\n ////////////////////////////////////\n // END NavigationStrategyManager implementation\n ////////////////////////////////////\n\n ////////////////////////////////////\n // StudyContentSource implementation\n ////////////////////////////////////\n\n public async getNewCards(limit: number = 99): Promise<StudySessionNewItem[]> {\n const u = await this._getCurrentUser();\n\n try {\n const navigator = await this.createNavigator(u);\n return navigator.getNewCards(limit);\n } catch (e) {\n logger.error(`[courseDB] Error in getNewCards: ${e}`);\n throw e;\n }\n }\n\n public async getPendingReviews(): Promise<(StudySessionReviewItem & ScheduledCard)[]> {\n const u = await this._getCurrentUser();\n\n try {\n const navigator = await this.createNavigator(u);\n return navigator.getPendingReviews();\n } catch (e) {\n logger.error(`[courseDB] Error in getPendingReviews: ${e}`);\n throw e;\n }\n }\n\n /**\n * Get cards with suitability scores for presentation.\n *\n * This is the PRIMARY API for content sources going forward. Delegates to the\n * course's configured NavigationStrategy to get scored candidates.\n *\n * @param limit - Maximum number of cards to return\n * @returns Cards sorted by score descending\n */\n public async getWeightedCards(limit: number): Promise<WeightedCard[]> {\n const u = await this._getCurrentUser();\n\n try {\n const navigator = await this.createNavigator(u);\n return navigator.getWeightedCards(limit);\n } catch (e) {\n logger.error(`[courseDB] Error getting weighted cards: ${e}`);\n throw e;\n }\n }\n\n public async getCardsCenteredAtELO(\n options: {\n limit: number;\n elo: 'user' | 'random' | number;\n } = {\n limit: 99,\n elo: 'user',\n },\n filter?: (a: QualifiedCardID) => boolean\n ): Promise<StudySessionItem[]> {\n let targetElo: number;\n\n if (options.elo === 'user') {\n const u = await this._getCurrentUser();\n\n targetElo = -1;\n try {\n const courseDoc = (await u.getCourseRegistrationsDoc()).courses.find((c) => {\n return c.courseID === this.id;\n })!;\n targetElo = EloToNumber(courseDoc.elo);\n } catch {\n targetElo = 1000;\n }\n } else if (options.elo === 'random') {\n const bounds = await GET_CACHED(`elo-bounds-${this.id}`, () => this.getELOBounds());\n targetElo = Math.round(bounds.low + Math.random() * (bounds.high - bounds.low));\n // logger.log(`Picked ${targetElo} from [${bounds.low}, ${bounds.high}]`);\n } else {\n targetElo = options.elo;\n }\n\n let cards: (QualifiedCardID & { elo?: number })[] = [];\n let mult: number = 4;\n let previousCount: number = -1;\n let newCount: number = 0;\n\n while (cards.length < options.limit && newCount !== previousCount) {\n cards = await this.getCardsByELO(targetElo, mult * options.limit);\n previousCount = newCount;\n newCount = cards.length;\n\n logger.debug(`Found ${cards.length} elo neighbor cards...`);\n\n if (filter) {\n cards = cards.filter(filter);\n logger.debug(`Filtered to ${cards.length} cards...`);\n }\n\n mult *= 2;\n }\n\n const selectedCards: {\n courseID: string;\n cardID: string;\n elo?: number;\n }[] = [];\n\n while (selectedCards.length < options.limit && cards.length > 0) {\n const index = randIntWeightedTowardZero(cards.length);\n const card = cards.splice(index, 1)[0];\n selectedCards.push(card);\n }\n\n return selectedCards.map((c) => {\n return {\n courseID: this.id,\n cardID: c.cardID,\n contentSourceType: 'course',\n contentSourceID: this.id,\n elo: c.elo,\n status: 'new',\n };\n });\n }\n\n // Admin search methods\n public async searchCards(query: string): Promise<any[]> {\n logger.log(`[CourseDB ${this.id}] Searching for: \"${query}\"`);\n\n // Try multiple search approaches\n let displayableData;\n\n try {\n // Try regex search on the correct data structure: data[0].data\n displayableData = await this.db.find({\n selector: {\n docType: 'DISPLAYABLE_DATA',\n 'data.0.data': { $regex: `.*${query}.*` },\n },\n });\n logger.log(`[CourseDB ${this.id}] Regex search on data[0].data successful`);\n } catch (regexError) {\n logger.log(\n `[CourseDB ${this.id}] Regex search failed, falling back to manual search:`,\n regexError\n );\n\n // Fallback: get all displayable data and filter manually\n const allDisplayable = await this.db.find({\n selector: {\n docType: 'DISPLAYABLE_DATA',\n },\n });\n\n logger.log(\n `[CourseDB ${this.id}] Retrieved ${allDisplayable.docs.length} documents for manual filtering`\n );\n\n displayableData = {\n docs: allDisplayable.docs.filter((doc) => {\n // Search entire document as JSON string - inclusive approach for admin tool\n const docString = JSON.stringify(doc).toLowerCase();\n const match = docString.includes(query.toLowerCase());\n if (match) {\n logger.log(`[CourseDB ${this.id}] Manual match found in document: ${doc._id}`);\n }\n return match;\n }),\n };\n }\n\n logger.log(\n `[CourseDB ${this.id}] Found ${displayableData.docs.length} displayable data documents`\n );\n\n if (displayableData.docs.length === 0) {\n // Debug: Let's see what displayable data exists\n const allDisplayableData = await this.db.find({\n selector: {\n docType: 'DISPLAYABLE_DATA',\n },\n limit: 5, // Just sample a few\n });\n\n logger.log(\n `[CourseDB ${this.id}] Sample displayable data:`,\n allDisplayableData.docs.map((d) => ({\n id: d._id,\n docType: (d as any).docType,\n dataStructure: (d as any).data ? Object.keys((d as any).data) : 'no data field',\n dataContent: (d as any).data,\n fullDoc: d,\n }))\n );\n }\n\n const allResults: any[] = [];\n\n for (const dd of displayableData.docs) {\n const cards = await this.db.find({\n selector: {\n docType: 'CARD',\n id_displayable_data: { $in: [dd._id] },\n },\n });\n\n logger.log(\n `[CourseDB ${this.id}] Displayable data ${dd._id} linked to ${cards.docs.length} cards`\n );\n allResults.push(...cards.docs);\n }\n\n logger.log(`[CourseDB ${this.id}] Total cards found: ${allResults.length}`);\n return allResults;\n }\n\n public async find(\n request: PouchDB.Find.FindRequest<any>\n ): Promise<PouchDB.Find.FindResponse<any>> {\n return this.db.find(request);\n }\n}\n\n/**\n * Returns a list of registered datashapes for the specified\n * course.\n * @param courseID The ID of the course\n */\nexport async function getCourseDataShapes(courseID: string) {\n const cfg = await getCredentialledCourseConfig(courseID);\n return cfg!.dataShapes;\n}\n\nexport async function getCredentialledDataShapes(courseID: string) {\n const cfg = await getCredentialledCourseConfig(courseID);\n\n return cfg.dataShapes;\n}\n\nexport async function getCourseQuestionTypes(courseID: string) {\n const cfg = await getCredentialledCourseConfig(courseID);\n return cfg!.questionTypes;\n}\n\n// todo: this is actually returning full tag docs now.\n// - performance issue when tags have lots of\n// applied docs\n// - will require a computed couch DB view\nexport async function getCourseTagStubs(\n courseID: string\n): Promise<PouchDB.Core.AllDocsResponse<Tag>> {\n logger.debug(`Getting tag stubs for course: ${courseID}`);\n const stubs = await filterAllDocsByPrefix<Tag>(\n getCourseDB(courseID),\n DocType.TAG.valueOf() + '-'\n );\n\n stubs.rows.forEach((row) => {\n logger.debug(`\\tTag stub for doc: ${row.id}`);\n });\n\n return stubs;\n}\n\nexport async function deleteTag(courseID: string, tagName: string) {\n tagName = getTagID(tagName);\n const courseDB = getCourseDB(courseID);\n const doc = await courseDB.get<Tag>(DocType.TAG.valueOf() + '-' + tagName);\n const resp = await courseDB.remove(doc);\n return resp;\n}\n\nexport async function createTag(courseID: string, tagName: string, author: string) {\n logger.debug(`Creating tag: ${tagName}...`);\n const tagID = getTagID(tagName);\n const courseDB = getCourseDB(courseID);\n const resp = await courseDB.put<Tag>({\n course: courseID,\n docType: DocType.TAG,\n name: tagName,\n snippet: '',\n taggedCards: [],\n wiki: '',\n author,\n _id: tagID,\n });\n return resp;\n}\n\nexport async function updateTag(tag: Tag) {\n const prior = await getTag(tag.course, tag.name);\n return await getCourseDB(tag.course).put<Tag>({\n ...tag,\n _rev: prior._rev,\n });\n}\n\nexport async function getTag(courseID: string, tagName: string) {\n const tagID = getTagID(tagName);\n const courseDB = getCourseDB(courseID);\n return courseDB.get<Tag>(tagID);\n}\n\nexport async function removeTagFromCard(courseID: string, cardID: string, tagID: string) {\n // todo: possible future perf. hit if tags have large #s of taggedCards.\n // In this case, should be converted to a server-request\n tagID = getTagID(tagID);\n const courseDB = getCourseDB(courseID);\n const tag = await courseDB.get<Tag>(tagID);\n tag.taggedCards = tag.taggedCards.filter((taggedID) => {\n return cardID !== taggedID;\n });\n return courseDB.put<Tag>(tag);\n}\n\n/**\n * Returns an array of ancestor tag IDs, where:\n * return[0] = parent,\n * return[1] = grandparent,\n * return[2] = great grandparent,\n * etc.\n *\n * If ret is empty, the tag itself is a root\n */\nexport function getAncestorTagIDs(courseID: string, tagID: string): string[] {\n tagID = getTagID(tagID);\n const split = tagID.split('>');\n if (split.length === 1) {\n return [];\n } else {\n split.pop();\n const parent = split.join('>');\n return [parent].concat(getAncestorTagIDs(courseID, parent));\n }\n}\n\nexport async function getChildTagStubs(courseID: string, tagID: string) {\n return await filterAllDocsByPrefix(getCourseDB(courseID), tagID + '>');\n}\n\nexport async function getAppliedTags(id_course: string, id_card: string) {\n const db = getCourseDB(id_course);\n\n const result = await db.query<TagStub>('getTags', {\n startkey: id_card,\n endkey: id_card,\n // include_docs: true\n });\n\n // log(`getAppliedTags looked up: ${id_card}`);\n // log(`getAppliedTags returning: ${JSON.stringify(result)}`);\n\n return result;\n}\n\nexport async function updateCardElo(courseID: string, cardID: string, elo: CourseElo) {\n if (elo) {\n // checking against null, undefined, NaN\n const cDB = getCourseDB(courseID);\n const card = await cDB.get<CardData>(cardID);\n logger.debug(`Replacing ${JSON.stringify(card.elo)} with ${JSON.stringify(elo)}`);\n card.elo = elo;\n return cDB.put(card); // race conditions - is it important? probably not (net-zero effect)\n }\n}\n\nexport async function updateCredentialledCourseConfig(courseID: string, config: CourseConfig) {\n logger.debug(`Updating course config:\n\n${JSON.stringify(config)}\n`);\n\n const db = getCourseDB(courseID);\n const old = await getCredentialledCourseConfig(courseID);\n\n return await db.put<CourseConfig>({\n ...config,\n _rev: (old as any)._rev,\n });\n}\n\nfunction isSuccessRow<T>(\n row:\n | {\n key: PouchDB.Core.DocumentKey;\n error: 'not_found';\n }\n | {\n doc?: PouchDB.Core.ExistingDocument<PouchDB.Core.AllDocsMeta & T> | null | undefined;\n id: PouchDB.Core.DocumentId;\n key: PouchDB.Core.DocumentKey;\n value: {\n rev: PouchDB.Core.RevisionId;\n deleted?: boolean | undefined;\n };\n }\n): row is {\n doc?: PouchDB.Core.ExistingDocument<PouchDB.Core.AllDocsMeta & T> | null | undefined;\n id: PouchDB.Core.DocumentId;\n key: PouchDB.Core.DocumentKey;\n value: {\n rev: PouchDB.Core.RevisionId;\n deleted?: boolean | undefined;\n };\n} {\n return 'doc' in row && row.doc !== null && row.doc !== undefined;\n}\n","import {\n StudyContentSource,\n StudySessionNewItem,\n StudySessionReviewItem,\n} from '@db/core/interfaces/contentSource';\nimport { WeightedCard } from '@db/core/navigators';\nimport { ClassroomConfig } from '@vue-skuilder/common';\nimport { ENV } from '@db/factory';\nimport { logger } from '@db/util/logger';\nimport moment from 'moment';\nimport pouch from './pouchdb-setup';\nimport { getCourseDB, getStartAndEndKeys, createPouchDBConfig, REVIEW_TIME_FORMAT } from '.';\nimport { CourseDB, getTag } from './courseDB';\n\nimport { UserDBInterface } from '@db/core';\nimport {\n AssignedContent,\n AssignedCourse,\n AssignedTag,\n StudentClassroomDBInterface,\n TeacherClassroomDBInterface,\n} from '@db/core/interfaces/classroomDB';\nimport { ScheduledCard } from '@db/core/types/user';\n\nconst classroomLookupDBTitle = 'classdb-lookup';\nexport const CLASSROOM_CONFIG = 'ClassroomConfig';\n\nexport type ClassroomMessage = object;\n\nabstract class ClassroomDBBase {\n public _id!: string;\n protected _db!: PouchDB.Database;\n protected _cfg!: ClassroomConfig;\n protected _initComplete: boolean = false;\n\n protected readonly _content_prefix: string = 'content';\n protected get _content_searchkeys() {\n return getStartAndEndKeys(this._content_prefix);\n }\n\n protected abstract init(): Promise<void>;\n\n public async getAssignedContent(): Promise<AssignedContent[]> {\n logger.info(`Getting assigned content...`);\n // see couchdb docs 6.2.2:\n // Guide to Views -> Views Collation -> String Ranges\n const docRows = await this._db.allDocs<AssignedContent>({\n startkey: this._content_prefix,\n endkey: this._content_prefix + `\\ufff0`,\n include_docs: true,\n });\n\n const ret = docRows.rows.map((row) => {\n return row.doc!;\n });\n // logger.info(`Assigned content: ${JSON.stringify(ret)}`);\n\n return ret;\n }\n\n protected getContentId(content: AssignedContent): string {\n if (content.type === 'tag') {\n return `${this._content_prefix}-${content.courseID}-${content.tagID}`;\n } else {\n return `${this._content_prefix}-${content.courseID}`;\n }\n }\n\n public get ready(): boolean {\n return this._initComplete;\n }\n public getConfig(): ClassroomConfig {\n return this._cfg;\n }\n}\n\nexport class StudentClassroomDB\n extends ClassroomDBBase\n implements StudyContentSource, StudentClassroomDBInterface\n{\n // private readonly _prefix: string = 'content';\n private userMessages!: PouchDB.Core.Changes<object>;\n private _user: UserDBInterface;\n\n private constructor(classID: string, user: UserDBInterface) {\n super();\n this._id = classID;\n this._user = user;\n // init() is called explicitly in factory method, not in constructor\n }\n\n async init(): Promise<void> {\n const dbName = `classdb-student-${this._id}`;\n this._db = new pouch(\n ENV.COUCHDB_SERVER_PROTOCOL + '://' + ENV.COUCHDB_SERVER_URL + dbName,\n createPouchDBConfig()\n );\n try {\n const cfg = await this._db.get<ClassroomConfig>(CLASSROOM_CONFIG);\n this._cfg = cfg;\n this.userMessages = this._db.changes({\n since: 'now',\n live: true,\n include_docs: true,\n });\n this._initComplete = true;\n return;\n } catch (e) {\n throw new Error(`Error in StudentClassroomDB constructor: ${JSON.stringify(e)}`);\n }\n }\n\n public static async factory(classID: string, user: UserDBInterface): Promise<StudentClassroomDB> {\n const ret = new StudentClassroomDB(classID, user);\n await ret.init();\n return ret;\n }\n\n public setChangeFcn(f: (value: unknown) => object): void {\n // todo: make this into a view request, w/ the user's name attached\n // todo: requires creating the view doc on classroom create in /express\n void this.userMessages.on('change', f);\n }\n\n public async getPendingReviews(): Promise<(StudySessionReviewItem & ScheduledCard)[]> {\n const u = this._user;\n return (await u.getPendingReviews())\n .filter((r) => r.scheduledFor === 'classroom' && r.schedulingAgentId === this._id)\n .map((r) => {\n return {\n ...r,\n qualifiedID: `${r.courseId}-${r.cardId}`,\n courseID: r.courseId,\n cardID: r.cardId,\n contentSourceType: 'classroom',\n contentSourceID: this._id,\n reviewID: r._id,\n status: 'review',\n };\n });\n }\n\n public async getNewCards(): Promise<StudySessionNewItem[]> {\n const activeCards = await this._user.getActiveCards();\n const now = moment.utc();\n const assigned = await this.getAssignedContent();\n const due = assigned.filter((c) => now.isAfter(moment.utc(c.activeOn, REVIEW_TIME_FORMAT)));\n\n logger.info(`Due content: ${JSON.stringify(due)}`);\n\n let ret: StudySessionNewItem[] = [];\n\n for (let i = 0; i < due.length; i++) {\n const content = due[i];\n\n if (content.type === 'course') {\n const db = new CourseDB(content.courseID, async () => this._user);\n ret = ret.concat(await db.getNewCards());\n } else if (content.type === 'tag') {\n const tagDoc = await getTag(content.courseID, content.tagID);\n\n ret = ret.concat(\n tagDoc.taggedCards.map((c) => {\n return {\n courseID: content.courseID,\n cardID: c,\n qualifiedID: `${content.courseID}-${c}`,\n contentSourceType: 'classroom',\n contentSourceID: this._id,\n status: 'new',\n };\n })\n );\n } else if (content.type === 'card') {\n // returning card docs - not IDs\n ret.push(await getCourseDB(content.courseID).get(content.cardID));\n }\n }\n\n logger.info(\n `New Cards from classroom ${this._cfg.name}: ${ret.map((c) => `${c.courseID}-${c.cardID}`)}`\n );\n\n return ret.filter((c) => {\n if (activeCards.some((ac) => c.cardID === ac.cardID)) {\n // [ ] almost certainly broken after removing qualifiedID from StudySessionItem\n return false;\n } else {\n return true;\n }\n });\n }\n\n /**\n * Get cards with suitability scores for presentation.\n *\n * This implementation wraps the legacy getNewCards/getPendingReviews methods,\n * assigning score=1.0 to all cards. StudentClassroomDB does not currently\n * support pluggable navigation strategies.\n *\n * @param limit - Maximum number of cards to return\n * @returns Cards sorted by score descending (all scores = 1.0)\n */\n public async getWeightedCards(limit: number): Promise<WeightedCard[]> {\n const [newCards, reviews] = await Promise.all([this.getNewCards(), this.getPendingReviews()]);\n\n const weighted: WeightedCard[] = [\n ...newCards.map((c) => ({\n cardId: c.cardID,\n courseId: c.courseID,\n score: 1.0,\n provenance: [\n {\n strategy: 'classroom',\n strategyName: 'Classroom',\n strategyId: 'CLASSROOM',\n action: 'generated' as const,\n score: 1.0,\n reason: 'Classroom legacy getNewCards(), new card',\n },\n ],\n })),\n ...reviews.map((r) => ({\n cardId: r.cardID,\n courseId: r.courseID,\n score: 1.0,\n provenance: [\n {\n strategy: 'classroom',\n strategyName: 'Classroom',\n strategyId: 'CLASSROOM',\n action: 'generated' as const,\n score: 1.0,\n reason: 'Classroom legacy getPendingReviews(), review',\n },\n ],\n })),\n ];\n\n // Sort by score descending (all 1.0 in this case) and limit\n return weighted.slice(0, limit);\n }\n}\n\n/**\n * Interface for managing a classroom.\n */\nexport class TeacherClassroomDB extends ClassroomDBBase implements TeacherClassroomDBInterface {\n private _stuDb!: PouchDB.Database;\n\n private constructor(classID: string) {\n super();\n this._id = classID;\n }\n\n async init(): Promise<void> {\n const dbName = `classdb-teacher-${this._id}`;\n const stuDbName = `classdb-student-${this._id}`;\n this._db = new pouch(\n ENV.COUCHDB_SERVER_PROTOCOL + '://' + ENV.COUCHDB_SERVER_URL + dbName,\n createPouchDBConfig()\n );\n this._stuDb = new pouch(\n ENV.COUCHDB_SERVER_PROTOCOL + '://' + ENV.COUCHDB_SERVER_URL + stuDbName,\n createPouchDBConfig()\n );\n try {\n return this._db\n .get<ClassroomConfig>(CLASSROOM_CONFIG)\n .then((cfg) => {\n this._cfg = cfg;\n this._initComplete = true;\n })\n .then(() => {\n return;\n });\n } catch (e) {\n throw new Error(`Error in TeacherClassroomDB constructor: ${JSON.stringify(e)}`);\n }\n }\n\n public static async factory(classID: string): Promise<TeacherClassroomDB> {\n const ret = new TeacherClassroomDB(classID);\n await ret.init();\n return ret;\n }\n\n public async removeContent(content: AssignedContent): Promise<void> {\n const contentID = this.getContentId(content);\n\n try {\n const doc = await this._db.get(contentID);\n await this._db.remove(doc);\n void this._db.replicate.to(this._stuDb, {\n doc_ids: [contentID],\n });\n } catch (error) {\n logger.error('Failed to remove content:', contentID, error);\n }\n }\n\n public async assignContent(content: AssignedContent): Promise<boolean> {\n let put: PouchDB.Core.Response;\n const id: string = this.getContentId(content);\n\n if (content.type === 'tag') {\n put = await this._db.put<AssignedTag>({\n courseID: content.courseID,\n tagID: content.tagID,\n type: 'tag',\n _id: id,\n assignedBy: content.assignedBy,\n assignedOn: moment.utc(),\n activeOn: content.activeOn || moment.utc(),\n });\n } else {\n put = await this._db.put<AssignedCourse>({\n courseID: content.courseID,\n type: 'course',\n _id: id,\n assignedBy: content.assignedBy,\n assignedOn: moment.utc(),\n activeOn: content.activeOn || moment.utc(),\n });\n }\n\n if (put.ok) {\n void this._db.replicate.to(this._stuDb, {\n doc_ids: [id],\n });\n return true;\n } else {\n return false;\n }\n }\n}\n\nexport const ClassroomLookupDB: () => PouchDB.Database = () =>\n new pouch(ENV.COUCHDB_SERVER_PROTOCOL + '://' + ENV.COUCHDB_SERVER_URL + classroomLookupDBTitle, {\n skip_setup: true,\n });\n\nexport function getClassroomDB(classID: string, version: 'student' | 'teacher'): PouchDB.Database {\n const dbName = `classdb-${version}-${classID}`;\n logger.info(`Retrieving classroom db: ${dbName}`);\n\n return new pouch(\n ENV.COUCHDB_SERVER_PROTOCOL + '://' + ENV.COUCHDB_SERVER_URL + dbName,\n createPouchDBConfig()\n );\n}\n\nexport async function getClassroomConfig(classID: string): Promise<ClassroomConfig> {\n return await getClassroomDB(classID, 'student').get<ClassroomConfig>(CLASSROOM_CONFIG);\n}\n","import pouch from './pouchdb-setup';\nimport { ENV } from '@db/factory';\nimport {\n createPouchDBConfig,\n getStartAndEndKeys,\n getCredentialledCourseConfig,\n updateCredentialledCourseConfig,\n} from '.';\nimport { TeacherClassroomDB, ClassroomLookupDB } from './classroomDB';\nimport { PouchError } from './types';\n\nimport { AdminDBInterface } from '@db/core';\nimport CourseLookup from './courseLookupDB';\nimport { logger } from '@db/util/logger';\n\nexport class AdminDB implements AdminDBInterface {\n private usersDB!: PouchDB.Database;\n\n constructor() {\n // [ ] execute a check here against credentials, and throw an error\n // if the user is not an admin\n this.usersDB = new pouch(\n ENV.COUCHDB_SERVER_PROTOCOL + '://' + ENV.COUCHDB_SERVER_URL + '_users',\n createPouchDBConfig()\n );\n }\n\n public async getUsers() {\n return (\n await this.usersDB.allDocs({\n include_docs: true,\n ...getStartAndEndKeys('org.couchdb.user:'),\n })\n ).rows.map((r) => r.doc!);\n }\n\n public async getCourses() {\n const list = await CourseLookup.allCourseWare();\n return await Promise.all(\n list.map((c) => {\n return getCredentialledCourseConfig(c._id);\n })\n );\n }\n public async removeCourse(id: string) {\n // remove the indexer\n const delResp = await CourseLookup.delete(id);\n\n // set the 'CourseConfig' to 'deleted'\n const cfg = await getCredentialledCourseConfig(id);\n cfg.deleted = true;\n const isDeletedResp = await updateCredentialledCourseConfig(id, cfg);\n\n return {\n ok: delResp.ok && isDeletedResp.ok,\n id: delResp.id,\n rev: delResp.rev,\n };\n }\n\n public async getClassrooms() {\n // const joincodes =\n const uuids = (\n await ClassroomLookupDB().allDocs<{ uuid: string }>({\n include_docs: true,\n })\n ).rows.map((r) => r.doc!.uuid);\n logger.debug(uuids.join(', '));\n\n const promisedCRDbs: TeacherClassroomDB[] = [];\n for (let i = 0; i < uuids.length; i++) {\n try {\n const db = await TeacherClassroomDB.factory(uuids[i]);\n promisedCRDbs.push(db);\n } catch (e) {\n const err = e as PouchError;\n if (err.error && err.error === 'not_found') {\n logger.warn(`db ${uuids[i]} not found`);\n }\n }\n }\n\n const dbs = await Promise.all(promisedCRDbs);\n return dbs.map((db) => {\n return {\n ...db.getConfig(),\n _id: db._id,\n };\n });\n }\n}\n","import { ENV, NOT_SET } from '@db/factory';\nimport { logger } from '@db/util/logger';\nimport fetch from 'cross-fetch';\n\ninterface SessionResponse {\n info: unknown;\n ok: boolean;\n userCtx: {\n name: string;\n roles: string[];\n };\n}\n\nexport async function getCurrentSession(): Promise<SessionResponse> {\n // Legacy XMLHttpRequest implementation\n // return new Promise((resolve, reject) => {\n // const authXML = new XMLHttpRequest();\n // authXML.withCredentials = true;\n //\n // authXML.onerror = (e): void => {\n // reject(new Error('Session check failed:', e));\n // };\n //\n // authXML.addEventListener('load', () => {\n // try {\n // const resp: SessionResponse = JSON.parse(authXML.responseText);\n // resolve(resp);\n // } catch (e) {\n // reject(e);\n // }\n // });\n //\n // const url = `${ENV.COUCHDB_SERVER_PROTOCOL}://${ENV.COUCHDB_SERVER_URL}_session`;\n // authXML.open('GET', url);\n // authXML.send();\n // });\n \n try {\n // Handle case where ENV variables might not be properly set\n if (ENV.COUCHDB_SERVER_URL === NOT_SET || ENV.COUCHDB_SERVER_PROTOCOL === NOT_SET) {\n throw new Error(`CouchDB server configuration not properly initialized. Protocol: \"${ENV.COUCHDB_SERVER_PROTOCOL}\", URL: \"${ENV.COUCHDB_SERVER_URL}\"`);\n }\n \n // Ensure URL has proper slash before _session endpoint\n const baseUrl = ENV.COUCHDB_SERVER_URL.endsWith('/') \n ? ENV.COUCHDB_SERVER_URL.slice(0, -1) \n : ENV.COUCHDB_SERVER_URL;\n const url = `${ENV.COUCHDB_SERVER_PROTOCOL}://${baseUrl}/_session`;\n logger.debug(`Attempting session check at: ${url}`);\n \n const response = await fetch(url, {\n method: 'GET',\n credentials: 'include',\n });\n \n if (!response.ok) {\n throw new Error(`Session check failed: ${response.status}`);\n }\n \n const resp: SessionResponse = await response.json();\n return resp;\n } catch (error) {\n // Use same URL construction logic for error reporting\n const baseUrl = ENV.COUCHDB_SERVER_URL.endsWith('/') \n ? ENV.COUCHDB_SERVER_URL.slice(0, -1) \n : ENV.COUCHDB_SERVER_URL;\n const url = `${ENV.COUCHDB_SERVER_PROTOCOL}://${baseUrl}/_session`;\n logger.error(`Session check error attempting to connect to: ${url} - ${error}`);\n throw new Error(`Session check failed connecting to ${url}: ${error}`);\n }\n}\n\nexport async function getLoggedInUsername(): Promise<string> {\n const session = await getCurrentSession();\n if (session.userCtx.name && session.userCtx.name !== '') {\n return session.userCtx.name;\n }\n // Not logged in - throw so caller can handle guest account\n throw new Error('No logged in user');\n}\n","// packages/db/src/impl/couch/CouchDBSyncStrategy.ts\n\nimport { ENV } from '@db/factory';\nimport { GuestUsername } from '../../core/types/types-legacy';\nimport { logger } from '../../util/logger';\nimport { Status } from '@vue-skuilder/common';\nimport type { SyncStrategy } from '../common/SyncStrategy';\nimport type { AccountCreationResult, AuthenticationResult } from '../common/types';\nimport { getLocalUserDB, hexEncode, updateGuestAccountExpirationDate, accomodateGuest } from '../common';\nimport pouch from './pouchdb-setup';\nimport { createPouchDBConfig } from './index';\nimport { getLoggedInUsername } from './auth';\n\nconst log = (s: any) => {\n logger.info(s);\n};\n\n/**\n * Sync strategy that implements full CouchDB remote synchronization\n * Handles account creation, authentication, and live sync with remote CouchDB server\n */\nexport class CouchDBSyncStrategy implements SyncStrategy {\n private syncHandle?: any; // Handle to cancel sync if needed\n\n setupRemoteDB(username: string): PouchDB.Database {\n if (username === GuestUsername || username.startsWith(GuestUsername)) {\n // For guest users, remote is same as local (no remote sync)\n return getLocalUserDB(username);\n } else {\n // For real users, connect to remote CouchDB\n return this.getUserDB(username);\n }\n }\n\n getWriteDB(username: string): PouchDB.Database {\n if (username === GuestUsername || username.startsWith(GuestUsername)) {\n // Guest users write to local database\n return getLocalUserDB(username);\n } else {\n // Authenticated users write to remote (which will sync to local)\n return this.getUserDB(username);\n }\n }\n\n startSync(localDB: PouchDB.Database, remoteDB: PouchDB.Database): void {\n // Only sync if local and remote are different instances\n if (localDB !== remoteDB) {\n this.syncHandle = pouch.sync(localDB, remoteDB, {\n live: true,\n retry: true,\n });\n }\n // If they're the same (guest mode), no sync needed\n }\n\n stopSync?(): void {\n if (this.syncHandle) {\n this.syncHandle.cancel();\n this.syncHandle = undefined;\n }\n }\n\n canCreateAccount(): boolean {\n return true;\n }\n\n canAuthenticate(): boolean {\n return true;\n }\n\n async createAccount(username: string, password: string): Promise<AccountCreationResult> {\n // IMPORTANT: Capture funnel username BEFORE any operations that might change session state\n const funnelUsername = await this.getCurrentUsername();\n const isFunnelAccount = funnelUsername.startsWith(GuestUsername);\n\n if (isFunnelAccount) {\n logger.info(`Creating account for funnel user ${funnelUsername} -> ${username}`);\n }\n\n try {\n const signupRequest = await this.getRemoteCouchRootDB().signUp(username, password);\n\n if (signupRequest.ok) {\n log(`CREATEACCOUNT: Successfully created account for ${username}`);\n\n // Log out any existing session\n try {\n const logoutResult = await this.getRemoteCouchRootDB().logOut();\n log(`CREATEACCOUNT: logged out: ${logoutResult.ok}`);\n } catch {\n // Ignore logout errors - might not be logged in\n }\n\n // Log in as the new user\n const loginResult = await this.getRemoteCouchRootDB().logIn(username, password);\n log(`CREATEACCOUNT: logged in as new user: ${loginResult.ok}`);\n\n if (loginResult.ok) {\n // Migrate funnel account data if applicable\n if (isFunnelAccount) {\n logger.info(`Migrating data from funnel account ${funnelUsername} to ${username}`);\n const migrationResult = await this.migrateFunnelData(funnelUsername, username);\n if (!migrationResult.success) {\n logger.warn(`Migration failed: ${migrationResult.error}`);\n // Continue anyway - don't block account creation\n }\n }\n\n return {\n status: Status.ok,\n error: undefined,\n };\n } else {\n return {\n status: Status.error,\n error: 'Failed to log in after account creation',\n };\n }\n } else {\n logger.warn(`Signup not OK: ${JSON.stringify(signupRequest)}`);\n return {\n status: Status.error,\n error: 'Account creation failed',\n };\n }\n } catch (e: any) {\n if (e.reason === 'Document update conflict.') {\n return {\n status: Status.error,\n error: 'This username is taken!',\n };\n }\n logger.error(`Error on signup: ${JSON.stringify(e)}`);\n return {\n status: Status.error,\n error: e.message || 'Unknown error during account creation',\n };\n }\n }\n\n async authenticate(username: string, password: string): Promise<AuthenticationResult> {\n try {\n const loginResult = await this.getRemoteCouchRootDB().logIn(username, password);\n\n if (loginResult.ok) {\n log(`Successfully logged in as ${username}`);\n return {\n ok: true,\n };\n } else {\n log(`Login failed for ${username}`);\n return {\n ok: false,\n error: 'Invalid username or password',\n };\n }\n } catch (error: any) {\n logger.error(`Authentication error for ${username}:`, error);\n return {\n ok: false,\n error: error.message || 'Authentication failed',\n };\n }\n }\n\n async logout(): Promise<AuthenticationResult> {\n try {\n const result = await this.getRemoteCouchRootDB().logOut();\n return {\n ok: result.ok,\n error: result.ok ? undefined : 'Logout failed',\n };\n } catch (error: any) {\n logger.error('Logout error:', error);\n return {\n ok: false,\n error: error.message || 'Logout failed',\n };\n }\n }\n\n async getCurrentUsername(): Promise<string> {\n logger.log('[funnel] CouchDBSyncStrategy.getCurrentUsername() called');\n try {\n const loggedInUsername = await getLoggedInUsername();\n logger.log('[funnel] getLoggedInUsername() returned:', loggedInUsername);\n return loggedInUsername;\n } catch (e) {\n // Not logged in - return unique guest account\n logger.log('[funnel] getLoggedInUsername() failed, calling accomodateGuest()');\n logger.log('[funnel] Error was:', e);\n const guestInfo = accomodateGuest();\n logger.log('[funnel] accomodateGuest() returned:', guestInfo);\n return guestInfo.username;\n }\n }\n\n /**\n * Migrate data from funnel account to real account\n */\n private async migrateFunnelData(\n oldUsername: string,\n newUsername: string\n ): Promise<{ success: boolean; error?: string }> {\n try {\n logger.info(`Starting data migration from ${oldUsername} to ${newUsername}`);\n\n const oldLocalDB = getLocalUserDB(oldUsername);\n const newLocalDB = getLocalUserDB(newUsername);\n\n // Get all docs from funnel account\n const allDocs = await oldLocalDB.allDocs({ include_docs: true });\n\n logger.info(`Found ${allDocs.rows.length} documents in funnel account`);\n\n // Filter out design docs and prepare for migration\n const docsToMigrate = allDocs.rows\n .filter(row => !row.id.startsWith('_design/'))\n .map(row => ({\n ...row.doc,\n _rev: undefined, // Remove rev to insert as new\n }));\n\n if (docsToMigrate.length > 0) {\n await newLocalDB.bulkDocs(docsToMigrate);\n logger.info(`Successfully migrated ${docsToMigrate.length} documents from ${oldUsername} to ${newUsername}`);\n } else {\n logger.info('No documents to migrate from funnel account');\n }\n\n return { success: true };\n } catch (error) {\n logger.error('Migration failed:', error);\n return {\n success: false,\n error: error instanceof Error ? error.message : 'Unknown error'\n };\n }\n }\n\n /**\n * Get remote CouchDB root database for authentication operations\n */\n private getRemoteCouchRootDB(): PouchDB.Database {\n const remoteStr: string =\n ENV.COUCHDB_SERVER_PROTOCOL + '://' + ENV.COUCHDB_SERVER_URL + 'skuilder';\n\n try {\n return new pouch(remoteStr, {\n skip_setup: true,\n });\n } catch (error) {\n logger.error('Failed to initialize remote CouchDB connection:', error);\n throw new Error(`Failed to initialize CouchDB: ${JSON.stringify(error)}`);\n }\n }\n\n /**\n * Get remote user database for a specific user\n */\n private getUserDB(username: string): PouchDB.Database {\n const guestAccount: boolean = false;\n\n const hexName = hexEncode(username);\n const dbName = `userdb-${hexName}`;\n log(`Fetching user database: ${dbName} (${username})`);\n\n // Odd construction here the result of a bug in the\n // interaction between pouch, pouch-auth.\n // see: https://github.com/pouchdb-community/pouchdb-authentication/issues/239\n const ret = new pouch(\n ENV.COUCHDB_SERVER_PROTOCOL + '://' + ENV.COUCHDB_SERVER_URL + dbName,\n createPouchDBConfig()\n );\n\n if (guestAccount) {\n updateGuestAccountExpirationDate(ret);\n }\n\n return ret;\n }\n}\n","import { ENV } from '@db/factory';\nimport {\n DocType,\n DocTypePrefixes,\n GuestUsername,\n log,\n SkuilderCourseData,\n} from '../../core/types/types-legacy';\nimport fetch from 'cross-fetch';\n// import { getCurrentUser } from '../../stores/useAuthStore';\nimport moment, { Moment } from 'moment';\nimport { logger } from '@db/util/logger';\n\nimport pouch from './pouchdb-setup';\n\nimport { ScheduledCard } from '@db/core/types/user';\nimport process from 'process';\n\nconst isBrowser = typeof window !== 'undefined';\n\nif (isBrowser) {\n (window as any).process = process; // required as a fix for pouchdb - see #18\n}\n\nconst expiryDocID: string = 'GuestAccountExpirationDate';\n\nconst GUEST_LOCAL_DB = `userdb-${GuestUsername}`;\nexport const localUserDB: PouchDB.Database = new pouch(GUEST_LOCAL_DB);\n\nexport function hexEncode(str: string): string {\n let hex: string;\n let returnStr: string = '';\n\n for (let i = 0; i < str.length; i++) {\n hex = str.charCodeAt(i).toString(16);\n returnStr += ('000' + hex).slice(3);\n }\n\n return returnStr;\n}\nconst pouchDBincludeCredentialsConfig: PouchDB.Configuration.RemoteDatabaseConfiguration = {\n fetch(url: string | Request, opts: RequestInit): Promise<Response> {\n opts.credentials = 'include';\n\n return (pouch as any).fetch(url, opts);\n },\n} as PouchDB.Configuration.RemoteDatabaseConfiguration;\n\n/**\n * Creates PouchDB configuration with appropriate authentication method\n * - Uses HTTP Basic Auth when credentials are available (Node.js/MCP)\n * - Falls back to cookie auth for browser environments\n */\nexport function createPouchDBConfig(): PouchDB.Configuration.RemoteDatabaseConfiguration {\n // Check if running in Node.js with explicit credentials\n const hasExplicitCredentials = ENV.COUCHDB_USERNAME && ENV.COUCHDB_PASSWORD;\n const isNodeEnvironment = typeof window === 'undefined';\n \n if (hasExplicitCredentials && isNodeEnvironment) {\n // Use HTTP Basic Auth for Node.js environments (MCP server)\n return {\n fetch(url: string | Request, opts: RequestInit = {}): Promise<Response> {\n const basicAuth = btoa(`${ENV.COUCHDB_USERNAME}:${ENV.COUCHDB_PASSWORD}`);\n const headers = new Headers(opts.headers || {});\n headers.set('Authorization', `Basic ${basicAuth}`);\n \n const newOpts = {\n ...opts,\n headers: headers\n };\n \n return (pouch as any).fetch(url, newOpts);\n }\n } as PouchDB.Configuration.RemoteDatabaseConfiguration;\n }\n \n // Use cookie-based auth for browser environments or when no explicit credentials\n return pouchDBincludeCredentialsConfig;\n}\n\nfunction getCouchDB(dbName: string): PouchDB.Database {\n return new pouch(\n ENV.COUCHDB_SERVER_PROTOCOL + '://' + ENV.COUCHDB_SERVER_URL + dbName,\n createPouchDBConfig()\n );\n}\n\nexport function getCourseDB(courseID: string): PouchDB.Database {\n // todo: keep a cache of opened courseDBs? need to benchmark this somehow\n return new pouch(\n ENV.COUCHDB_SERVER_PROTOCOL + '://' + ENV.COUCHDB_SERVER_URL + 'coursedb-' + courseID,\n createPouchDBConfig()\n );\n}\n\nexport async function getLatestVersion() {\n try {\n const docs = await getCouchDB('version').allDocs({\n descending: true,\n limit: 1,\n });\n if (docs && docs.rows && docs.rows[0]) {\n return docs.rows[0].id;\n } else {\n return '0.0.0';\n }\n } catch {\n return '-1';\n }\n}\n\n/**\n * Checks the remote couchdb to see if a given username is available\n * @param username The username to be checked\n */\nexport async function usernameIsAvailable(username: string): Promise<boolean> {\n log(`Checking availability of ${username}`);\n \n // Legacy XMLHttpRequest implementation (browser sync)\n // const req = new XMLHttpRequest();\n // const url = ENV.COUCHDB_SERVER_URL + 'userdb-' + hexEncode(username);\n // req.open('HEAD', url, false);\n // req.send();\n // return req.status === 404;\n \n try {\n const url = ENV.COUCHDB_SERVER_URL + 'userdb-' + hexEncode(username);\n const response = await fetch(url, { method: 'HEAD' });\n return response.status === 404;\n } catch (error) {\n log(`Error checking username availability: ${error}`);\n return false;\n }\n}\n\nexport function updateGuestAccountExpirationDate(guestDB: PouchDB.Database<object>) {\n const currentTime = moment.utc();\n const expirationDate: string = currentTime.add(2, 'months').toISOString();\n\n void guestDB\n .get(expiryDocID)\n .then((doc) => {\n return guestDB.put({\n _id: expiryDocID,\n _rev: doc._rev,\n date: expirationDate,\n });\n })\n .catch(() => {\n return guestDB.put({\n _id: expiryDocID,\n date: expirationDate,\n });\n });\n}\n\nexport function getCourseDocs<T extends SkuilderCourseData>(\n courseID: string,\n docIDs: string[],\n options: PouchDB.Core.AllDocsOptions = {}\n) {\n return getCourseDB(courseID).allDocs<T>({\n ...options,\n keys: docIDs,\n });\n}\n\nexport function getCourseDoc<T extends SkuilderCourseData>(\n courseID: string,\n docID: PouchDB.Core.DocumentId,\n options: PouchDB.Core.GetOptions = {}\n): Promise<T> {\n return getCourseDB(courseID).get<T>(docID, options);\n}\n\n/**\n * Returns *all* cards from the parameter courses, in\n * 'qualified' card format (\"courseid-cardid\")\n *\n * @param courseIDs A list of all course_ids to get cards from\n */\nexport async function getRandomCards(courseIDs: string[]) {\n if (courseIDs.length === 0) {\n throw new Error(`getRandomCards:\\n\\tAttempted to get all cards from no courses!`);\n } else {\n const courseResults = await Promise.all(\n courseIDs.map((course) => {\n return getCourseDB(course).find({\n selector: {\n docType: DocType.CARD,\n },\n limit: 1000,\n });\n })\n );\n\n const ret: string[] = [];\n courseResults.forEach((courseCards, index) => {\n courseCards.docs.forEach((doc) => {\n ret.push(`${courseIDs[index]}-${doc._id}`);\n });\n });\n\n return ret;\n }\n}\n\nexport const REVIEW_TIME_FORMAT: string = 'YYYY-MM-DD--kk:mm:ss-SSS';\n\nexport function getCouchUserDB(username: string): PouchDB.Database {\n const guestAccount: boolean = false;\n // console.log(`Getting user db: ${username}`);\n\n const hexName = hexEncode(username);\n const dbName = `userdb-${hexName}`;\n log(`Fetching user database: ${dbName} (${username})`);\n\n // odd construction here the result of a bug in the\n // interaction between pouch, pouch-auth.\n // see: https://github.com/pouchdb-community/pouchdb-authentication/issues/239\n const ret = new pouch(\n ENV.COUCHDB_SERVER_PROTOCOL + '://' + ENV.COUCHDB_SERVER_URL + dbName,\n createPouchDBConfig()\n );\n if (guestAccount) {\n updateGuestAccountExpirationDate(ret);\n }\n\n return ret;\n}\n\nexport function scheduleCardReview(review: {\n user: string;\n course_id: string;\n card_id: PouchDB.Core.DocumentId;\n time: Moment;\n scheduledFor: ScheduledCard['scheduledFor'];\n schedulingAgentId: ScheduledCard['schedulingAgentId'];\n}) {\n const now = moment.utc();\n logger.info(`Scheduling for review in: ${review.time.diff(now, 'h') / 24} days`);\n void getCouchUserDB(review.user).put<ScheduledCard>({\n _id: DocTypePrefixes[DocType.SCHEDULED_CARD] + review.time.format(REVIEW_TIME_FORMAT),\n cardId: review.card_id,\n reviewTime: review.time.toISOString(),\n courseId: review.course_id,\n scheduledAt: now.toISOString(),\n scheduledFor: review.scheduledFor,\n schedulingAgentId: review.schedulingAgentId,\n });\n}\n\nexport function filterAllDocsByPrefix<T>(\n db: PouchDB.Database,\n prefix: string,\n opts?: PouchDB.Core.AllDocsOptions\n) {\n // see couchdb docs 6.2.2:\n // Guide to Views -> Views Collation -> String Ranges\n const options: PouchDB.Core.AllDocsWithinRangeOptions = {\n startkey: prefix,\n endkey: prefix + '\\ufff0',\n include_docs: true,\n };\n\n if (opts) {\n Object.assign(options, opts);\n }\n return db.allDocs<T>(options);\n}\n\nexport function getStartAndEndKeys(key: string): {\n startkey: string;\n endkey: string;\n} {\n return {\n startkey: key,\n endkey: key + '\\ufff0',\n };\n}\n\n//////////////////////\n// Package exports\n//////////////////////\n\nexport * from '../../core/interfaces/contentSource';\nexport * from './adminDB';\nexport * from './classroomDB';\nexport * from './courseAPI';\nexport * from './courseDB';\nexport * from './CouchDBSyncStrategy';\n","import { DocType, DocTypePrefixes } from '@db/core';\nimport { getCardHistoryID } from '@db/core/util';\nimport { CourseElo, Status } from '@vue-skuilder/common';\nimport moment, { Moment } from 'moment';\nimport { GuestUsername } from '../../core/types/types-legacy';\nimport { logger } from '../../util/logger';\n\nimport {\n ClassroomRegistrationDoc,\n UserCourseSetting,\n UserDBInterface,\n UsrCrsDataInterface,\n} from '@db/core';\nimport {\n ActivityRecord,\n CourseRegistration,\n CourseRegistrationDoc,\n ScheduledCard,\n UserConfig,\n} from '@db/core/types/user';\nimport { DocumentUpdater } from '@db/study';\nimport { CardHistory, CardRecord } from '../../core/types/types-legacy';\nimport type { SyncStrategy } from './SyncStrategy';\nimport {\n filterAllDocsByPrefix,\n getStartAndEndKeys,\n REVIEW_TIME_FORMAT,\n getLocalUserDB,\n scheduleCardReviewLocal,\n removeScheduledCardReviewLocal,\n} from './userDBHelpers';\nimport { PouchError } from '../couch/types';\nimport UpdateQueue, { Update } from '../couch/updateQueue';\nimport { UsrCrsData } from '../couch/user-course-relDB';\nimport { getCredentialledCourseConfig } from '../couch/index';\n\nconst log = (s: any) => {\n logger.info(s);\n};\n\n// logger.log(`Connecting to remote: ${remoteStr}`);\n\ninterface DesignDoc {\n _id: string;\n views: {\n [viewName: string]: {\n map: string; // String representation of the map function\n };\n };\n}\n\n/**\n * Base user database implementation that uses a pluggable sync strategy.\n * Handles local storage operations and delegates sync/remote operations to the strategy.\n */\nexport class BaseUser implements UserDBInterface, DocumentUpdater {\n private static _instance: BaseUser;\n private static _initialized: boolean = false;\n\n public static Dummy(syncStrategy: SyncStrategy): BaseUser {\n return new BaseUser('Me', syncStrategy);\n }\n\n static readonly DOC_IDS = {\n CONFIG: 'CONFIG',\n COURSE_REGISTRATIONS: 'CourseRegistrations',\n CLASSROOM_REGISTRATIONS: 'ClassroomRegistrations',\n };\n\n // private email: string;\n private _username: string;\n private syncStrategy: SyncStrategy;\n\n public getUsername(): string {\n return this._username;\n }\n\n public isLoggedIn(): boolean {\n return !this._username.startsWith(GuestUsername);\n }\n\n public remote(): PouchDB.Database {\n return this.remoteDB;\n }\n\n private localDB!: PouchDB.Database;\n private remoteDB!: PouchDB.Database;\n private writeDB!: PouchDB.Database; // Database to use for write operations (local-first approach)\n\n private updateQueue!: UpdateQueue;\n\n public async createAccount(\n username: string,\n password: string\n ): Promise<{\n status: Status;\n error: string;\n }> {\n if (!this.syncStrategy.canCreateAccount()) {\n throw new Error('Account creation not supported by current sync strategy');\n }\n\n if (!this._username.startsWith(GuestUsername)) {\n throw new Error(\n `Cannot create a new account while logged in:\nCurrently logged-in as ${this._username}.`\n );\n }\n\n const result = await this.syncStrategy.createAccount!(username, password);\n\n // If account creation was successful, update the username and reinitialize\n if (result.status === Status.ok) {\n log(`Account created successfully, updating username to ${username}`);\n this._username = username;\n try {\n localStorage.removeItem('sk-guest-uuid');\n } catch (e) {\n logger.warn('localStorage not available (Node.js environment):', e);\n }\n await this.init();\n }\n\n return {\n status: result.status,\n error: result.error || '',\n };\n }\n public async login(username: string, password: string) {\n if (!this.syncStrategy.canAuthenticate()) {\n throw new Error('Authentication not supported by current sync strategy');\n }\n\n if (!this._username.startsWith(GuestUsername) && this._username != username) {\n if (this._username != username) {\n throw new Error(`Cannot change accounts while logged in.\n Log out of account ${this.getUsername()} before logging in as ${username}.`);\n }\n logger.warn(`User ${this._username} is already logged in, but executing login again.`);\n }\n\n const loginResult = await this.syncStrategy.authenticate!(username, password);\n if (loginResult.ok) {\n log(`Logged in as ${username}`);\n this._username = username;\n try {\n localStorage.removeItem('sk-guest-uuid');\n } catch (e) {\n logger.warn('localStorage not available (Node.js environment):', e);\n }\n await this.init();\n }\n return loginResult;\n }\n\n public async resetUserData(): Promise<{ status: Status; error?: string }> {\n // Only allow reset for local-only sync strategies\n if (this.syncStrategy.canAuthenticate()) {\n return {\n status: Status.error,\n error:\n 'Reset user data is only available for local-only mode. Use logout instead for remote sync.',\n };\n }\n\n try {\n const localDB = getLocalUserDB(this._username);\n\n // Get all documents to identify user data to clear\n const allDocs = await localDB.allDocs({ include_docs: false });\n\n // Identify documents to delete (preserve authentication and user identity)\n const docsToDelete = allDocs.rows\n .filter((row) => {\n const id = row.id;\n // Delete user progress data but preserve core user documents\n return (\n id.startsWith(DocTypePrefixes[DocType.CARDRECORD]) || // Card interaction history\n id.startsWith(DocTypePrefixes[DocType.SCHEDULED_CARD]) || // Scheduled reviews\n id === BaseUser.DOC_IDS.COURSE_REGISTRATIONS || // Course registrations\n id === BaseUser.DOC_IDS.CLASSROOM_REGISTRATIONS || // Classroom registrations\n id === BaseUser.DOC_IDS.CONFIG // User config\n );\n })\n .map((row) => ({ _id: row.id, _rev: row.value.rev, _deleted: true }));\n\n if (docsToDelete.length > 0) {\n await localDB.bulkDocs(docsToDelete);\n }\n\n // Reinitialize to create fresh default documents\n await this.init();\n\n return { status: Status.ok };\n } catch (error) {\n logger.error('Failed to reset user data:', error);\n return {\n status: Status.error,\n error: error instanceof Error ? error.message : 'Unknown error during reset',\n };\n }\n }\n\n public async logout() {\n if (!this.syncStrategy.canAuthenticate()) {\n // For strategies that don't support authentication, just switch to guest\n this._username = await this.syncStrategy.getCurrentUsername();\n await this.init();\n return { ok: true };\n }\n\n const ret = await this.syncStrategy.logout!();\n // return to 'guest' mode\n this._username = await this.syncStrategy.getCurrentUsername();\n await this.init();\n\n return ret;\n }\n\n public async get<T>(id: string): Promise<T & PouchDB.Core.RevisionIdMeta> {\n return this.localDB.get<T>(id);\n }\n\n public update<T extends PouchDB.Core.Document<object>>(id: string, update: Update<T>) {\n return this.updateQueue.update(id, update);\n }\n\n public async getCourseRegistrationsDoc(): Promise<\n CourseRegistrationDoc & PouchDB.Core.IdMeta & PouchDB.Core.GetMeta\n > {\n logger.debug(`Fetching courseRegistrations for ${this.getUsername()}`);\n\n let ret;\n\n try {\n const regDoc = await this.localDB.get<CourseRegistrationDoc>(\n BaseUser.DOC_IDS.COURSE_REGISTRATIONS\n );\n return regDoc;\n } catch (e) {\n const err = e as PouchError;\n if (err.status === 404) {\n await this.localDB.put<CourseRegistrationDoc>({\n _id: BaseUser.DOC_IDS.COURSE_REGISTRATIONS,\n courses: [],\n studyWeight: {},\n });\n ret = await this.getCourseRegistrationsDoc();\n } else {\n throw new Error(\n `Unexpected error ${JSON.stringify(e)} in getOrCreateCourseRegistrationDoc...`\n );\n }\n }\n\n return ret;\n }\n\n public async getActiveCourses() {\n const reg = await this.getCourseRegistrationsDoc();\n return reg.courses.filter((c) => {\n return c.status === undefined || c.status === 'active';\n });\n }\n\n /**\n * Returns a promise of the card IDs that the user has\n * a scheduled review for.\n *\n */\n public async getActiveCards() {\n const keys = getStartAndEndKeys(DocTypePrefixes[DocType.SCHEDULED_CARD]);\n\n const reviews = await this.remoteDB.allDocs<ScheduledCard>({\n startkey: keys.startkey,\n endkey: keys.endkey,\n include_docs: true,\n });\n\n return reviews.rows.map((r) => {\n return {\n courseID: r.doc!.courseId,\n cardID: r.doc!.cardId,\n };\n });\n }\n\n public async getActivityRecords(): Promise<ActivityRecord[]> {\n try {\n const hist = await this.getHistory();\n\n const allRecords: ActivityRecord[] = [];\n if (!Array.isArray(hist)) {\n logger.error('getHistory did not return an array:', hist);\n return allRecords;\n }\n\n // Sample the first few records to understand structure\n let sampleCount = 0;\n\n for (let i = 0; i < hist.length; i++) {\n try {\n if (hist[i] && Array.isArray(hist[i]!.records)) {\n hist[i]!.records.forEach((record: CardRecord) => {\n try {\n // Skip this record if timeStamp is missing\n if (!record.timeStamp) {\n return;\n }\n\n let timeStamp;\n\n // Handle different timestamp formats\n if (typeof record.timeStamp === 'object') {\n // It's likely a Moment object\n if (typeof record.timeStamp.toDate === 'function') {\n // It's definitely a Moment object\n timeStamp = record.timeStamp.toISOString();\n } else if (record.timeStamp instanceof Date) {\n // It's a Date object\n timeStamp = record.timeStamp.toISOString();\n } else {\n // Log a sample of unknown object types, but don't flood logger\n if (sampleCount < 3) {\n logger.warn('Unknown timestamp object type:', record.timeStamp);\n sampleCount++;\n }\n return;\n }\n } else if (typeof record.timeStamp === 'string') {\n // It's already a string, but make sure it's a valid date\n const date = new Date(record.timeStamp);\n if (isNaN(date.getTime())) {\n return; // Invalid date string\n }\n timeStamp = record.timeStamp;\n } else if (typeof record.timeStamp === 'number') {\n // Assume it's a Unix timestamp (milliseconds since epoch)\n timeStamp = new Date(record.timeStamp).toISOString();\n } else {\n // Unknown type, skip\n return;\n }\n\n allRecords.push({\n timeStamp,\n courseID: record.courseID || 'unknown',\n cardID: record.cardID || 'unknown',\n timeSpent: record.timeSpent || 0,\n type: 'card_view',\n });\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n } catch (err) {\n // Silently skip problematic records to avoid flooding logs\n }\n });\n }\n } catch (err) {\n logger.error('Error processing history item:', err);\n }\n }\n\n logger.debug(`Found ${allRecords.length} activity records`);\n return allRecords;\n } catch (err) {\n logger.error('Error in getActivityRecords:', err);\n return [];\n }\n }\n\n private async getReviewstoDate(targetDate: Moment, course_id?: string) {\n const keys = getStartAndEndKeys(DocTypePrefixes[DocType.SCHEDULED_CARD]);\n\n const reviews = await this.remoteDB.allDocs<ScheduledCard>({\n startkey: keys.startkey,\n endkey: keys.endkey,\n include_docs: true,\n });\n\n log(\n `Fetching ${this._username}'s scheduled reviews${\n course_id ? ` for course ${course_id}` : ''\n }.`\n );\n return reviews.rows\n .filter((r) => {\n if (r.id.startsWith(DocTypePrefixes[DocType.SCHEDULED_CARD])) {\n const date = moment.utc(\n r.id.substr(DocTypePrefixes[DocType.SCHEDULED_CARD].length),\n REVIEW_TIME_FORMAT\n );\n if (targetDate.isAfter(date)) {\n if (course_id === undefined || r.doc!.courseId === course_id) {\n return true;\n }\n }\n }\n })\n .map((r) => r.doc!);\n }\n\n public async getReviewsForcast(daysCount: number) {\n const time = moment.utc().add(daysCount, 'days');\n return this.getReviewstoDate(time);\n }\n\n public async getPendingReviews(course_id?: string) {\n const now = moment.utc();\n return this.getReviewstoDate(now, course_id);\n }\n\n public async getScheduledReviewCount(course_id: string): Promise<number> {\n return (await this.getPendingReviews(course_id)).length;\n }\n\n public async getRegisteredCourses() {\n const regDoc = await this.getCourseRegistrationsDoc();\n return regDoc.courses.filter((c) => {\n return !c.status || c.status === 'active' || c.status === 'maintenance-mode';\n });\n }\n\n public async getCourseRegDoc(courseID: string) {\n const regDocs = await this.getCourseRegistrationsDoc();\n const ret = regDocs.courses.find((c) => c.courseID === courseID);\n if (ret) {\n return ret;\n } else {\n throw new Error(`Course registration not found for course ID: ${courseID}`);\n }\n }\n\n public async registerForCourse(course_id: string, previewMode: boolean = false) {\n return this.getCourseRegistrationsDoc()\n .then((doc: CourseRegistrationDoc) => {\n const status = previewMode ? 'preview' : 'active';\n logger.debug(`Registering for ${course_id} with status: ${status}`);\n\n const regItem: CourseRegistration = {\n status: status,\n courseID: course_id,\n user: true,\n admin: false,\n moderator: false,\n elo: {\n global: {\n score: 1000,\n count: 0,\n },\n tags: {},\n misc: {},\n },\n };\n\n if (\n doc.courses.filter((course) => {\n return course.courseID === regItem.courseID;\n }).length === 0\n ) {\n log(`It's a new course registration!`);\n doc.courses.push(regItem);\n doc.studyWeight[course_id] = 1;\n } else {\n doc.courses.forEach((c) => {\n log(`Found the previously registered course!`);\n if (c.courseID === course_id) {\n c.status = status;\n }\n });\n }\n\n return this.localDB.put<CourseRegistrationDoc>(doc);\n })\n .catch((e) => {\n log(`Registration failed because of: ${JSON.stringify(e)}`);\n throw e;\n });\n }\n public async dropCourse(course_id: string, dropStatus: CourseRegistration['status'] = 'dropped') {\n return this.getCourseRegistrationsDoc().then((doc) => {\n let index: number = -1;\n for (let i = 0; i < doc.courses.length; i++) {\n if (doc.courses[i].courseID === course_id) {\n index = i;\n }\n }\n\n if (index !== -1) {\n // remove from the relative-weighting of course study\n delete doc.studyWeight[course_id];\n // set drop status\n doc.courses[index].status = dropStatus;\n } else {\n throw new Error(\n `User ${this.getUsername()} is not currently registered for course ${course_id}`\n );\n }\n\n return this.localDB.put<CourseRegistrationDoc>(doc);\n });\n }\n\n public async getCourseInterface(courseId: string): Promise<UsrCrsDataInterface> {\n return new UsrCrsData(this, courseId);\n }\n\n public async getUserEditableCourses() {\n let courseIDs: string[] = [];\n\n const registeredCourses = await this.getCourseRegistrationsDoc();\n\n courseIDs = courseIDs.concat(\n registeredCourses.courses.map((course) => {\n return course.courseID;\n })\n );\n\n const cfgs = await Promise.all(\n courseIDs.map(async (id) => {\n return await getCredentialledCourseConfig(id);\n })\n );\n return cfgs;\n }\n\n public async getConfig(): Promise<UserConfig & PouchDB.Core.IdMeta & PouchDB.Core.GetMeta> {\n const defaultConfig: PouchDB.Core.Document<UserConfig> = {\n _id: BaseUser.DOC_IDS.CONFIG,\n darkMode: false,\n likesConfetti: false,\n sessionTimeLimit: 5,\n };\n\n try {\n const cfg = await this.localDB.get<UserConfig>(BaseUser.DOC_IDS.CONFIG);\n logger.debug('Raw config from DB:', cfg);\n\n return cfg;\n } catch (e) {\n const err = e as PouchError;\n if (err.name && err.name === 'not_found') {\n await this.localDB.put<UserConfig>(defaultConfig);\n return this.getConfig();\n } else {\n logger.error(`Error setting user default config:`, e);\n throw new Error(`Error returning the user's configuration: ${JSON.stringify(e)}`);\n }\n }\n }\n\n public async setConfig(items: Partial<UserConfig>) {\n logger.debug(`Setting Config items ${JSON.stringify(items)}`);\n\n const c = await this.getConfig();\n const put = await this.localDB.put<UserConfig>({\n ...c,\n ...items,\n });\n\n if (put.ok) {\n logger.debug(`Config items set: ${JSON.stringify(items)}`);\n } else {\n logger.error(`Error setting config items: ${JSON.stringify(put)}`);\n }\n }\n\n /**\n *\n * This function should be called *only* by the pouchdb datalayer provider\n * auth store.\n *\n *\n * Anyone else seeking the current user should use the auth store's\n * exported `getCurrentUser` method.\n *\n */\n public static async instance(syncStrategy: SyncStrategy, username?: string): Promise<BaseUser> {\n if (username) {\n BaseUser._instance = new BaseUser(username, syncStrategy);\n await BaseUser._instance.init();\n return BaseUser._instance;\n } else if (BaseUser._instance && BaseUser._initialized) {\n // log(`USER.instance() returning user ${BaseUser._instance._username}`);\n return BaseUser._instance;\n } else if (BaseUser._instance) {\n return new Promise((resolve) => {\n (function waitForUser() {\n if (BaseUser._initialized) {\n return resolve(BaseUser._instance);\n } else {\n setTimeout(waitForUser, 50);\n }\n })();\n });\n } else {\n const guestUsername = await syncStrategy.getCurrentUsername();\n BaseUser._instance = new BaseUser(guestUsername, syncStrategy);\n await BaseUser._instance.init();\n return BaseUser._instance;\n }\n }\n\n private constructor(username: string, syncStrategy: SyncStrategy) {\n BaseUser._initialized = false;\n this._username = username;\n this.syncStrategy = syncStrategy;\n this.setDBandQ();\n }\n\n private setDBandQ() {\n this.localDB = getLocalUserDB(this._username);\n this.remoteDB = this.syncStrategy.setupRemoteDB(this._username);\n // writeDB follows local-first pattern: static mode writes to local, CouchDB writes to remote/local as appropriate\n this.writeDB = this.syncStrategy.getWriteDB\n ? this.syncStrategy.getWriteDB(this._username)\n : this.localDB;\n this.updateQueue = new UpdateQueue(this.localDB, this.writeDB);\n }\n\n private async init() {\n BaseUser._initialized = false;\n\n // Skip admin user\n if (this._username === 'admin') {\n BaseUser._initialized = true;\n return;\n }\n\n this.setDBandQ();\n\n this.syncStrategy.startSync(this.localDB, this.remoteDB);\n this.applyDesignDocs().catch((error) => {\n log(`Error in applyDesignDocs background task: ${error}`);\n if (error && typeof error === 'object') {\n log(`Full error details in applyDesignDocs: ${JSON.stringify(error)}`);\n }\n });\n this.deduplicateReviews().catch((error) => {\n log(`Error in deduplicateReviews background task: ${error}`);\n if (error && typeof error === 'object') {\n log(`Full error details in background task: ${JSON.stringify(error)}`);\n }\n });\n BaseUser._initialized = true;\n }\n\n private static designDocs: DesignDoc[] = [\n {\n _id: '_design/reviewCards',\n views: {\n reviewCards: {\n map: `function (doc) {\n if (doc._id && doc._id.indexOf('card_review') === 0 && doc.courseId && doc.cardId) {\n emit(doc._id, doc.courseId + '-' + doc.cardId);\n }\n }`,\n },\n },\n },\n ];\n\n private async applyDesignDocs() {\n log(`Starting applyDesignDocs for user: ${this._username}`);\n log(`Remote DB name: ${this.remoteDB.name || 'unknown'}`);\n\n if (this._username === 'admin') {\n // Skip admin user\n log('Skipping design docs for admin user');\n return;\n }\n\n log(`Applying ${BaseUser.designDocs.length} design docs`);\n for (const doc of BaseUser.designDocs) {\n log(`Applying design doc: ${doc._id}`);\n try {\n // Try to get existing doc\n try {\n const existingDoc = await this.remoteDB.get(doc._id);\n // Update existing doc\n await this.remoteDB.put({\n ...doc,\n _rev: existingDoc._rev,\n });\n } catch (e: unknown) {\n if (e instanceof Error && e.name === 'not_found') {\n // Create new doc\n await this.remoteDB.put(doc);\n } else {\n throw e; // Re-throw unexpected errors\n }\n }\n } catch (error: unknown) {\n if ((error as any).name && (error as any).name === 'conflict') {\n logger.warn(`Design doc ${doc._id} update conflict - will retry`);\n // Wait a bit and try again\n await new Promise((resolve) => setTimeout(resolve, 1000));\n await this.applyDesignDoc(doc); // Recursive retry\n } else {\n logger.error(`Failed to apply design doc ${doc._id}:`, error);\n throw error;\n }\n }\n }\n }\n\n // Helper method for single doc update with retry\n private async applyDesignDoc(doc: DesignDoc, retries = 3): Promise<void> {\n try {\n const existingDoc = await this.remoteDB.get(doc._id);\n await this.remoteDB.put({\n ...doc,\n _rev: existingDoc._rev,\n });\n } catch (e: unknown) {\n if (e instanceof Error && e.name === 'conflict' && retries > 0) {\n await new Promise((resolve) => setTimeout(resolve, 1000));\n return this.applyDesignDoc(doc, retries - 1);\n }\n throw e;\n }\n }\n\n /**\n * Logs a record of the user's interaction with the card and returns the card's\n * up-to-date history.\n *\n * **Automatic Initialization:**\n * If this is the user's first interaction with the card (CardHistory doesn't exist),\n * this method automatically creates the CardHistory document with initial values\n * (lapses: 0, streak: 0, bestInterval: 0).\n *\n * **Error Handling:**\n * - Handles 404 errors by creating initial CardHistory document\n * - Re-throws all other errors from UpdateQueue\n *\n * // [ ] #db-refactor extract to a smaller scope - eg, UserStudySession\n *\n * @param record - The recent recorded interaction between user and card\n * @returns The updated state of the card's CardHistory data\n * @throws Error if document creation fails or non-404 database error occurs\n */\n\n public async putCardRecord<T extends CardRecord>(\n record: T\n ): Promise<CardHistory<CardRecord> & PouchDB.Core.RevisionIdMeta> {\n const cardHistoryID = getCardHistoryID(record.courseID, record.cardID);\n // stringify the current record to make it writable to couchdb\n record.timeStamp = moment.utc(record.timeStamp).toString() as unknown as Moment;\n\n try {\n const cardHistory = await this.update<CardHistory<T>>(\n cardHistoryID,\n function (h: CardHistory<T>) {\n h.records.push(record);\n h.bestInterval = h.bestInterval || 0;\n h.lapses = h.lapses || 0;\n h.streak = h.streak || 0;\n return h;\n }\n );\n\n // Convert timestamps to moment objects\n cardHistory.records = cardHistory.records.map<T>((record) => {\n const ret: T = {\n ...(record as object),\n } as T;\n ret.timeStamp = moment.utc(record.timeStamp);\n return ret;\n });\n return cardHistory;\n } catch (e) {\n const reason = e as PouchError;\n if (reason.status === 404) {\n try {\n const initCardHistory: CardHistory<T> = {\n _id: cardHistoryID,\n cardID: record.cardID,\n courseID: record.courseID,\n records: [record],\n lapses: 0,\n streak: 0,\n bestInterval: 0,\n };\n const putResult = await this.writeDB.put<CardHistory<T>>(initCardHistory);\n return { ...initCardHistory, _rev: putResult.rev };\n } catch (creationError) {\n throw new Error(\n `Failed to create CardHistory for ${cardHistoryID}. Reason: ${creationError}`\n );\n }\n } else {\n throw new Error(`putCardRecord failed because of:\n name:${reason.name}\n error: ${reason.error}\n message: ${reason.message}`);\n }\n }\n }\n\n private async deduplicateReviews() {\n try {\n log('Starting deduplication of scheduled reviews...');\n log(`Remote DB name: ${this.remoteDB.name || 'unknown'}`);\n log(`Write DB name: ${this.writeDB.name || 'unknown'}`);\n /**\n * Maps the qualified-id of a scheduled review card to\n * the docId of the same scheduled review.\n *\n * EG: {\n * courseId-cardId: 'card_review_2021-06--17:12:165'\n * }\n */\n const reviewsMap: { [index: string]: string } = {};\n const duplicateDocIds: string[] = [];\n\n log(\n `Attempting to query remoteDB for reviewCards/reviewCards. Database: ${this.remoteDB.name || 'unknown'}`\n );\n const scheduledReviews = await this.remoteDB.query<{\n id: string;\n value: string;\n }>('reviewCards/reviewCards');\n\n log(`Found ${scheduledReviews.rows.length} scheduled reviews to process`);\n\n // First pass: identify duplicates\n scheduledReviews.rows.forEach((r) => {\n const qualifiedCardId = r.value; // courseId-cardId\n const docId = r.key; // card_review_2021-06--17:12:165\n\n if (reviewsMap[qualifiedCardId]) {\n // this card is scheduled more than once! mark the earlier one for deletion\n log(`Found duplicate scheduled review for card: ${qualifiedCardId}`);\n log(\n `Marking earlier review ${reviewsMap[qualifiedCardId]} for deletion, keeping ${docId}`\n );\n duplicateDocIds.push(reviewsMap[qualifiedCardId]);\n // replace with the later-dated scheduled review\n reviewsMap[qualifiedCardId] = docId;\n } else {\n // note that this card is scheduled for review\n reviewsMap[qualifiedCardId] = docId;\n }\n });\n\n // Second pass: remove duplicates\n if (duplicateDocIds.length > 0) {\n log(`Removing ${duplicateDocIds.length} duplicate reviews...`);\n const deletePromises = duplicateDocIds.map(async (docId) => {\n try {\n const doc = await this.remoteDB.get(docId);\n await this.writeDB.remove(doc);\n log(`Successfully removed duplicate review: ${docId}`);\n } catch (error) {\n log(`Failed to remove duplicate review ${docId}: ${error}`);\n }\n });\n\n await Promise.all(deletePromises);\n log(`Deduplication complete. Processed ${duplicateDocIds.length} duplicates`);\n } else {\n log('No duplicate reviews found');\n }\n } catch (error) {\n log(`Error during review deduplication: ${error}`);\n if (error && typeof error === 'object' && 'status' in error && error.status === 404) {\n log(\n `Database not found (404) during review deduplication. Database: ${this.remoteDB.name || 'unknown'}`\n );\n log(\n `This might indicate the user database doesn't exist or the reviewCards view isn't available`\n );\n }\n // Log full error details for debugging\n if (error && typeof error === 'object') {\n log(`Full error details: ${JSON.stringify(error)}`);\n }\n }\n }\n\n /**\n * Returns a promise of the card IDs that the user has\n * encountered in the past.\n *\n * @param course_id optional specification of individual course\n */\n async getSeenCards(course_id?: string) {\n let prefix = DocTypePrefixes[DocType.CARDRECORD];\n if (course_id) {\n prefix += course_id;\n }\n const docs = await filterAllDocsByPrefix(this.localDB, prefix, {\n include_docs: false,\n });\n // const docs = await this.localDB.allDocs({});\n const ret: PouchDB.Core.DocumentId[] = [];\n docs.rows.forEach((row) => {\n if (row.id.startsWith(DocTypePrefixes[DocType.CARDRECORD])) {\n ret.push(row.id.substr(DocTypePrefixes[DocType.CARDRECORD].length));\n }\n });\n return ret;\n }\n\n /**\n *\n * @returns A promise of the cards that the user has seen in the past.\n */\n async getHistory() {\n const cards = await filterAllDocsByPrefix<CardHistory<CardRecord>>(\n this.remoteDB,\n DocTypePrefixes[DocType.CARDRECORD],\n {\n include_docs: true,\n attachments: false,\n }\n );\n return cards.rows.map((r) => r.doc);\n }\n\n async updateCourseSettings(course_id: string, settings: UserCourseSetting[]) {\n void this.getCourseRegistrationsDoc().then((doc) => {\n const crs = doc.courses.find((c) => c.courseID === course_id);\n if (crs) {\n if (crs.settings === null || crs.settings === undefined) {\n crs.settings = {};\n }\n settings.forEach((setting) => {\n crs!.settings![setting.key] = setting.value;\n });\n }\n\n return this.localDB.put(doc);\n });\n }\n async getCourseSettings(course_id: string) {\n const regDoc = await this.getCourseRegistrationsDoc();\n const crsDoc = regDoc.courses.find((c) => c.courseID === course_id);\n\n if (crsDoc) {\n return crsDoc.settings;\n } else {\n throw new Error(`getCourseSettings Failed:\n User is not registered for course ${course_id}`);\n }\n }\n\n private async getOrCreateClassroomRegistrationsDoc(): Promise<\n ClassroomRegistrationDoc & PouchDB.Core.IdMeta & PouchDB.Core.GetMeta\n > {\n let ret;\n\n try {\n ret = await this.remoteDB.get<ClassroomRegistrationDoc>(\n BaseUser.DOC_IDS.CLASSROOM_REGISTRATIONS\n );\n } catch (e) {\n const err = e as PouchError;\n\n if (err.status === 404) {\n // doc does not exist. Create it and then run this fcn again.\n await this.writeDB.put<ClassroomRegistrationDoc>({\n _id: BaseUser.DOC_IDS.CLASSROOM_REGISTRATIONS,\n registrations: [],\n });\n ret = await this.getOrCreateClassroomRegistrationsDoc();\n } else {\n // Properly serialize error information\n const errorDetails = {\n name: err.name,\n status: err.status,\n message: err.message,\n reason: err.reason,\n error: err.error,\n };\n\n logger.error(\n 'Database error in getOrCreateClassroomRegistrationsDoc (private method):',\n errorDetails\n );\n\n throw new Error(\n `Database error accessing classroom registrations: ${err.message || err.name || 'Unknown error'} (status: ${err.status})`\n );\n }\n }\n\n logger.debug(`Returning classroom registrations doc: ${JSON.stringify(ret)}`);\n return ret;\n }\n\n /**\n * Retrieves the list of active classroom IDs where the user is registered as a student.\n *\n * @returns Promise<string[]> - Array of classroom IDs, or empty array if classroom\n * registration document is unavailable due to database errors\n *\n * @description This method gracefully handles database connectivity issues by returning\n * an empty array when the classroom registrations document cannot be accessed.\n * This ensures that users can still access other application features even\n * when classroom functionality is temporarily unavailable.\n */\n public async getActiveClasses(): Promise<string[]> {\n try {\n return (await this.getOrCreateClassroomRegistrationsDoc()).registrations\n .filter((c) => c.registeredAs === 'student')\n .map((c) => c.classID);\n } catch (error) {\n logger.warn(\n 'Failed to load classroom registrations, continuing without classroom data:',\n error\n );\n // Return empty array so user can still access other features\n return [];\n }\n }\n\n public async scheduleCardReview(review: {\n user: string;\n course_id: string;\n card_id: PouchDB.Core.DocumentId;\n time: Moment;\n scheduledFor: ScheduledCard['scheduledFor'];\n schedulingAgentId: ScheduledCard['schedulingAgentId'];\n }) {\n return scheduleCardReviewLocal(this.writeDB, review);\n }\n public async removeScheduledCardReview(reviewId: string): Promise<void> {\n return removeScheduledCardReviewLocal(this.writeDB, reviewId);\n }\n\n public async registerForClassroom(\n _classId: string,\n _registerAs: 'student' | 'teacher' | 'aide' | 'admin'\n ): Promise<PouchDB.Core.Response> {\n return registerUserForClassroom(this._username, _classId, _registerAs);\n }\n\n public async dropFromClassroom(classId: string): Promise<PouchDB.Core.Response> {\n return dropUserFromClassroom(this._username, classId);\n }\n public async getUserClassrooms(): Promise<ClassroomRegistrationDoc> {\n return getUserClassrooms(this._username);\n }\n\n public async updateUserElo(courseId: string, elo: CourseElo): Promise<PouchDB.Core.Response> {\n return updateUserElo(this._username, courseId, elo);\n }\n}\n\nexport function accomodateGuest(): {\n username: string;\n firstVisit: boolean;\n} {\n logger.log('[funnel] accomodateGuest() called');\n\n // Check if localStorage is available (browser environment)\n if (typeof localStorage === 'undefined') {\n logger.log('[funnel] localStorage not available (Node.js environment), returning default guest');\n return {\n username: GuestUsername + 'nodejs-test',\n firstVisit: true,\n };\n }\n\n const dbUUID = 'sk-guest-uuid';\n let firstVisit: boolean;\n\n const existingUUID = localStorage.getItem(dbUUID);\n logger.log('[funnel] Checking localStorage for key:', dbUUID);\n logger.log('[funnel] Existing UUID value:', existingUUID);\n logger.log('[funnel] existingUUID !== null:', existingUUID !== null);\n\n if (existingUUID !== null) {\n firstVisit = false;\n logger.log(`[funnel] Returning guest ${existingUUID} \"logging in\".`);\n } else {\n firstVisit = true;\n logger.log('[funnel] No existing UUID, generating new one...');\n const uuid = generateUUID();\n logger.log('[funnel] Generated UUID:', uuid);\n logger.log('[funnel] UUID length:', uuid.length);\n\n try {\n localStorage.setItem(dbUUID, uuid);\n logger.log('[funnel] Successfully stored UUID in localStorage');\n const verification = localStorage.getItem(dbUUID);\n logger.log('[funnel] Verification read from localStorage:', verification);\n } catch (e) {\n logger.error('[funnel] ERROR storing UUID:', e);\n }\n\n logger.log(`[funnel] Accommodating a new guest with account: ${uuid}`);\n }\n\n const finalUUID = localStorage.getItem(dbUUID);\n const finalUsername = GuestUsername + finalUUID;\n logger.log('[funnel] Final UUID from localStorage:', finalUUID);\n logger.log('[funnel] GuestUsername constant:', GuestUsername);\n logger.log('[funnel] Final username to return:', finalUsername);\n\n return {\n username: finalUsername,\n firstVisit: firstVisit,\n };\n\n // Use cryptographically secure UUID generation\n function generateUUID() {\n logger.log('[funnel] Inside generateUUID()');\n\n // Use crypto.randomUUID() if available (Node 14.17+ / modern browsers)\n if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {\n const uuid = crypto.randomUUID();\n logger.log('[funnel] Generated UUID using crypto.randomUUID():', uuid);\n return uuid;\n }\n\n // Fallback for older environments: use crypto.getRandomValues()\n if (typeof crypto !== 'undefined' && typeof crypto.getRandomValues === 'function') {\n const bytes = new Uint8Array(16);\n crypto.getRandomValues(bytes);\n\n // Set version (4) and variant bits according to RFC 4122\n bytes[6] = (bytes[6] & 0x0f) | 0x40; // Version 4\n bytes[8] = (bytes[8] & 0x3f) | 0x80; // Variant 10\n\n const uuid = [\n Array.from(bytes.slice(0, 4)).map(b => b.toString(16).padStart(2, '0')).join(''),\n Array.from(bytes.slice(4, 6)).map(b => b.toString(16).padStart(2, '0')).join(''),\n Array.from(bytes.slice(6, 8)).map(b => b.toString(16).padStart(2, '0')).join(''),\n Array.from(bytes.slice(8, 10)).map(b => b.toString(16).padStart(2, '0')).join(''),\n Array.from(bytes.slice(10, 16)).map(b => b.toString(16).padStart(2, '0')).join(''),\n ].join('-');\n\n logger.log('[funnel] Generated UUID using crypto.getRandomValues():', uuid);\n return uuid;\n }\n\n // Last resort fallback (should never happen in modern environments)\n logger.warn('[funnel] crypto API not available, using timestamp-based UUID (NOT SECURE)');\n let d = new Date().getTime();\n if (typeof performance !== 'undefined' && typeof performance.now === 'function') {\n d += performance.now();\n }\n const uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {\n const r = (d + Math.random() * 16) % 16 | 0;\n d = Math.floor(d / 16);\n return (c === 'x' ? r : (r & 0x3) | 0x8).toString(16);\n });\n logger.log('[funnel] Generated UUID (fallback):', uuid);\n return uuid;\n }\n}\n\nconst userCoursesDoc = 'CourseRegistrations';\nconst userClassroomsDoc = 'ClassroomRegistrations';\n\nasync function getOrCreateClassroomRegistrationsDoc(\n user: string\n): Promise<ClassroomRegistrationDoc & PouchDB.Core.IdMeta & PouchDB.Core.GetMeta> {\n let ret;\n\n try {\n ret = await getLocalUserDB(user).get<ClassroomRegistrationDoc>(userClassroomsDoc);\n } catch (e) {\n const err = e as PouchError;\n\n if (err.status === 404) {\n // doc does not exist. Create it and then run this fcn again.\n await getLocalUserDB(user).put<ClassroomRegistrationDoc>({\n _id: userClassroomsDoc,\n registrations: [],\n });\n ret = await getOrCreateClassroomRegistrationsDoc(user);\n } else {\n // Properly serialize error information\n const errorDetails = {\n name: err.name,\n status: err.status,\n message: err.message,\n reason: err.reason,\n error: err.error,\n };\n\n logger.error(\n 'Database error in getOrCreateClassroomRegistrationsDoc (standalone function):',\n errorDetails\n );\n\n throw new Error(\n `Database error accessing classroom registrations: ${err.message || err.name || 'Unknown error'} (status: ${err.status})`\n );\n }\n }\n\n return ret;\n}\n\nasync function getOrCreateCourseRegistrationsDoc(\n user: string\n): Promise<CourseRegistrationDoc & PouchDB.Core.IdMeta & PouchDB.Core.GetMeta> {\n let ret;\n\n try {\n ret = await getLocalUserDB(user).get<CourseRegistrationDoc>(userCoursesDoc);\n } catch (e) {\n const err = e as PouchError;\n if (err.status === 404) {\n // doc does not exist. Create it and then run this fcn again.\n await getLocalUserDB(user).put<CourseRegistrationDoc>({\n _id: userCoursesDoc,\n courses: [],\n studyWeight: {},\n });\n ret = await getOrCreateCourseRegistrationsDoc(user);\n } else {\n throw new Error(\n `Unexpected error ${JSON.stringify(e)} in getOrCreateCourseRegistrationDoc...`\n );\n }\n }\n\n return ret;\n}\n\nexport async function updateUserElo(user: string, course_id: string, elo: CourseElo) {\n const regDoc = await getOrCreateCourseRegistrationsDoc(user);\n const course = regDoc.courses.find((c) => c.courseID === course_id)!;\n course.elo = elo;\n return getLocalUserDB(user).put(regDoc);\n}\n\nexport async function registerUserForClassroom(\n user: string,\n classID: string,\n registerAs: 'student' | 'teacher' | 'aide' | 'admin'\n) {\n log(`Registering user: ${user} in course: ${classID}`);\n return getOrCreateClassroomRegistrationsDoc(user).then((doc) => {\n const regItem = {\n classID: classID,\n registeredAs: registerAs,\n };\n\n if (\n doc.registrations.filter((reg) => {\n return reg.classID === regItem.classID && reg.registeredAs === regItem.registeredAs;\n }).length === 0\n ) {\n doc.registrations.push(regItem);\n } else {\n log(`User ${user} is already registered for class ${classID}`);\n }\n\n return getLocalUserDB(user).put(doc);\n });\n}\n\nexport async function dropUserFromClassroom(user: string, classID: string) {\n return getOrCreateClassroomRegistrationsDoc(user).then((doc) => {\n let index: number = -1;\n\n for (let i = 0; i < doc.registrations.length; i++) {\n if (doc.registrations[i].classID === classID) {\n index = i;\n }\n }\n\n if (index !== -1) {\n doc.registrations.splice(index, 1);\n }\n return getLocalUserDB(user).put(doc);\n });\n}\n\nexport async function getUserClassrooms(user: string) {\n return getOrCreateClassroomRegistrationsDoc(user);\n}\n","// packages/db/src/impl/common/index.ts\n\nexport type { SyncStrategy } from './SyncStrategy';\nexport { BaseSyncStrategy } from './SyncStrategy';\nexport type {\n AccountCreationResult,\n AuthenticationResult,\n UserSession,\n SyncConfig,\n SyncStatus,\n} from './types';\nexport { BaseUser, accomodateGuest } from './BaseUserDB';\nexport {\n REVIEW_TIME_FORMAT,\n hexEncode,\n filterAllDocsByPrefix,\n getStartAndEndKeys,\n updateGuestAccountExpirationDate,\n getLocalUserDB,\n scheduleCardReviewLocal,\n removeScheduledCardReviewLocal,\n} from './userDBHelpers';\n","// db/src/impl/couch/PouchDataLayerProvider.ts\n\nimport {\n AdminDBInterface,\n ClassroomDBInterface,\n CoursesDBInterface,\n CourseDBInterface,\n DataLayerProvider,\n UserDBInterface,\n UserDBReader,\n} from '../../core/interfaces';\nimport { logger } from '../../util/logger';\nimport { initializeDataDirectory } from '../../util/dataDirectory';\n\nimport { getLoggedInUsername } from './auth';\n\nimport { AdminDB } from './adminDB';\nimport { StudentClassroomDB, TeacherClassroomDB } from './classroomDB';\nimport { CourseDB, CoursesDB } from './courseDB';\n\nimport { BaseUser } from '../common';\nimport { CouchDBSyncStrategy } from './CouchDBSyncStrategy';\n\nexport class CouchDataLayerProvider implements DataLayerProvider {\n private initialized: boolean = false;\n private userDB!: UserDBInterface;\n private currentUsername: string = '';\n\n // the scoped list of courseIDs for a UI focused on a specific course\n // or group of courses\n private _courseIDs: string[] = [];\n\n constructor(coursIDs?: string[]) {\n if (coursIDs) {\n this._courseIDs = coursIDs;\n }\n }\n\n async initialize(): Promise<void> {\n if (this.initialized) return;\n\n // Check if we are in a Node.js environment\n const isNodeEnvironment =\n typeof process !== 'undefined' && process.versions != null && process.versions.node != null;\n\n if (isNodeEnvironment) {\n logger.info(\n 'CouchDataLayerProvider: Running in Node.js environment, creating guest UserDB for testing.'\n );\n await initializeDataDirectory();\n // In Node.js (testing) environment, create a guest user instance\n const syncStrategy = new CouchDBSyncStrategy();\n this.userDB = await BaseUser.instance(syncStrategy);\n } else {\n // Assume browser-like environment, proceed with user session logic\n // Let CouchDBSyncStrategy.getCurrentUsername() handle both logged-in and guest users\n const syncStrategy = new CouchDBSyncStrategy();\n this.userDB = await BaseUser.instance(syncStrategy);\n this.currentUsername = this.userDB.getUsername();\n logger.debug(`Current username: ${this.currentUsername}`);\n }\n\n this.initialized = true;\n }\n\n async teardown(): Promise<void> {\n // Close connections, etc.\n this.initialized = false;\n }\n\n getUserDB(): UserDBInterface {\n return this.userDB;\n }\n\n getCourseDB(courseId: string): CourseDBInterface {\n return new CourseDB(courseId, async () => this.getUserDB());\n }\n\n getCoursesDB(): CoursesDBInterface {\n return new CoursesDB(this._courseIDs);\n }\n\n async getClassroomDB(\n classId: string,\n type: 'student' | 'teacher'\n ): Promise<ClassroomDBInterface> {\n if (type === 'student') {\n return await StudentClassroomDB.factory(classId, this.getUserDB());\n } else {\n return await TeacherClassroomDB.factory(classId);\n }\n }\n\n getAdminDB(): AdminDBInterface {\n return new AdminDB();\n }\n\n async createUserReaderForUser(targetUsername: string): Promise<UserDBReader> {\n // Security check: only admin can access other users' data\n const requestingUsername = await getLoggedInUsername();\n if (requestingUsername !== 'admin') {\n throw new Error('Unauthorized: Only admin users can access other users\\' data');\n }\n\n logger.info(`Admin user '${requestingUsername}' requesting UserDBReader for '${targetUsername}'`);\n\n // Create a new sync strategy for the target user\n const syncStrategy = new CouchDBSyncStrategy();\n \n // Create a BaseUser instance for the target user\n // Note: This creates a read-capable user instance without affecting the current session\n const targetUserDB = await BaseUser.instance(syncStrategy, targetUsername);\n \n // Return as UserDBReader (which BaseUser implements since UserDBInterface extends UserDBReader)\n return targetUserDB as UserDBReader;\n }\n\n isReadOnly(): boolean {\n return false;\n }\n}\n","// packages/db/src/impl/static/StaticDataUnpacker.ts\n\nimport { StaticCourseManifest, ChunkMetadata } from '../../util/packer/types';\nimport { logger } from '../../util/logger';\nimport { DocType, DocTypePrefixes } from '@db/core';\n\n// Browser-compatible path utilities\nconst pathUtils = {\n isAbsolute: (path: string): boolean => {\n // Check for Windows absolute paths (C:\\ or \\\\server\\)\n if (/^[a-zA-Z]:[\\\\/]/.test(path) || /^\\\\\\\\/.test(path)) {\n return true;\n }\n // Check for Unix absolute paths (/)\n if (path.startsWith('/')) {\n return true;\n }\n return false;\n },\n};\n\n// Check if we're in Node.js environment and fs is available\nlet nodeFS: any = null;\ntry {\n // Use eval to prevent bundlers from including fs in browser builds\n if (typeof window === 'undefined' && typeof process !== 'undefined' && process.versions?.node) {\n nodeFS = eval('require')('fs');\n }\n} catch {\n // fs not available, will use fetch\n}\n\ninterface EloIndexEntry {\n elo: number;\n cardId: string;\n}\n\ninterface EloIndex {\n sorted: EloIndexEntry[];\n buckets: Record<string, EloIndexEntry[]>;\n stats: {\n min: number;\n max: number;\n count: number;\n };\n}\n\ninterface TagsIndex {\n byTag: Record<string, string[]>; // tagName -> cardIds\n byCard: Record<string, string[]>; // cardId -> tagNames\n}\n\nexport class StaticDataUnpacker {\n private manifest: StaticCourseManifest;\n private basePath: string;\n private documentCache: Map<string, any> = new Map();\n private chunkCache: Map<string, any[]> = new Map();\n private indexCache: Map<string, any> = new Map();\n\n constructor(manifest: StaticCourseManifest, basePath: string) {\n this.manifest = manifest;\n this.basePath = basePath;\n }\n\n /**\n * Get a document by ID, loading from appropriate chunk if needed\n */\n async getDocument<T = any>(id: string): Promise<T> {\n // Check document cache first\n if (this.documentCache.has(id)) {\n const doc = this.documentCache.get(id);\n return await this.hydrateAttachments(doc);\n }\n\n // Find which chunk contains this document\n const chunk = await this.findChunkForDocument(id);\n if (!chunk) {\n logger.error(\n `Document ${id} not found in any chunk. Available chunks:`,\n this.manifest.chunks.map((c) => `${c.id} (${c.docType}): ${c.startKey} - ${c.endKey}`)\n );\n throw new Error(`Document ${id} not found in any chunk`);\n }\n\n // Load the chunk if not cached\n await this.loadChunk(chunk.id);\n\n // Try to get the document from cache again\n if (this.documentCache.has(id)) {\n const doc = this.documentCache.get(id);\n return await this.hydrateAttachments(doc);\n }\n\n logger.error(`Document ${id} not found in chunk ${chunk.id}`);\n throw new Error(`Document ${id} not found in chunk ${chunk.id}`);\n }\n\n /**\n * Query cards by ELO score, returning card IDs sorted by ELO\n */\n async queryByElo(targetElo: number, limit: number = 25): Promise<string[]> {\n const eloIndex = (await this.loadIndex('elo')) as EloIndex;\n\n if (!eloIndex || !eloIndex.sorted) {\n logger.warn('ELO index not found or malformed, returning empty results');\n return [];\n }\n\n // Find cards near the target ELO\n const sorted = eloIndex.sorted;\n let startIndex = 0;\n\n // Binary search to find insertion point for target ELO\n let left = 0;\n let right = sorted.length - 1;\n while (left <= right) {\n const mid = Math.floor((left + right) / 2);\n if (sorted[mid].elo < targetElo) {\n left = mid + 1;\n } else {\n right = mid - 1;\n }\n }\n startIndex = left;\n\n // Collect cards around the target ELO\n const result: string[] = [];\n const halfLimit = Math.floor(limit / 2);\n\n // Get cards below target ELO\n for (\n let i = Math.max(0, startIndex - halfLimit);\n i < startIndex && result.length < limit;\n i++\n ) {\n result.push(sorted[i].cardId);\n }\n\n // Get cards at or above target ELO\n for (let i = startIndex; i < sorted.length && result.length < limit; i++) {\n result.push(sorted[i].cardId);\n }\n\n return result;\n }\n\n /**\n * Get all tag names mapped to their card arrays\n */\n async getTagsIndex(): Promise<TagsIndex> {\n return (await this.loadIndex('tags')) as TagsIndex;\n }\n\n private getDocTypeFromId(id: string): DocType | undefined {\n for (const docTypeKey in DocTypePrefixes) {\n const prefix = DocTypePrefixes[docTypeKey as DocType];\n if (id.startsWith(`${prefix}-`)) {\n return docTypeKey as DocType;\n }\n }\n return undefined;\n }\n\n /**\n * Find which chunk contains a specific document ID\n */\n private async findChunkForDocument(docId: string): Promise<ChunkMetadata | undefined> {\n const expectedDocType = this.getDocTypeFromId(docId);\n\n if (expectedDocType) {\n const typeChunks = this.manifest.chunks.filter((c) => c.docType === expectedDocType);\n\n for (const chunk of typeChunks) {\n if (docId >= chunk.startKey && docId <= chunk.endKey) {\n const exists = await this.verifyDocumentInChunk(docId, chunk);\n if (exists) {\n return chunk;\n }\n }\n }\n } else {\n // Fallback for documents without recognized prefixes (e.g., CourseConfig, or old documents)\n // This part remains for backward compatibility and non-prefixed documents.\n // It's less efficient but necessary if not all document types are prefixed.\n for (const chunk of this.manifest.chunks) {\n if (docId >= chunk.startKey && docId <= chunk.endKey) {\n // Verify document actually exists in chunk\n const exists = await this.verifyDocumentInChunk(docId, chunk);\n if (exists) {\n return chunk;\n }\n }\n }\n\n // Finally try any other chunk types\n const otherChunks = this.manifest.chunks.filter(\n (c) => c.docType !== 'CARD' && c.docType !== 'DISPLAYABLE_DATA' && c.docType !== 'TAG'\n );\n for (const chunk of otherChunks) {\n if (docId >= chunk.startKey && docId <= chunk.endKey) {\n // Verify document actually exists in chunk\n const exists = await this.verifyDocumentInChunk(docId, chunk);\n if (exists) {\n return chunk;\n }\n }\n }\n\n return undefined;\n }\n return undefined;\n }\n\n /**\n * Verify that a document actually exists in a specific chunk by loading and checking it\n */\n private async verifyDocumentInChunk(docId: string, chunk: ChunkMetadata): Promise<boolean> {\n try {\n // Load the chunk if not already cached\n await this.loadChunk(chunk.id);\n\n // Check if the document is now in our document cache\n return this.documentCache.has(docId);\n } catch {\n return false;\n }\n }\n\n /**\n * Load a chunk file and cache its documents\n */\n private async loadChunk(chunkId: string): Promise<void> {\n if (this.chunkCache.has(chunkId)) {\n return; // Already loaded\n }\n\n const chunk = this.manifest.chunks.find((c) => c.id === chunkId);\n if (!chunk) {\n throw new Error(`Chunk ${chunkId} not found in manifest`);\n }\n\n try {\n const chunkPath = `${this.basePath}/${chunk.path}`;\n logger.debug(`Loading chunk from ${chunkPath}`);\n\n let documents: any[];\n\n // Check if we're in a Node.js environment with local files\n if (this.isLocalPath(chunkPath) && nodeFS) {\n // Use fs for local file access (e.g., in tests)\n const fileContent = await nodeFS.promises.readFile(chunkPath, 'utf8');\n documents = JSON.parse(fileContent);\n } else {\n // Use fetch for URL-based access (e.g., in browser)\n const response = await fetch(chunkPath);\n if (!response.ok) {\n throw new Error(\n `Failed to fetch chunk ${chunkId}: ${response.status} ${response.statusText}`\n );\n }\n documents = await response.json();\n }\n\n this.chunkCache.set(chunkId, documents);\n\n // Cache individual documents for quick lookup\n for (const doc of documents) {\n if (doc._id) {\n this.documentCache.set(doc._id, doc);\n }\n }\n\n logger.debug(`Loaded ${documents.length} documents from chunk ${chunkId}`);\n } catch (error) {\n logger.error(`Failed to load chunk ${chunkId}:`, error);\n throw error;\n }\n }\n\n /**\n * Load an index file and cache it\n */\n private async loadIndex(indexName: string): Promise<any> {\n if (this.indexCache.has(indexName)) {\n return this.indexCache.get(indexName);\n }\n\n const indexMeta = this.manifest.indices.find((idx) => idx.name === indexName);\n if (!indexMeta) {\n throw new Error(`Index ${indexName} not found in manifest`);\n }\n\n try {\n const indexPath = `${this.basePath}/${indexMeta.path}`;\n logger.debug(`Loading index from ${indexPath}`);\n\n let indexData: any;\n\n // Check if we're in a Node.js environment with local files\n if (this.isLocalPath(indexPath) && nodeFS) {\n // Use fs for local file access (e.g., in tests)\n const fileContent = await nodeFS.promises.readFile(indexPath, 'utf8');\n indexData = JSON.parse(fileContent);\n } else {\n // Use fetch for URL-based access (e.g., in browser)\n const response = await fetch(indexPath);\n if (!response.ok) {\n throw new Error(\n `Failed to fetch index ${indexName}: ${response.status} ${response.statusText}`\n );\n }\n indexData = await response.json();\n }\n\n this.indexCache.set(indexName, indexData);\n\n logger.debug(`Loaded index ${indexName}`);\n return indexData;\n } catch (error) {\n logger.error(`Failed to load index ${indexName}:`, error);\n throw error;\n }\n }\n\n /**\n * Get a document by ID without hydration (raw document access)\n * Used internally to avoid recursion during attachment hydration\n */\n private async getRawDocument<T = any>(id: string): Promise<T> {\n // Check document cache first\n if (this.documentCache.has(id)) {\n return this.documentCache.get(id);\n }\n\n // Find which chunk contains this document\n const chunk = await this.findChunkForDocument(id);\n if (!chunk) {\n logger.error(\n `Document ${id} not found in any chunk. Available chunks:`,\n this.manifest.chunks.map((c) => `${c.id} (${c.docType}): ${c.startKey} - ${c.endKey}`)\n );\n throw new Error(`Document ${id} not found in any chunk`);\n }\n\n // Load the chunk if not cached\n await this.loadChunk(chunk.id);\n\n // Try to get the document from cache again\n if (this.documentCache.has(id)) {\n return this.documentCache.get(id);\n }\n\n logger.error(`Document ${id} not found in chunk ${chunk.id}`);\n throw new Error(`Document ${id} not found in chunk ${chunk.id}`);\n }\n\n /**\n * Get attachment URL for a given document and attachment name\n */\n getAttachmentUrl(docId: string, attachmentName: string): string {\n // Construct attachment path: attachments/{docId}/{attachmentName}.{ext}\n // The exact filename will be resolved from the document's _attachments metadata\n return `${this.basePath}/attachments/${docId}/${attachmentName}`;\n }\n\n /**\n * Load attachment data from the document and get the correct file path\n * Uses raw document access to avoid recursion with hydration\n */\n async getAttachmentPath(docId: string, attachmentName: string): Promise<string | null> {\n try {\n const doc = await this.getRawDocument(docId);\n if (doc._attachments && doc._attachments[attachmentName]) {\n const attachment = doc._attachments[attachmentName];\n if (attachment.path) {\n // Return the full path as stored in the document\n return `${this.basePath}/${attachment.path}`;\n }\n }\n return null;\n } catch {\n return null;\n }\n }\n\n /**\n * Load attachment as a blob (for browser) or buffer (for Node.js)\n */\n async getAttachmentBlob(docId: string, attachmentName: string): Promise<Blob | Buffer | null> {\n const attachmentPath = await this.getAttachmentPath(docId, attachmentName);\n if (!attachmentPath) {\n return null;\n }\n\n try {\n // Check if we're in a Node.js environment with local files\n if (this.isLocalPath(attachmentPath) && nodeFS) {\n // Use fs for local file access (e.g., in tests)\n const buffer = await nodeFS.promises.readFile(attachmentPath);\n return buffer;\n } else {\n // Use fetch for URL-based access (e.g., in browser)\n const response = await fetch(attachmentPath);\n if (!response.ok) {\n throw new Error(\n `Failed to fetch attachment ${docId}/${attachmentName}: ${response.status} ${response.statusText}`\n );\n }\n return await response.blob();\n }\n } catch (error) {\n logger.error(`Failed to load attachment ${docId}/${attachmentName}:`, error);\n return null;\n }\n }\n\n /**\n * Hydrate document attachments by converting file paths to blob URLs\n */\n private async hydrateAttachments<T = any>(doc: T): Promise<T> {\n // logger.debug(`[hydrateAttachments] Starting hydration for doc: ${JSON.stringify(doc)}`);\n const typedDoc = doc as any;\n\n // If no attachments, return document as-is\n if (!typedDoc._attachments) {\n return doc;\n }\n\n // Clone the document to avoid mutating the cached version\n const hydratedDoc = JSON.parse(JSON.stringify(doc));\n\n // Process each attachment\n for (const [attachmentName, attachment] of Object.entries(typedDoc._attachments)) {\n // logger.debug(\n // `[hydrateAttachments] Processing attachment: ${attachmentName} for doc ${typedDoc?._id}`\n // );\n const attachmentData = attachment as any;\n\n // If attachment has a path, convert it to a blob URL\n if (attachmentData.path) {\n // logger.debug(\n // `[hydrateAttachments] Attachment ${attachmentName} has path: ${attachmentData.path}. Attempting to get blob.`\n // );\n try {\n const blob = await this.getAttachmentBlob(typedDoc._id, attachmentName);\n if (blob) {\n // logger.debug(\n // `[hydrateAttachments] Successfully retrieved blob for ${typedDoc._id}/${attachmentName}. Size: ${blob instanceof Blob ? blob.size : (blob as Buffer).length}`\n // );\n // Create blob URL for browser rendering\n if (typeof window !== 'undefined' && window.URL) {\n // Store attachment data in PouchDB-compatible format\n hydratedDoc._attachments[attachmentName] = {\n ...attachmentData,\n data: blob,\n stub: false, // Indicates this contains actual data, not just metadata\n };\n // logger.debug(\n // `[hydrateAttachments] Added blobUrl and blob to attachment ${attachmentName} for doc ${typedDoc._id}.`\n // );\n } else {\n // In Node.js environment, just attach the buffer\n hydratedDoc._attachments[attachmentName] = {\n ...attachmentData,\n buffer: blob, // Attach buffer for Node.js use\n };\n // logger.debug(\n // `[hydrateAttachments] Added buffer to attachment ${attachmentName} for doc ${typedDoc._id}.`\n // );\n }\n } else {\n logger.warn(\n `[hydrateAttachments] getAttachmentBlob returned null for ${typedDoc._id}/${attachmentName}. Skipping hydration for this attachment.`\n );\n }\n } catch (error) {\n logger.warn(\n `[hydrateAttachments] Failed to hydrate attachment ${typedDoc._id}/${attachmentName}:`,\n error\n );\n // Keep original attachment data if hydration fails\n }\n } else {\n logger.debug(\n `[hydrateAttachments] Attachment ${attachmentName} for doc ${typedDoc._id} has no path. Skipping blob conversion.`\n );\n }\n }\n\n // logger.debug(\n // `[hydrateAttachments] Finished hydration for doc ${typedDoc?._id}. Returning hydrated doc: ${JSON.stringify(hydratedDoc)}`\n // );\n return hydratedDoc;\n }\n\n /**\n * Clear all caches (useful for testing or memory management)\n */\n clearCaches(): void {\n this.documentCache.clear();\n this.chunkCache.clear();\n this.indexCache.clear();\n }\n\n /**\n * Get cache statistics for debugging\n */\n getCacheStats(): {\n documents: number;\n chunks: number;\n indices: number;\n } {\n return {\n documents: this.documentCache.size,\n chunks: this.chunkCache.size,\n indices: this.indexCache.size,\n };\n }\n\n /**\n * Check if a path is a local file path (vs URL)\n */\n private isLocalPath(filePath: string): boolean {\n // Check if it's an absolute path or doesn't start with http/https\n return (\n !filePath.startsWith('http://') &&\n !filePath.startsWith('https://') &&\n (pathUtils.isAbsolute(filePath) || filePath.startsWith('./') || filePath.startsWith('../'))\n );\n }\n}\n","// packages/db/src/impl/static/courseDB.ts\n\nimport {\n CourseDBInterface,\n UserDBInterface,\n CourseInfo,\n StudySessionNewItem,\n StudySessionReviewItem,\n} from '../../core/interfaces';\nimport { StaticDataUnpacker } from './StaticDataUnpacker';\nimport { StaticCourseManifest } from '../../util/packer/types';\nimport { CourseConfig, CourseElo, DataShape, Status } from '@vue-skuilder/common';\nimport {\n Tag,\n TagStub,\n DocType,\n SkuilderCourseData,\n QualifiedCardID,\n} from '../../core/types/types-legacy';\nimport { DataLayerResult } from '../../core/types/db';\nimport { ContentNavigationStrategyData } from '../../core/types/contentNavigationStrategy';\nimport { ScheduledCard } from '../../core/types/user';\nimport { Navigators } from '../../core/navigators';\nimport { logger } from '../../util/logger';\n\nexport class StaticCourseDB implements CourseDBInterface {\n constructor(\n private courseId: string,\n private unpacker: StaticDataUnpacker,\n private userDB: UserDBInterface,\n private manifest: StaticCourseManifest\n ) {}\n\n getCourseID(): string {\n return this.courseId;\n }\n\n async getCourseConfig(): Promise<CourseConfig> {\n if (this.manifest.courseConfig != null) {\n return this.manifest.courseConfig;\n } else {\n throw new Error(`Course config not found for course ${this.courseId}`);\n }\n }\n\n async updateCourseConfig(_cfg: CourseConfig): Promise<PouchDB.Core.Response> {\n throw new Error('Cannot update course config in static mode');\n }\n\n async getCourseInfo(): Promise<CourseInfo> {\n // Count only cards, not all documents\n // Use chunks metadata to count card documents specifically\n const cardCount = this.manifest.chunks\n .filter((chunk) => chunk.docType === DocType.CARD)\n .reduce((total, chunk) => total + chunk.documentCount, 0);\n\n return {\n cardCount,\n registeredUsers: 0, // Always 0 in static mode\n };\n }\n\n async getCourseDoc<T extends SkuilderCourseData>(\n id: string,\n _options?: PouchDB.Core.GetOptions\n ): Promise<T> {\n return this.unpacker.getDocument(id);\n }\n\n async getCourseDocs<T extends SkuilderCourseData>(\n ids: string[],\n _options?: PouchDB.Core.AllDocsOptions\n ): Promise<PouchDB.Core.AllDocsWithKeysResponse<{} & T>> {\n const rows = await Promise.all(\n ids.map(async (id) => {\n try {\n const doc = await this.unpacker.getDocument(id);\n return {\n id,\n key: id,\n value: { rev: '1-static' },\n doc,\n };\n } catch {\n return {\n key: id,\n error: 'not_found' as const,\n };\n }\n })\n );\n\n return {\n total_rows: ids.length,\n offset: 0,\n rows,\n };\n }\n\n async getCardsByELO(\n elo: number,\n limit?: number\n ): Promise<\n {\n courseID: string;\n cardID: string;\n elo?: number;\n }[]\n > {\n return (await this.unpacker.queryByElo(elo, limit || 25)).map((card) => {\n const [courseID, cardID, elo] = card.split('-');\n return { courseID, cardID, elo: elo ? parseInt(elo) : undefined };\n });\n }\n\n async getCardEloData(cardIds: string[]): Promise<CourseElo[]> {\n const results = await Promise.all(\n cardIds.map(async (id) => {\n try {\n const card = await this.unpacker.getDocument(id);\n return card.elo || { global: { score: 1000, count: 0 }, tags: {}, misc: {} };\n } catch {\n return { global: { score: 1000, count: 0 }, tags: {}, misc: {} };\n }\n })\n );\n return results;\n }\n\n async updateCardElo(cardId: string, _elo: CourseElo): Promise<PouchDB.Core.Response> {\n // No updates to card data in static mode - this is a noop\n return { ok: true, id: cardId, rev: '1-static' };\n }\n\n async getNewCards(limit: number = 99): Promise<StudySessionNewItem[]> {\n const activeCards = await this.userDB.getActiveCards();\n return (\n await this.getCardsCenteredAtELO({ limit: limit, elo: 'user' }, (c: QualifiedCardID) => {\n if (activeCards.some((ac) => c.cardID === ac.cardID)) {\n return false;\n } else {\n return true;\n }\n })\n ).map((c) => {\n return {\n ...c,\n status: 'new',\n };\n });\n }\n\n async getCardsCenteredAtELO(\n options: { limit: number; elo: 'user' | 'random' | number },\n filter?: (id: QualifiedCardID) => boolean\n ): Promise<StudySessionNewItem[]> {\n let targetElo = typeof options.elo === 'number' ? options.elo : 1000;\n\n if (options.elo === 'user') {\n // Get user's ELO for this course\n try {\n const regDoc = await this.userDB.getCourseRegistrationsDoc();\n const courseReg = regDoc.courses.find((c) => c.courseID === this.courseId);\n if (courseReg && typeof courseReg.elo === 'object') {\n targetElo = courseReg.elo.global.score;\n }\n } catch {\n targetElo = 1000;\n }\n } else if (options.elo === 'random') {\n targetElo = 800 + Math.random() * 400; // Random between 800-1200\n }\n\n let cardIds = (await this.unpacker.queryByElo(targetElo, options.limit * 2)).map((c) => {\n return {\n cardID: c,\n courseID: this.courseId,\n };\n });\n\n if (filter) {\n cardIds = cardIds.filter(filter);\n }\n\n return cardIds.slice(0, options.limit).map((card) => ({\n status: 'new' as const,\n // qualifiedID: `${this.courseId}-${cardId}`,\n cardID: card.cardID,\n contentSourceType: 'course' as const,\n contentSourceID: this.courseId,\n courseID: this.courseId,\n }));\n }\n\n async getAppliedTags(cardId: string): Promise<PouchDB.Query.Response<TagStub>> {\n try {\n const tagsIndex = await this.unpacker.getTagsIndex();\n const cardTags = tagsIndex.byCard[cardId] || [];\n\n const rows = await Promise.all(\n cardTags.map(async (tagName) => {\n const tagId = `${DocType.TAG}-${tagName}`;\n\n try {\n // Try to get the full tag document\n const tagDoc = await this.unpacker.getDocument(tagId);\n return {\n id: tagId,\n key: cardId,\n value: {\n name: tagDoc.name,\n snippet: tagDoc.snippet,\n count: tagDoc.taggedCards?.length || 0,\n },\n };\n } catch (error) {\n if (error && (error as PouchDB.Core.Error).status === 404) {\n logger.warn(`Tag document not found for ${tagName}, creating stub`);\n } else {\n logger.error(`Error getting tag document for ${tagName}:`, error);\n throw error;\n }\n // If tag document not found, create a minimal stub\n return {\n id: tagId,\n key: cardId,\n value: {\n name: tagName,\n snippet: `Tag: ${tagName}`,\n count: tagsIndex.byTag[tagName]?.length || 0,\n },\n };\n }\n })\n );\n\n return {\n total_rows: rows.length,\n offset: 0,\n rows,\n };\n } catch (error) {\n logger.error(`Error getting applied tags for card ${cardId}:`, error);\n return {\n total_rows: 0,\n offset: 0,\n rows: [],\n };\n }\n }\n\n async addTagToCard(_cardId: string, _tagId: string): Promise<PouchDB.Core.Response> {\n throw new Error('Cannot modify tags in static mode');\n }\n\n async removeTagFromCard(_cardId: string, _tagId: string): Promise<PouchDB.Core.Response> {\n throw new Error('Cannot modify tags in static mode');\n }\n\n async createTag(_tagName: string): Promise<PouchDB.Core.Response> {\n throw new Error('Cannot create tags in static mode');\n }\n\n async getTag(tagName: string): Promise<Tag> {\n return this.unpacker.getDocument(`${DocType.TAG}-${tagName}`);\n }\n\n async updateTag(_tag: Tag): Promise<PouchDB.Core.Response> {\n throw new Error('Cannot update tags in static mode');\n }\n\n async getCourseTagStubs(): Promise<PouchDB.Core.AllDocsResponse<Tag>> {\n try {\n const tagsIndex = await this.unpacker.getTagsIndex();\n\n if (!tagsIndex || !tagsIndex.byTag) {\n logger.warn('Tags index not found or empty');\n return {\n total_rows: 0,\n offset: 0,\n rows: [],\n };\n }\n\n // Create tag stubs from the index\n const tagNames = Object.keys(tagsIndex.byTag);\n const rows = await Promise.all(\n tagNames.map(async (tagName) => {\n const cardIds = tagsIndex.byTag[tagName] || [];\n const tagId = `${DocType.TAG}-${tagName}`;\n\n try {\n // Try to get the full tag document\n const tagDoc = await this.unpacker.getDocument(tagId);\n return {\n id: tagId,\n key: tagId,\n value: { rev: '1-static' },\n doc: tagDoc,\n };\n } catch (error) {\n // If tag document not found, create a minimal stub\n if (error && (error as PouchDB.Core.Error).status === 404) {\n logger.warn(`Tag document not found for ${tagName}, creating stub`);\n const stubDoc = {\n _id: tagId,\n _rev: '1-static',\n course: this.courseId,\n docType: DocType.TAG,\n name: tagName,\n snippet: `Tag: ${tagName}`,\n wiki: '',\n taggedCards: cardIds,\n author: 'system',\n };\n return {\n id: tagId,\n key: tagId,\n value: { rev: '1-static' },\n doc: stubDoc,\n };\n } else {\n logger.error(`Error getting tag document for ${tagName}:`, error);\n throw error;\n }\n }\n })\n );\n\n return {\n total_rows: rows.length,\n offset: 0,\n rows,\n };\n } catch (error) {\n logger.error('Failed to get course tag stubs:', error);\n return {\n total_rows: 0,\n offset: 0,\n rows: [],\n };\n }\n }\n\n async addNote(\n _codeCourse: string,\n _shape: DataShape,\n _data: unknown,\n _author: string,\n _tags: string[],\n _uploads?: { [key: string]: PouchDB.Core.FullAttachment },\n _elo?: CourseElo\n ): Promise<DataLayerResult> {\n return {\n status: Status.error,\n message: 'Cannot add notes in static mode',\n };\n }\n\n async removeCard(_cardId: string): Promise<PouchDB.Core.Response> {\n throw new Error('Cannot remove cards in static mode');\n }\n\n async getInexperiencedCards(): Promise<any[]> {\n // Would need to be pre-computed in indices\n return [];\n }\n\n // Navigation Strategy Manager implementation\n async getNavigationStrategy(_id: string): Promise<ContentNavigationStrategyData> {\n return {\n _id: 'NAVIGATION_STRATEGY-ELO',\n docType: DocType.NAVIGATION_STRATEGY,\n name: 'ELO',\n description: 'ELO-based navigation strategy',\n implementingClass: Navigators.ELO,\n course: this.courseId,\n serializedData: '',\n };\n }\n\n async getAllNavigationStrategies(): Promise<ContentNavigationStrategyData[]> {\n return [await this.getNavigationStrategy('ELO')];\n }\n\n async addNavigationStrategy(_data: ContentNavigationStrategyData): Promise<void> {\n throw new Error('Cannot add navigation strategies in static mode');\n }\n\n async updateNavigationStrategy(_id: string, _data: ContentNavigationStrategyData): Promise<void> {\n throw new Error('Cannot update navigation strategies in static mode');\n }\n\n // Study Content Source implementation\n async getPendingReviews(): Promise<(StudySessionReviewItem & ScheduledCard)[]> {\n // In static mode, reviews would be stored locally\n return [];\n }\n\n // Attachment helper methods (internal use, not part of interface)\n\n /**\n * Get attachment URL for a document and attachment name\n * Internal helper method for static attachment serving\n */\n getAttachmentUrl(docId: string, attachmentName: string): string {\n return this.unpacker.getAttachmentUrl(docId, attachmentName);\n }\n\n /**\n * Load attachment as blob/buffer\n * Internal helper method for static attachment serving\n */\n async getAttachmentBlob(docId: string, attachmentName: string): Promise<Blob | Buffer | null> {\n return this.unpacker.getAttachmentBlob(docId, attachmentName);\n }\n\n // Admin search methods\n async searchCards(_query: string): Promise<any[]> {\n // In static mode, return empty results for now\n // Could be implemented with local search if needed\n return [];\n }\n\n async find(_request: PouchDB.Find.FindRequest<any>): Promise<PouchDB.Find.FindResponse<any>> {\n // In static mode, return empty results for now\n // Could be implemented with local search if needed\n return {\n docs: [],\n warning: 'Find operations not supported in static mode',\n } as any;\n }\n}\n","// packages/db/src/impl/static/coursesDB.ts\n\nimport { CoursesDBInterface } from '../../core/interfaces';\nimport { CourseConfig } from '@vue-skuilder/common';\nimport { StaticCourseManifest } from '../../util/packer/types';\nimport { logger } from '../../util/logger';\n\nexport class StaticCoursesDB implements CoursesDBInterface {\n constructor(\n private manifests: Record<string, StaticCourseManifest>,\n private dependencyNameToCourseId?: Map<string, string>\n ) {}\n\n async getCourseConfig(courseId: string): Promise<CourseConfig> {\n // Try direct lookup by courseId first\n let manifest = this.manifests[courseId];\n\n // If not found, try lookup by dependency name (backwards compatibility)\n if (!manifest && this.dependencyNameToCourseId) {\n const mappedCourseId = this.dependencyNameToCourseId.get(courseId);\n if (mappedCourseId) {\n manifest = this.manifests[mappedCourseId];\n }\n }\n\n if (!manifest) {\n logger.warn(`Course manifest for ${courseId} not found`);\n throw new Error(`Course ${courseId} not found`);\n }\n\n if (manifest.courseConfig) {\n return manifest.courseConfig;\n } else {\n logger.warn(`Course config not found in manifest for course ${courseId}`);\n throw new Error(`Course config not found for course ${courseId}`);\n }\n }\n\n async getCourseList(): Promise<CourseConfig[]> {\n // Return configs for all available courses\n return Object.keys(this.manifests).map(\n (courseId) =>\n ({\n courseID: courseId,\n name: this.manifests[courseId].courseName,\n // ... other config fields\n }) as CourseConfig\n );\n }\n\n async disambiguateCourse(_courseId: string, _disambiguator: string): Promise<void> {\n logger.warn('Cannot disambiguate courses in static mode');\n }\n}\n","// packages/db/src/impl/static/NoOpSyncStrategy.ts\nimport type { SyncStrategy } from '../common/SyncStrategy';\nimport type { AccountCreationResult, AuthenticationResult } from '../common/types';\nimport { getLocalUserDB, accomodateGuest } from '../common';\n\n/**\n * Sync strategy for static deployments that provides no-op implementations\n * for remote operations. Uses only local storage with no remote synchronization.\n */\nexport class NoOpSyncStrategy implements SyncStrategy {\n private currentUsername: string = accomodateGuest().username;\n\n setupRemoteDB(username: string): PouchDB.Database {\n // Return the same database instance as local - sync with self is a no-op\n return getLocalUserDB(username);\n }\n\n getWriteDB(username: string): PouchDB.Database {\n // In static mode, always write to local database\n return getLocalUserDB(username);\n }\n\n startSync(_localDB: PouchDB.Database, _remoteDB: PouchDB.Database): void {\n // No-op - in static mode, local and remote are the same database instance\n // PouchDB sync with itself is harmless and efficient\n }\n\n stopSync?(): void {\n // No-op - no sync to stop in static mode\n }\n\n canCreateAccount(): boolean {\n return false; // Account creation not supported in static mode\n }\n\n canAuthenticate(): boolean {\n return false; // Remote authentication not supported in static mode\n }\n\n async createAccount(_username: string, _password: string): Promise<AccountCreationResult> {\n throw new Error(\n 'Account creation not supported in static mode. Use local account switching instead.'\n );\n }\n\n async authenticate(_username: string, _password: string): Promise<AuthenticationResult> {\n throw new Error(\n 'Remote authentication not supported in static mode. Use local account switching instead.'\n );\n }\n\n async logout(): Promise<AuthenticationResult> {\n // In static mode, \"logout\" means switch back to guest user\n this.currentUsername = accomodateGuest().username;\n return {\n ok: true,\n };\n }\n\n async getCurrentUsername(): Promise<string> {\n // In static mode, always return guest username\n // TODO: This will be enhanced with local account switching to support multiple local users\n return this.currentUsername;\n }\n\n /**\n * Set the current username (for local account switching)\n * This method is specific to NoOpSyncStrategy and not part of the base interface\n */\n setCurrentUsername(username: string): void {\n this.currentUsername = username;\n }\n}\n","\n// packages/db/src/impl/static/StaticDataLayerProvider.ts\n\nimport {\n AdminDBInterface,\n ClassroomDBInterface,\n CoursesDBInterface,\n CourseDBInterface,\n DataLayerProvider,\n UserDBInterface,\n UserDBReader,\n} from '../../core/interfaces';\nimport { logger } from '../../util/logger';\nimport { StaticCourseManifest } from '../../util/packer/types';\nimport { StaticDataUnpacker } from './StaticDataUnpacker';\nimport { StaticCourseDB } from './courseDB';\nimport { StaticCoursesDB } from './coursesDB';\nimport { BaseUser } from '../common';\nimport { NoOpSyncStrategy } from './NoOpSyncStrategy';\n\n\ninterface SkuilderManifest {\n name?: string;\n version?: string;\n description?: string;\n dependencies?: Record<string, string>;\n}\n\ninterface StaticDataLayerConfig {\n localStoragePrefix?: string;\n rootManifest: SkuilderManifest; // The parsed root skuilder.json object\n rootManifestUrl: string; // The absolute URL where the root manifest was found\n}\n\nexport class StaticDataLayerProvider implements DataLayerProvider {\n private config: StaticDataLayerConfig;\n private initialized: boolean = false;\n private courseUnpackers: Map<string, StaticDataUnpacker> = new Map();\n private manifests: Record<string, StaticCourseManifest> = {};\n // Mapping from dependency name to actual courseId for backwards compatibility\n private dependencyNameToCourseId: Map<string, string> = new Map();\n\n constructor(config: Partial<StaticDataLayerConfig>) {\n this.config = {\n localStoragePrefix: config.localStoragePrefix || 'skuilder-static',\n rootManifest: config.rootManifest || { dependencies: {} },\n rootManifestUrl: config.rootManifestUrl || '/',\n };\n }\n\n private async resolveCourseDependencies(): Promise<void> {\n logger.info('[StaticDataLayerProvider] Starting course dependency resolution...');\n const rootManifest = this.config.rootManifest;\n\n for (const [courseName, courseUrl] of Object.entries(rootManifest.dependencies || {})) {\n try {\n logger.debug(`[StaticDataLayerProvider] Resolving dependency: ${courseName} from ${courseUrl}`);\n \n const courseManifestUrl = new URL(courseUrl as string, this.config.rootManifestUrl).href;\n const courseJsonResponse = await fetch(courseManifestUrl);\n if (!courseJsonResponse.ok) {\n throw new Error(`Failed to fetch course manifest for ${courseName}`);\n }\n const courseJson = await courseJsonResponse.json();\n\n if (courseJson.content && courseJson.content.manifest) {\n const baseUrl = new URL('.', courseManifestUrl).href;\n const finalManifestUrl = new URL(courseJson.content.manifest, courseManifestUrl).href;\n\n const finalManifestResponse = await fetch(finalManifestUrl);\n if (!finalManifestResponse.ok) {\n throw new Error(`Failed to fetch final content manifest for ${courseName} at ${finalManifestUrl}`);\n }\n const finalManifest = await finalManifestResponse.json();\n\n // Extract courseId from the manifest to use as the lookup key\n const courseId = finalManifest.courseId || finalManifest.courseConfig?.courseID;\n if (!courseId) {\n throw new Error(`Course manifest for ${courseName} missing courseId`);\n }\n\n this.manifests[courseId] = finalManifest;\n const unpacker = new StaticDataUnpacker(finalManifest, baseUrl);\n this.courseUnpackers.set(courseId, unpacker);\n\n // Also store mapping from dependency name to courseId for backwards compatibility\n // This allows lookup by either dependency name or courseId\n this.dependencyNameToCourseId.set(courseName, courseId);\n\n logger.info(`[StaticDataLayerProvider] Successfully resolved and prepared course: ${courseName} (courseId: ${courseId})`);\n }\n } catch (e) {\n logger.error(`[StaticDataLayerProvider] Failed to resolve dependency ${courseName}:`, e);\n // Continue to next dependency\n }\n }\n logger.info('[StaticDataLayerProvider] Course dependency resolution complete.');\n }\n\n async initialize(): Promise<void> {\n if (this.initialized) return;\n\n logger.info('Initializing static data layer provider');\n await this.resolveCourseDependencies();\n this.initialized = true;\n }\n\n async teardown(): Promise<void> {\n this.courseUnpackers.clear();\n this.initialized = false;\n }\n\n getUserDB(): UserDBInterface {\n const syncStrategy = new NoOpSyncStrategy();\n // For now, use guest user - local account switching will be added later\n return BaseUser.Dummy(syncStrategy);\n }\n\n getCourseDB(courseId: string): CourseDBInterface {\n // Try direct lookup by courseId first\n let unpacker = this.courseUnpackers.get(courseId);\n let actualCourseId = courseId;\n\n // If not found, try lookup by dependency name (backwards compatibility)\n if (!unpacker) {\n const mappedCourseId = this.dependencyNameToCourseId.get(courseId);\n if (mappedCourseId) {\n unpacker = this.courseUnpackers.get(mappedCourseId);\n actualCourseId = mappedCourseId;\n }\n }\n\n if (!unpacker) {\n throw new Error(`Course ${courseId} not found or failed to initialize in static data layer.`);\n }\n const manifest = this.manifests[actualCourseId];\n return new StaticCourseDB(actualCourseId, unpacker, this.getUserDB(), manifest);\n }\n\n getCoursesDB(): CoursesDBInterface {\n return new StaticCoursesDB(this.manifests, this.dependencyNameToCourseId);\n }\n\n async getClassroomDB(\n _classId: string,\n _type: 'student' | 'teacher'\n ): Promise<ClassroomDBInterface> {\n throw new Error('Classrooms not supported in static mode');\n }\n\n getAdminDB(): AdminDBInterface {\n throw new Error('Admin functions not supported in static mode');\n }\n\n async createUserReaderForUser(targetUsername: string): Promise<UserDBReader> {\n logger.warn(`StaticDataLayerProvider: Multi-user access not supported in static mode`);\n logger.warn(`Request: trying to access data for ${targetUsername}`);\n logger.warn(`Returning current user's data instead`);\n \n // In static mode, just return the current user's DB as a reader\n // This is safe since static mode is typically for development/testing\n return this.getUserDB() as UserDBReader;\n }\n\n isReadOnly(): boolean {\n return true;\n }\n}\n","// db/src/factory.ts\n\nimport { DataLayerProvider } from './core/interfaces';\nimport { BaseUser } from './impl/common';\nimport { logger } from './util/logger';\nimport { StaticCourseManifest } from './util/packer/types';\n\nconst NOT_SET = 'NOT_SET' as const;\n\ninterface DBEnv {\n COUCHDB_SERVER_URL: string; // URL of CouchDB server\n COUCHDB_SERVER_PROTOCOL: string; // Protocol of CouchDB server (http or https)\n COUCHDB_USERNAME?: string;\n COUCHDB_PASSWORD?: string;\n LOCAL_STORAGE_PREFIX: string; // Prefix for IndexedDB storage names\n}\n\nexport const ENV: DBEnv = {\n COUCHDB_SERVER_PROTOCOL: NOT_SET,\n COUCHDB_SERVER_URL: NOT_SET,\n LOCAL_STORAGE_PREFIX: '',\n};\n\nexport { NOT_SET };\n\n// Configuration type for data layer initialization\nexport interface DataLayerConfig {\n type: 'couch' | 'static';\n options: {\n staticContentPath?: string; // Path to static content JSON files\n localStoragePrefix?: string; // Prefix for IndexedDB storage names\n manifests?: Record<string, StaticCourseManifest>; // Course manifests for static mode\n COUCHDB_SERVER_URL?: string;\n COUCHDB_SERVER_PROTOCOL?: string;\n COUCHDB_USERNAME?: string;\n COUCHDB_PASSWORD?: string;\n\n COURSE_IDS?: string[];\n };\n}\n\n// Singleton instance\nlet dataLayerInstance: DataLayerProvider | null = null;\n\n/**\n * Initialize the data layer with the specified configuration\n */\nexport async function initializeDataLayer(config: DataLayerConfig): Promise<DataLayerProvider> {\n if (dataLayerInstance) {\n logger.warn('Data layer already initialized. Returning existing instance.');\n return dataLayerInstance;\n }\n if (config.options.localStoragePrefix) {\n ENV.LOCAL_STORAGE_PREFIX = config.options.localStoragePrefix;\n }\n\n if (config.type === 'couch') {\n if (!config.options.COUCHDB_SERVER_URL || !config.options.COUCHDB_SERVER_PROTOCOL) {\n throw new Error('Missing CouchDB server URL or protocol');\n }\n ENV.COUCHDB_SERVER_PROTOCOL = config.options.COUCHDB_SERVER_PROTOCOL;\n ENV.COUCHDB_SERVER_URL = config.options.COUCHDB_SERVER_URL;\n ENV.COUCHDB_USERNAME = config.options.COUCHDB_USERNAME;\n ENV.COUCHDB_PASSWORD = config.options.COUCHDB_PASSWORD;\n\n if (\n config.options.COUCHDB_PASSWORD &&\n config.options.COUCHDB_USERNAME &&\n typeof window !== 'undefined'\n ) {\n // Dynamic import to avoid loading both implementations when only one is needed\n const { CouchDBSyncStrategy } = await import('./impl/couch/CouchDBSyncStrategy');\n\n // Create a sync strategy instance and authenticate\n const syncStrategy = new CouchDBSyncStrategy();\n\n const user = await BaseUser.instance(syncStrategy, config.options.COUCHDB_USERNAME);\n const authResult = await user.login(\n config.options.COUCHDB_USERNAME,\n config.options.COUCHDB_PASSWORD\n );\n\n if (authResult.ok) {\n logger.info(`Successfully authenticated as ${config.options.COUCHDB_USERNAME}`);\n } else {\n logger.warn(`Authentication failed: ${authResult.error}`);\n }\n }\n\n // Dynamic import to avoid loading both implementations when only one is needed\n const { CouchDataLayerProvider } = await import('./impl/couch/PouchDataLayerProvider');\n dataLayerInstance = new CouchDataLayerProvider(config.options.COURSE_IDS);\n } else if (config.type === 'static') {\n const { StaticDataLayerProvider } = await import('./impl/static/StaticDataLayerProvider');\n dataLayerInstance = new StaticDataLayerProvider(config.options);\n } else {\n throw new Error(`Unknown data layer type: ${config.type}`);\n }\n\n await dataLayerInstance.initialize();\n return dataLayerInstance;\n}\n\n/**\n * Get the initialized data layer instance\n * @throws Error if not initialized\n */\nexport function getDataLayer(): DataLayerProvider {\n if (!dataLayerInstance) {\n throw new Error('Data layer not initialized. Call initializeDataLayer first.');\n }\n return dataLayerInstance;\n}\n\n/**\n * Reset the data layer (primarily for testing)\n */\nexport async function _resetDataLayer(): Promise<void> {\n if (dataLayerInstance) {\n await dataLayerInstance.teardown();\n }\n dataLayerInstance = null;\n}\n","import {\n StudyContentSource,\n StudySessionNewItem,\n StudySessionReviewItem,\n} from '@db/core/interfaces/contentSource';\nimport { WeightedCard } from '@db/core/navigators';\nimport { UserDBInterface } from '@db/core';\nimport { ScheduledCard } from '@db/core/types/user';\nimport { TagFilter, hasActiveFilter } from '@vue-skuilder/common';\nimport { getTag } from '../impl/couch/courseDB';\nimport { logger } from '@db/util/logger';\n\n/**\n * A StudyContentSource that filters cards based on tag inclusion/exclusion.\n *\n * This enables ephemeral, tag-scoped study sessions where users can focus\n * on specific topics within a course without permanent configuration.\n *\n * Filter logic:\n * - If `include` is non-empty: card must have at least one of the included tags\n * - If `exclude` is non-empty: card must not have any of the excluded tags\n * - Both filters are applied (include first, then exclude)\n */\nexport class TagFilteredContentSource implements StudyContentSource {\n private courseId: string;\n private filter: TagFilter;\n private user: UserDBInterface;\n\n // Cache resolved card IDs to avoid repeated lookups within a session\n private resolvedCardIds: Set<string> | null = null;\n\n constructor(courseId: string, filter: TagFilter, user: UserDBInterface) {\n this.courseId = courseId;\n this.filter = filter;\n this.user = user;\n\n logger.info(\n `[TagFilteredContentSource] Created for course \"${courseId}\" with filter:`,\n JSON.stringify(filter)\n );\n }\n\n /**\n * Resolves the TagFilter to a set of eligible card IDs.\n *\n * - Cards in `include` tags are OR'd together (card needs at least one)\n * - Cards in `exclude` tags are removed from the result\n */\n private async resolveFilteredCardIds(): Promise<Set<string>> {\n // Return cached result if available\n if (this.resolvedCardIds !== null) {\n return this.resolvedCardIds;\n }\n\n const includedCardIds = new Set<string>();\n\n // Build inclusion set (OR of all include tags)\n if (this.filter.include.length > 0) {\n for (const tagName of this.filter.include) {\n try {\n const tagDoc = await getTag(this.courseId, tagName);\n tagDoc.taggedCards.forEach((cardId) => includedCardIds.add(cardId));\n } catch (error) {\n logger.warn(\n `[TagFilteredContentSource] Could not resolve tag \"${tagName}\" for inclusion:`,\n error\n );\n }\n }\n }\n\n // If no include tags specified or none resolved, return empty set\n // (requiring explicit inclusion prevents \"study everything\" on empty filter)\n if (includedCardIds.size === 0 && this.filter.include.length > 0) {\n logger.warn(\n `[TagFilteredContentSource] No cards found for include tags: ${this.filter.include.join(', ')}`\n );\n this.resolvedCardIds = new Set();\n return this.resolvedCardIds;\n }\n\n // Build exclusion set\n const excludedCardIds = new Set<string>();\n if (this.filter.exclude.length > 0) {\n for (const tagName of this.filter.exclude) {\n try {\n const tagDoc = await getTag(this.courseId, tagName);\n tagDoc.taggedCards.forEach((cardId) => excludedCardIds.add(cardId));\n } catch (error) {\n logger.warn(\n `[TagFilteredContentSource] Could not resolve tag \"${tagName}\" for exclusion:`,\n error\n );\n }\n }\n }\n\n // Apply exclusion filter\n const finalCardIds = new Set<string>();\n for (const cardId of includedCardIds) {\n if (!excludedCardIds.has(cardId)) {\n finalCardIds.add(cardId);\n }\n }\n\n logger.info(\n `[TagFilteredContentSource] Resolved ${finalCardIds.size} cards ` +\n `(included: ${includedCardIds.size}, excluded: ${excludedCardIds.size})`\n );\n\n this.resolvedCardIds = finalCardIds;\n return finalCardIds;\n }\n\n /**\n * Gets new cards that match the tag filter and are not already active for the user.\n */\n public async getNewCards(limit?: number): Promise<StudySessionNewItem[]> {\n if (!hasActiveFilter(this.filter)) {\n logger.warn('[TagFilteredContentSource] getNewCards called with no active filter');\n return [];\n }\n\n const eligibleCardIds = await this.resolveFilteredCardIds();\n const activeCards = await this.user.getActiveCards();\n const activeCardIds = new Set(activeCards.map((c) => c.cardID));\n\n const newItems: StudySessionNewItem[] = [];\n for (const cardId of eligibleCardIds) {\n if (!activeCardIds.has(cardId)) {\n newItems.push({\n courseID: this.courseId,\n cardID: cardId,\n contentSourceType: 'course',\n contentSourceID: this.courseId,\n status: 'new',\n });\n }\n\n if (limit !== undefined && newItems.length >= limit) {\n break;\n }\n }\n\n logger.info(`[TagFilteredContentSource] Found ${newItems.length} new cards matching filter`);\n return newItems;\n }\n\n /**\n * Gets pending reviews, filtered to only include cards that match the tag filter.\n */\n public async getPendingReviews(): Promise<(StudySessionReviewItem & ScheduledCard)[]> {\n if (!hasActiveFilter(this.filter)) {\n logger.warn('[TagFilteredContentSource] getPendingReviews called with no active filter');\n return [];\n }\n\n const eligibleCardIds = await this.resolveFilteredCardIds();\n const allReviews = await this.user.getPendingReviews(this.courseId);\n\n const filteredReviews = allReviews.filter((review) => {\n return eligibleCardIds.has(review.cardId);\n });\n\n logger.info(\n `[TagFilteredContentSource] Found ${filteredReviews.length} pending reviews matching filter ` +\n `(of ${allReviews.length} total)`\n );\n\n return filteredReviews.map((r) => ({\n ...r,\n courseID: r.courseId,\n cardID: r.cardId,\n contentSourceType: 'course' as const,\n contentSourceID: this.courseId,\n reviewID: r._id,\n status: 'review' as const,\n }));\n }\n\n /**\n * Get cards with suitability scores for presentation.\n *\n * This implementation wraps the legacy getNewCards/getPendingReviews methods,\n * assigning score=1.0 to all cards. TagFilteredContentSource does not currently\n * support pluggable navigation strategies - it returns flat-scored candidates.\n *\n * @param limit - Maximum number of cards to return\n * @returns Cards sorted by score descending (all scores = 1.0)\n */\n public async getWeightedCards(limit: number): Promise<WeightedCard[]> {\n const [newCards, reviews] = await Promise.all([\n this.getNewCards(limit),\n this.getPendingReviews(),\n ]);\n\n const weighted: WeightedCard[] = [\n ...reviews.map((r) => ({\n cardId: r.cardID,\n courseId: r.courseID,\n score: 1.0,\n provenance: [\n {\n strategy: 'tagFilter',\n strategyName: 'Tag Filter',\n strategyId: 'TAG_FILTER',\n action: 'generated' as const,\n score: 1.0,\n reason: `Tag-filtered review (tags: ${this.filter.include.join(', ')})`,\n },\n ],\n })),\n ...newCards.map((c) => ({\n cardId: c.cardID,\n courseId: c.courseID,\n score: 1.0,\n provenance: [\n {\n strategy: 'tagFilter',\n strategyName: 'Tag Filter',\n strategyId: 'TAG_FILTER',\n action: 'generated' as const,\n score: 1.0,\n reason: `Tag-filtered new card (tags: ${this.filter.include.join(', ')})`,\n },\n ],\n })),\n ];\n\n // Reviews first, then new cards; respect limit\n return weighted.slice(0, limit);\n }\n\n /**\n * Clears the cached resolved card IDs.\n * Call this if the underlying tag data may have changed during a session.\n */\n public clearCache(): void {\n this.resolvedCardIds = null;\n }\n\n /**\n * Returns the course ID this source is filtering.\n */\n public getCourseId(): string {\n return this.courseId;\n }\n\n /**\n * Returns the active tag filter.\n */\n public getFilter(): TagFilter {\n return this.filter;\n }\n}\n","import { getDataLayer } from '@db/factory';\nimport { UserDBInterface } from '..';\nimport { StudentClassroomDB } from '../../impl/couch/classroomDB';\nimport { ScheduledCard } from '@db/core/types/user';\nimport { WeightedCard } from '../navigators';\nimport { TagFilter, hasActiveFilter } from '@vue-skuilder/common';\nimport { TagFilteredContentSource } from '../../study/TagFilteredContentSource';\n\n// ============================================================================\n// API MIGRATION NOTICE\n// ============================================================================\n//\n// The StudyContentSource interface is being superseded by the ContentNavigator\n// class and its getWeightedCards() API. See:\n// packages/db/src/core/navigators/ARCHITECTURE.md\n//\n// HISTORICAL CONTEXT:\n// - This interface was designed to abstract 'classrooms' and 'courses' as\n// content sources for study sessions.\n// - getNewCards() and getPendingReviews() were artifacts of two hard-coded\n// navigation strategies: ELO proximity (new) and SRS scheduling (reviews).\n// - The new/review split reflected implementation details, not fundamentals.\n//\n// THE PROBLEM:\n// - \"What does 'get reviews' mean for an interference mitigator?\" - it doesn't.\n// - SRS is just one strategy that could express review urgency as scores.\n// - Some strategies generate candidates, others filter/score them.\n//\n// THE SOLUTION:\n// - ContentNavigator.getWeightedCards() returns unified scored candidates.\n// - WeightedCard.source field distinguishes new/review/failed (metadata, not API).\n// - Strategies compose via delegate pattern (filter wraps generator).\n//\n// MIGRATION PATH:\n// 1. ContentNavigator implements StudyContentSource for backward compat\n// 2. SessionController will migrate to call getWeightedCards()\n// 3. Legacy methods will be deprecated, then removed\n//\n// ============================================================================\n\nexport type StudySessionFailedItem = StudySessionFailedNewItem | StudySessionFailedReviewItem;\n\nexport interface StudySessionFailedNewItem extends StudySessionItem {\n status: 'failed-new';\n}\nexport interface StudySessionFailedReviewItem extends StudySessionReviewItem {\n status: 'failed-review';\n}\n\nexport interface StudySessionNewItem extends StudySessionItem {\n status: 'new';\n}\n\nexport interface StudySessionReviewItem extends StudySessionItem {\n reviewID: string;\n status: 'review' | 'failed-review';\n}\nexport function isReview(item: StudySessionItem): item is StudySessionReviewItem {\n const ret = item.status === 'review' || item.status === 'failed-review' || 'reviewID' in item;\n\n // console.log(`itemIsReview: ${ret}\n // \\t${JSON.stringify(item)}`);\n\n return ret;\n}\n\nexport interface StudySessionItem {\n status: 'new' | 'review' | 'failed-new' | 'failed-review';\n contentSourceType: 'course' | 'classroom';\n contentSourceID: string;\n // qualifiedID: `${string}-${string}` | `${string}-${string}-${number}`;\n cardID: string;\n courseID: string;\n elo?: number;\n // reviewID?: string;\n}\n\nexport interface ContentSourceID {\n type: 'course' | 'classroom';\n id: string;\n /**\n * Optional tag filter for scoped study sessions.\n * When present, creates a TagFilteredContentSource instead of a regular course source.\n */\n tagFilter?: TagFilter;\n}\n\n// #region docs_StudyContentSource\n/**\n * Interface for sources that provide study content to SessionController.\n *\n * @deprecated This interface will be superseded by ContentNavigator.getWeightedCards().\n * The getNewCards/getPendingReviews split was an artifact of hard-coded ELO and SRS\n * strategies. The new API returns unified WeightedCard[] with scores.\n *\n * MIGRATION:\n * - Implement ContentNavigator instead of StudyContentSource directly\n * - Override getWeightedCards() as the primary method\n * - Legacy methods can delegate to getWeightedCards() or be left as-is\n *\n * See: packages/db/src/core/navigators/ARCHITECTURE.md\n */\nexport interface StudyContentSource {\n /**\n * Get cards scheduled for review based on SRS algorithm.\n *\n * @deprecated Will be replaced by getWeightedCards() which returns scored candidates.\n * Review urgency will be expressed as a score rather than a separate method.\n */\n getPendingReviews(): Promise<(StudySessionReviewItem & ScheduledCard)[]>;\n\n /**\n * Get new cards for introduction, typically ordered by ELO proximity.\n *\n * @deprecated Will be replaced by getWeightedCards() which returns scored candidates.\n * New card selection and scoring will be unified with review scoring.\n *\n * @param n - Maximum number of new cards to return\n */\n getNewCards(n?: number): Promise<StudySessionNewItem[]>;\n\n /**\n * Get cards with suitability scores for presentation.\n *\n * This is the PRIMARY API for content sources going forward. Returns unified\n * scored candidates that can be sorted and selected by SessionController.\n *\n * The `source` field on WeightedCard indicates origin ('new' | 'review' | 'failed')\n * for queue routing purposes during the migration period.\n *\n * @param limit - Maximum number of cards to return\n * @returns Cards sorted by score descending\n */\n getWeightedCards?(limit: number): Promise<WeightedCard[]>;\n}\n// #endregion docs_StudyContentSource\n\nexport async function getStudySource(\n source: ContentSourceID,\n user: UserDBInterface\n): Promise<StudyContentSource> {\n if (source.type === 'classroom') {\n return await StudentClassroomDB.factory(source.id, user);\n } else {\n // Check if this is a tag-filtered course source\n if (hasActiveFilter(source.tagFilter)) {\n return new TagFilteredContentSource(source.id, source.tagFilter!, user);\n }\n\n // Regular course source\n return getDataLayer().getCourseDB(source.id) as unknown as StudyContentSource;\n }\n}\n","import { CourseConfig, CourseElo, DataShape, SkuilderCourseData } from '@vue-skuilder/common';\nimport { StudySessionNewItem, StudySessionItem } from './contentSource';\nimport { TagStub, Tag, QualifiedCardID } from '../types/types-legacy';\nimport { DataLayerResult } from '../types/db';\nimport { NavigationStrategyManager } from './navigationStrategyManager';\n\n/**\n * Course content and management\n */\nexport interface CoursesDBInterface {\n /**\n * Get course config\n */\n getCourseConfig(courseId: string): Promise<CourseConfig>;\n\n /**\n * Get a list of all courses\n */\n getCourseList(): Promise<CourseConfig[]>;\n\n disambiguateCourse(courseId: string, disambiguator: string): Promise<void>;\n}\n\nexport interface CourseInfo {\n cardCount: number;\n registeredUsers: number;\n}\n\nexport interface CourseDBInterface extends NavigationStrategyManager {\n /**\n * Get course config\n */\n getCourseConfig(): Promise<CourseConfig>;\n\n getCourseID(): string;\n\n /**\n * Set course config\n */\n updateCourseConfig(cfg: CourseConfig): Promise<PouchDB.Core.Response>;\n\n getCourseInfo(): Promise<CourseInfo>;\n\n getCourseDoc<T extends SkuilderCourseData>(\n id: string,\n options?: PouchDB.Core.GetOptions\n ): Promise<T>;\n getCourseDocs<T extends SkuilderCourseData>(\n ids: string[],\n options?: PouchDB.Core.AllDocsOptions\n ): Promise<PouchDB.Core.AllDocsWithKeysResponse<{} & T>>;\n\n /**\n * Get cards sorted by ELO rating\n */\n getCardsByELO(\n elo: number,\n limit?: number\n ): Promise<\n {\n courseID: string;\n cardID: string;\n elo?: number;\n }[]\n >;\n\n /**\n * Get ELO data for specific cards\n */\n getCardEloData(cardIds: string[]): Promise<CourseElo[]>;\n\n /**\n * Update card ELO rating\n */\n updateCardElo(cardId: string, elo: CourseElo): Promise<PouchDB.Core.Response>;\n\n /**\n * Get new cards for study\n */\n getNewCards(limit?: number): Promise<StudySessionNewItem[]>;\n\n /**\n * Get cards centered at a particular ELO rating\n */\n getCardsCenteredAtELO(\n options: { limit: number; elo: 'user' | 'random' | number },\n filter?: (card: QualifiedCardID) => boolean\n ): Promise<StudySessionItem[]>;\n\n /**\n * Get tags for a card\n */\n getAppliedTags(cardId: string): Promise<PouchDB.Query.Response<TagStub>>;\n\n /**\n * Add a tag to a card\n */\n addTagToCard(cardId: string, tagId: string, updateELO?: boolean): Promise<PouchDB.Core.Response>;\n\n /**\n * Remove a tag from a card\n */\n removeTagFromCard(cardId: string, tagId: string): Promise<PouchDB.Core.Response>;\n\n /**\n * Create a new tag\n */\n createTag(tagName: string, author: string): Promise<PouchDB.Core.Response>;\n\n /**\n * Get a tag by name\n */\n getTag(tagName: string): Promise<Tag>;\n\n /**\n * Update a tag\n */\n updateTag(tag: Tag): Promise<PouchDB.Core.Response>;\n\n /**\n * Get all tag stubs for a course\n */\n getCourseTagStubs(): Promise<PouchDB.Core.AllDocsResponse<Tag>>;\n\n /**\n * Add a note to the course\n */\n addNote(\n codeCourse: string,\n shape: DataShape,\n data: unknown,\n author: string,\n tags: string[],\n uploads?: { [key: string]: PouchDB.Core.FullAttachment },\n elo?: CourseElo\n ): Promise<DataLayerResult>;\n\n removeCard(cardId: string): Promise<PouchDB.Core.Response>;\n\n getInexperiencedCards(): Promise<\n {\n courseId: string;\n cardId: string;\n count: number;\n elo: CourseElo;\n }[]\n >;\n\n /**\n * Search for cards by text content\n * @param query Text to search for\n * @returns Array of matching card data\n */\n searchCards(query: string): Promise<any[]>;\n\n /**\n * Find documents using PouchDB query syntax\n * @param request PouchDB find request\n * @returns Query response\n */\n find(request: PouchDB.Find.FindRequest<any>): Promise<PouchDB.Find.FindResponse<any>>;\n}\n","// db/src/core/interfaces.ts\n\nimport { UserDBInterface, UserDBReader } from './userDB';\nimport { CourseDBInterface, CoursesDBInterface } from './courseDB';\nimport { ClassroomDBInterface } from './classroomDB';\nimport { AdminDBInterface } from './adminDB';\n\n/**\n * Main factory interface for data access\n */\nexport interface DataLayerProvider {\n /**\n * Get the user database interface\n */\n getUserDB(): UserDBInterface;\n\n /**\n * Create a UserDBReader for a specific user (admin access required)\n * Uses session authentication to verify requesting user is admin\n * @param targetUsername - The username to create a reader for\n * @throws Error if requesting user is not 'admin'\n */\n createUserReaderForUser(targetUsername: string): Promise<UserDBReader>;\n\n /**\n * Get a course database interface\n */\n getCourseDB(courseId: string): CourseDBInterface;\n\n /**\n * Get the courses-lookup interface\n */\n getCoursesDB(): CoursesDBInterface;\n\n /**\n * Get a classroom database interface\n */\n getClassroomDB(classId: string, type: 'student' | 'teacher'): Promise<ClassroomDBInterface>;\n\n /**\n * Get the admin database interface\n */\n getAdminDB(): AdminDBInterface;\n\n /**\n * Initialize the data layer\n */\n initialize(): Promise<void>;\n\n /**\n * Teardown the data layer\n */\n teardown(): Promise<void>;\n\n /**\n * Check if this data layer is read-only\n */\n isReadOnly(): boolean;\n}\n","import {\n ActivityRecord,\n CourseRegistration,\n CourseRegistrationDoc,\n ScheduledCard,\n} from '@db/core/types/user';\nimport { CourseElo, Status } from '@vue-skuilder/common';\nimport { Moment } from 'moment';\nimport { CardHistory, CardRecord, QualifiedCardID } from '../types/types-legacy';\nimport { UserConfig } from '../types/user';\nimport { DocumentUpdater } from '@db/study';\n\n/**\n * Read-only user data operations\n */\nexport interface UserDBReader {\n get<T>(id: string): Promise<T & PouchDB.Core.RevisionIdMeta>;\n getUsername(): string;\n isLoggedIn(): boolean;\n\n /**\n * Get user configuration\n */\n getConfig(): Promise<UserConfig>;\n\n /**\n * Get cards that the user has seen\n */\n getSeenCards(courseId?: string): Promise<string[]>;\n\n /**\n * Get cards that are actively scheduled for review\n */\n getActiveCards(): Promise<QualifiedCardID[]>;\n\n /**\n * Get user's course registrations\n */\n getCourseRegistrationsDoc(): Promise<CourseRegistrationDoc>;\n\n /**\n * Get the registration doc for a specific course.\n * @param courseId\n */\n getCourseRegDoc(courseId: string): Promise<CourseRegistration>;\n\n /**\n * Get user's active courses\n */\n getActiveCourses(): Promise<CourseRegistration[]>;\n\n /**\n * Get user's pending reviews\n */\n getPendingReviews(courseId?: string): Promise<ScheduledCard[]>;\n\n getActivityRecords(): Promise<ActivityRecord[]>;\n\n /**\n * Get user's classroom registrations\n */\n getUserClassrooms(): Promise<ClassroomRegistrationDoc>;\n\n /**\n * Get user's active classes\n */\n getActiveClasses(): Promise<string[]>;\n\n getCourseInterface(courseId: string): Promise<UsrCrsDataInterface>;\n}\n\n/**\n * User data mutation operations\n */\nexport interface UserDBWriter extends DocumentUpdater {\n /**\n * Update user configuration\n */\n setConfig(config: Partial<UserConfig>): Promise<void>;\n\n /**\n * Record a user's interaction with a card\n */\n putCardRecord<T extends CardRecord>(record: T): Promise<CardHistory<CardRecord>>;\n\n /**\n * Register user for a course\n */\n registerForCourse(courseId: string, previewMode?: boolean): Promise<PouchDB.Core.Response>;\n\n /**\n * Drop a course registration\n */\n dropCourse(courseId: string, dropStatus?: string): Promise<PouchDB.Core.Response>;\n\n /**\n * Schedule a card for review\n */\n scheduleCardReview(review: {\n user: string;\n course_id: string;\n card_id: string;\n time: Moment;\n scheduledFor: 'course' | 'classroom';\n schedulingAgentId: string;\n }): Promise<void>;\n\n /**\n * Remove a scheduled card review\n */\n removeScheduledCardReview(reviewId: string): Promise<void>;\n\n /**\n * Register user for a classroom\n */\n registerForClassroom(\n classId: string,\n registerAs: 'student' | 'teacher' | 'aide' | 'admin'\n ): Promise<PouchDB.Core.Response>;\n\n /**\n * Drop user from classroom\n */\n dropFromClassroom(classId: string): Promise<PouchDB.Core.Response>;\n\n /**\n * Update user's ELO rating for a course\n */\n updateUserElo(courseId: string, elo: CourseElo): Promise<PouchDB.Core.Response>;\n\n /**\n * Reset all user data (progress, registrations, etc.) while preserving authentication\n */\n resetUserData(): Promise<{ status: Status; error?: string }>;\n}\n\n/**\n * Authentication and account management operations\n */\nexport interface UserDBAuthenticator {\n /**\n * Create a new user account\n */\n createAccount(\n username: string,\n password: string\n ): Promise<{\n status: Status;\n error: string;\n }>;\n\n /**\n * Log in as a user\n */\n login(\n username: string,\n password: string\n ): Promise<{\n ok: boolean;\n name?: string;\n roles?: string[];\n }>;\n\n /**\n * Log out the current user\n */\n logout(): Promise<{\n ok: boolean;\n }>;\n}\n\n/**\n * Complete user database interface - combines all user operations\n * This maintains backward compatibility with existing code\n */\nexport interface UserDBInterface extends UserDBReader, UserDBWriter, UserDBAuthenticator {}\n\nexport interface UserCourseSettings {\n [setting: string]: string | number | boolean;\n}\n\nexport interface UserCourseSetting {\n key: string;\n value: string | number | boolean;\n}\n\n// [ ] reconsider here. Should maybe be generic type based on <T extends StudyContentSource> ?\nexport interface UsrCrsDataInterface {\n getScheduledReviewCount(): Promise<number>;\n getCourseSettings(): Promise<UserCourseSettings>;\n updateCourseSettings(updates: UserCourseSetting[]): void; // [ ] return a result of some sort?\n // getRegistrationDoc(): Promise<CourseRegistration>;\n}\n\nexport type ClassroomRegistrationDesignation = 'student' | 'teacher' | 'aide' | 'admin';\n\nexport interface ClassroomRegistration {\n classID: string;\n registeredAs: ClassroomRegistrationDesignation;\n}\n\nexport interface ClassroomRegistrationDoc {\n registrations: ClassroomRegistration[];\n}\n","export * from './adminDB';\nexport * from './classroomDB';\nexport * from './contentSource';\nexport * from './courseDB';\nexport * from './dataLayerProvider';\n\nexport * from './userDB';\n","import { CourseElo } from '@vue-skuilder/common';\nimport { Moment } from 'moment';\n\nexport interface SessionTrackingData {\n peekSessionCount: number;\n studySessionCount: number;\n sessionCount: number; // total\n firstSessionDate: string;\n lastSessionDate: string;\n signupPrompted: boolean;\n promptDismissalCount: number;\n studyModeAcknowledged: boolean;\n}\n\nexport interface UserConfig {\n darkMode: boolean;\n likesConfetti: boolean;\n sessionTimeLimit: number; // Session time limit in minutes\n email?: string; // Optional email for verification flows (added for enhanced auth)\n\n // Session tracking for trial enforcement (per-course)\n // Key is courseId (e.g., 'letterspractice-basic')\n sessionTracking?: Record<string, SessionTrackingData>;\n}\n\nexport interface ActivityRecord {\n timeStamp: number | string;\n [key: string]: any;\n}\n\nexport interface CourseRegistration {\n status?: 'active' | 'dropped' | 'maintenance-mode' | 'preview';\n courseID: string;\n admin: boolean;\n moderator: boolean;\n user: boolean;\n settings?: {\n [setting: string]: string | number | boolean;\n };\n elo: number | CourseElo;\n}\n\ninterface StudyWeights {\n [courseID: string]: number;\n}\n\nexport interface CourseRegistrationDoc {\n courses: CourseRegistration[];\n studyWeight: StudyWeights;\n}\n\nexport interface ScheduledCard {\n _id: PouchDB.Core.DocumentId;\n\n /**\n * The docID of the card to be reviewed\n */\n cardId: PouchDB.Core.DocumentId;\n /**\n * The ID of the course\n */\n courseId: string;\n /**\n * The time at which the card becomes eligible for review.\n *\n * (Should probably be UTC adjusted so that performance is\n * not wonky across time zones)\n * \n * Note: Stored as ISO string for PouchDB serialization compatibility,\n * but can be consumed as Moment objects via moment.utc(reviewTime)\n */\n reviewTime: string | Moment;\n\n /**\n * The time at which this scheduled event was created.\n * \n * Note: Stored as ISO string for PouchDB serialization compatibility,\n * but can be consumed as Moment objects via moment.utc(scheduledAt)\n */\n scheduledAt: string | Moment;\n\n /**\n * Classifying whether this card is scheduled on behalf of a\n * user-registered course or by as assigned content from a\n * user-registered classroom\n */\n scheduledFor: 'course' | 'classroom';\n\n /**\n * The ID of the course or classroom that requested this card\n */\n schedulingAgentId: string;\n}\n","import { CourseElo, Status, ParsedCard, BulkImportCardData } from '@vue-skuilder/common';\nimport { CourseDBInterface } from '../../core/interfaces/courseDB';\nimport { ImportResult, BulkCardProcessorConfig } from './types';\nimport { logger } from '../../util/logger';\n\n/**\n * Processes multiple cards from bulk text input\n *\n * @param parsedCards - Array of parsed cards to import\n * @param courseDB - Course database interface\n * @param config - Configuration for the card processor\n * @returns Array of import results\n */\nexport async function importParsedCards(\n parsedCards: ParsedCard[],\n courseDB: CourseDBInterface,\n config: BulkCardProcessorConfig\n): Promise<ImportResult[]> {\n const results: ImportResult[] = [];\n\n for (const parsedCard of parsedCards) {\n try {\n // processCard takes a ParsedCard and returns an ImportResult\n const result = await processCard(parsedCard, courseDB, config);\n results.push(result);\n } catch (error) {\n logger.error('Error processing card:', error);\n // Reconstruct originalText from parsedCard for this specific catch block\n // This is a fallback if processCard itself throws an unhandled error.\n // Normally, processCard should return an ImportResult with status 'error'.\n let errorOriginalText = parsedCard.markdown;\n if (parsedCard.tags && parsedCard.tags.length > 0) {\n errorOriginalText += `\\ntags: ${parsedCard.tags.join(', ')}`;\n }\n if (parsedCard.elo !== undefined) {\n errorOriginalText += `\\nelo: ${parsedCard.elo}`;\n }\n results.push({\n originalText: errorOriginalText,\n status: 'error',\n message: `Error processing card: ${\n error instanceof Error ? error.message : 'Unknown error'\n }`,\n });\n }\n }\n\n return results;\n}\n\n/**\n * Processes a single parsed card\n *\n * @param parsedCard - Parsed card data\n * @param courseDB - Course database interface\n * @param config - Configuration for the card processor\n * @returns Import result for the card\n */\nasync function processCard(\n parsedCard: ParsedCard,\n courseDB: CourseDBInterface,\n config: BulkCardProcessorConfig\n): Promise<ImportResult> {\n const { markdown, tags, elo } = parsedCard;\n\n // Build the original text representation including metadata\n let originalText = markdown;\n if (tags.length > 0) {\n originalText += `\\ntags: ${tags.join(', ')}`;\n }\n if (elo !== undefined) {\n originalText += `\\nelo: ${elo}`;\n }\n\n // Create card data object\n const cardData: BulkImportCardData = {\n Input: markdown,\n Uploads: [], // No uploads for bulk import\n };\n\n const tagsElo: CourseElo['tags'] = {};\n for (const tag of tags) {\n tagsElo[tag] = {\n score: elo || 0,\n count: 1,\n };\n }\n\n try {\n const result = await courseDB.addNote(\n config.courseCode,\n config.dataShape,\n cardData,\n config.userName,\n tags,\n undefined, // attachments\n elo\n ? {\n global: {\n score: elo,\n count: 1,\n },\n tags: tagsElo,\n misc: {},\n }\n : undefined\n );\n\n if (result.status === Status.ok) {\n return {\n originalText,\n status: 'success',\n message: 'Card added successfully.',\n cardId: result.id ? result.id : '(unknown)',\n };\n } else {\n return {\n originalText,\n status: 'error',\n message: result.message || 'Failed to add card to database. Unknown error.',\n };\n }\n } catch (error) {\n logger.error('Error adding note:', error);\n return {\n originalText,\n status: 'error',\n message: `Error adding card: ${error instanceof Error ? error.message : 'Unknown error'}`,\n };\n }\n}\n\n/**\n * Validates the configuration for bulk card processing\n *\n * @param config - Configuration to validate\n * @returns Object with validation result and error message if any\n */\nexport function validateProcessorConfig(config: Partial<BulkCardProcessorConfig>): {\n isValid: boolean;\n errorMessage?: string;\n} {\n if (!config.dataShape) {\n return {\n isValid: false,\n errorMessage: 'No data shape provided for card processing',\n };\n }\n\n if (!config.courseCode) {\n return {\n isValid: false,\n errorMessage: 'No course code provided for card processing',\n };\n }\n\n if (!config.userName) {\n return {\n isValid: false,\n errorMessage: 'No user name provided for card processing',\n };\n }\n\n return { isValid: true };\n}\n","import { DataShape } from '@vue-skuilder/common';\n\n/**\n * Interface representing the result of a bulk import operation for a single card\n */\nexport interface ImportResult {\n /** The original text input for the card */\n originalText: string;\n /** Status of the import operation */\n status: 'success' | 'error';\n /** Message describing the result or error */\n message: string;\n /** ID of the newly created card (only for success) */\n cardId?: string;\n}\n\n/**\n * Configuration for the bulk card processor\n */\nexport interface BulkCardProcessorConfig {\n /** The data shape to use for the cards */\n dataShape: DataShape;\n /** The course code used for adding notes */\n courseCode: string;\n /** The username of the current user */\n userName: string;\n}\n","export * from './cardProcessor.js';\nexport * from './types.js';","// Export all core interfaces and types\n\nexport * from './interfaces';\nexport * from './types/types-legacy';\nexport * from './types/user';\nexport * from '../util/Loggable';\nexport * from './util';\nexport * from './navigators';\nexport * from './bulkImport';\n","export * from './core';\n\nexport { default as CourseLookup } from './impl/couch/courseLookupDB';\n\nexport * from './study';\n\nexport * from './util';\nexport * from './factory';\n\n// Export CouchDB user types for use in Express backend\nexport type {\n UserAccountStatus,\n Entitlement,\n UserEntitlements,\n CouchDbUserDoc,\n} from './impl/couch/users.types';\n","import moment from 'moment';\nimport { CardHistory, CardRecord, UserDBInterface } from '@db/core';\nimport { isReview, StudySessionItem } from '@db/impl/couch';\nimport { newInterval } from '../SpacedRepetition';\nimport { logger } from '@db/util/logger';\n\n/**\n * Service responsible for Spaced Repetition System (SRS) scheduling logic.\n */\nexport class SrsService {\n private user: UserDBInterface;\n\n constructor(user: UserDBInterface) {\n this.user = user;\n }\n\n /**\n * Calculates the next review time for a card based on its history and\n * schedules it in the user's database.\n * @param history The full history of the card.\n * @param item The study session item, used to determine if a previous review needs to be cleared.\n */\n public async scheduleReview(\n history: CardHistory<CardRecord>,\n item: StudySessionItem\n ): Promise<void> {\n const nextInterval = newInterval(this.user, history);\n const nextReviewTime = moment.utc().add(nextInterval, 'seconds');\n\n if (isReview(item)) {\n logger.info(`[SrsService] Removing previously scheduled review for: ${item.cardID}`);\n void this.user.removeScheduledCardReview(item.reviewID);\n }\n\n void this.user.scheduleCardReview({\n user: this.user.getUsername(),\n course_id: history.courseID,\n card_id: history.cardID,\n time: nextReviewTime,\n scheduledFor: item.contentSourceType,\n schedulingAgentId: item.contentSourceID,\n });\n }\n}\n","import { CardHistory, CardRecord, QuestionRecord } from '@db/core/types/types-legacy';\nimport { areQuestionRecords } from '@db/core/util';\nimport { Update } from '@db/impl/couch/updateQueue';\nimport moment from 'moment';\nimport { logger } from '../util/logger';\n\ntype Moment = moment.Moment;\nconst duration = moment.duration;\n\nexport interface DocumentUpdater {\n update<T extends PouchDB.Core.Document<object>>(id: string, update: Update<T>): Promise<T>;\n}\n\n/**\n * Returns the minimum number of seconds that should pass before a\n * card is redisplayed for review / practice.\n *\n * @param cardHistory The user's history working with the given card\n */\nexport function newInterval(user: DocumentUpdater, cardHistory: CardHistory<CardRecord>): number {\n if (areQuestionRecords(cardHistory)) {\n return newQuestionInterval(user, cardHistory);\n } else {\n return 100000; // random - replace\n }\n}\n\nfunction newQuestionInterval(user: DocumentUpdater, cardHistory: CardHistory<QuestionRecord>) {\n const records = cardHistory.records;\n const currentAttempt = records[records.length - 1];\n const lastInterval: number = lastSuccessfulInterval(records);\n\n if (lastInterval > cardHistory.bestInterval) {\n cardHistory.bestInterval = lastInterval;\n // update bestInterval on cardHistory in db\n void user.update<CardHistory<QuestionRecord>>(cardHistory._id, {\n bestInterval: lastInterval,\n });\n }\n\n if (currentAttempt.isCorrect) {\n const skill = Math.min(1.0, Math.max(0.0, currentAttempt.performance as number));\n logger.debug(`Demontrated skill: \\t${skill}`);\n const interval: number = lastInterval * (0.75 + skill);\n cardHistory.lapses = getLapses(cardHistory.records);\n cardHistory.streak = getStreak(cardHistory.records);\n\n if (\n cardHistory.lapses &&\n cardHistory.streak &&\n cardHistory.bestInterval &&\n (cardHistory.lapses >= 0 || cardHistory.streak >= 0)\n ) {\n // weighted average of best-ever performance vs current performance, based\n // on how often the card has been failed, and the current streak of success\n const ret =\n (cardHistory.lapses * interval + cardHistory.streak * cardHistory.bestInterval) /\n (cardHistory.lapses + cardHistory.streak);\n logger.debug(`Weighted average interval calculation:\n\\t(${cardHistory.lapses} * ${interval} + ${cardHistory.streak} * ${cardHistory.bestInterval}) / (${cardHistory.lapses} + ${cardHistory.streak}) = ${ret}`);\n return ret;\n } else {\n return interval;\n }\n } else {\n return 0;\n }\n}\n\n/**\n * Returns the amount of time, in seconds, of the most recent successful\n * interval for this card. An interval is successful if the user answers\n * a question correctly on the first attempt.\n *\n * @param cardHistory The record of user attempts with the question\n */\nfunction lastSuccessfulInterval(cardHistory: QuestionRecord[]): number {\n for (let i = cardHistory.length - 1; i >= 1; i--) {\n if (cardHistory[i].priorAttemps === 0 && cardHistory[i].isCorrect) {\n const lastInterval = secondsBetween(cardHistory[i - 1].timeStamp, cardHistory[i].timeStamp);\n const ret = Math.max(lastInterval, 20 * 60 * 60);\n logger.debug(`Last interval w/ this card was: ${lastInterval}s, returning ${ret}s`);\n return ret;\n }\n }\n\n return getInitialInterval(cardHistory); // used as a magic number here - indicates no prior intervals\n}\n\nfunction getStreak(records: QuestionRecord[]): number {\n let streak = 0;\n let index = records.length - 1;\n\n while (index >= 0 && records[index].isCorrect) {\n index--;\n streak++;\n }\n\n return streak;\n}\nfunction getLapses(records: QuestionRecord[]): number {\n return records.filter((r) => r.isCorrect === false).length;\n}\n\nfunction getInitialInterval(cardHistory: QuestionRecord[]): number {\n logger.warn(`history of length: ${cardHistory.length} ignored!`);\n\n // todo make this a data-driven service, relying on:\n // - global experience w/ the card (ie, what interval\n // seems to be working well across the population)\n // - the individual user (how do they respond in general\n // when compared to the population)\n return 60 * 60 * 24 * 3; // 3 days\n}\n\n/**\n * Returns the time in seconds between two Moment objects\n * @param start The first time\n * @param end The second time\n */\nfunction secondsBetween(start: Moment, end: Moment): number {\n // assertion guard against mis-typed json from database\n start = moment(start);\n end = moment(end);\n const ret = duration(end.diff(start)).asSeconds();\n // console.log(`From start: ${start} to finish: ${end} is ${ret} seconds`);\n return ret;\n}\n","import { adjustCourseScores, toCourseElo } from '@vue-skuilder/common';\nimport { DataLayerProvider, UserDBInterface, CourseRegistrationDoc } from '@db/core';\nimport { StudySessionRecord } from '../SessionController';\nimport { logger } from '@db/util/logger';\n\n/**\n * Service responsible for ELO rating calculations and updates.\n */\nexport class EloService {\n private dataLayer: DataLayerProvider;\n private user: UserDBInterface;\n\n constructor(dataLayer: DataLayerProvider, user: UserDBInterface) {\n this.dataLayer = dataLayer;\n this.user = user;\n }\n\n /**\n * Updates both user and card ELO ratings based on user performance.\n * @param userScore Score between 0-1 representing user performance\n * @param course_id Course identifier\n * @param card_id Card identifier \n * @param userCourseRegDoc User's course registration document (will be mutated)\n * @param currentCard Current card session record\n * @param k Optional K-factor for ELO calculation\n */\n public async updateUserAndCardElo(\n userScore: number,\n course_id: string,\n card_id: string,\n userCourseRegDoc: CourseRegistrationDoc,\n currentCard: StudySessionRecord,\n k?: number\n ): Promise<void> {\n if (k) {\n logger.warn(`k value interpretation not currently implemented`);\n }\n const courseDB = this.dataLayer.getCourseDB(currentCard.card.course_id);\n const userElo = toCourseElo(userCourseRegDoc.courses.find((c) => c.courseID === course_id)!.elo);\n const cardElo = (await courseDB.getCardEloData([currentCard.card.card_id]))[0];\n\n if (cardElo && userElo) {\n const eloUpdate = adjustCourseScores(userElo, cardElo, userScore);\n userCourseRegDoc.courses.find((c) => c.courseID === course_id)!.elo = eloUpdate.userElo;\n\n const results = await Promise.allSettled([\n this.user.updateUserElo(course_id, eloUpdate.userElo),\n courseDB.updateCardElo(card_id, eloUpdate.cardElo),\n ]);\n\n // Check the results of each operation\n const userEloStatus = results[0].status === 'fulfilled';\n const cardEloStatus = results[1].status === 'fulfilled';\n\n if (userEloStatus && cardEloStatus) {\n const user = (results[0] as PromiseFulfilledResult<any>).value;\n const card = (results[1] as PromiseFulfilledResult<any>).value;\n\n if (user.ok && card && card.ok) {\n logger.info(\n `[EloService] Updated ELOS:\n \\tUser: ${JSON.stringify(eloUpdate.userElo)})\n \\tCard: ${JSON.stringify(eloUpdate.cardElo)})\n `\n );\n }\n } else {\n // Log which operations succeeded and which failed\n logger.warn(\n `[EloService] Partial ELO update:\n \\tUser ELO update: ${userEloStatus ? 'SUCCESS' : 'FAILED'}\n \\tCard ELO update: ${cardEloStatus ? 'SUCCESS' : 'FAILED'}`\n );\n\n if (!userEloStatus && results[0].status === 'rejected') {\n logger.error('[EloService] User ELO update error:', results[0].reason);\n }\n\n if (!cardEloStatus && results[1].status === 'rejected') {\n logger.error('[EloService] Card ELO update error:', results[1].reason);\n }\n }\n }\n }\n}","import {\n CardHistory,\n CardRecord,\n CourseRegistrationDoc,\n isQuestionRecord,\n QuestionRecord,\n StudySessionItem,\n} from '@db/core';\nimport { logger } from '@db/util/logger';\nimport { ResponseResult, StudySessionRecord } from '../SessionController';\nimport { EloService } from './EloService';\nimport { SrsService } from './SrsService';\n\n/**\n * Service responsible for orchestrating the complete response processing workflow.\n * Coordinates SRS scheduling and ELO updates for user card interactions.\n */\nexport class ResponseProcessor {\n private srsService: SrsService;\n private eloService: EloService;\n\n constructor(srsService: SrsService, eloService: EloService) {\n this.srsService = srsService;\n this.eloService = eloService;\n }\n\n /**\n * Processes a user's response to a card, handling SRS scheduling and ELO updates.\n * @param cardRecord User's response record\n * @param cardHistory Promise resolving to the card's history\n * @param studySessionItem Current study session item\n * @param courseRegistrationDoc User's course registration (for ELO updates)\n * @param currentCard Current study session record\n * @param courseId Course identifier\n * @param cardId Card identifier\n * @param maxAttemptsPerView Maximum attempts allowed per view\n * @param maxSessionViews Maximum session views for this card\n * @param sessionViews Current number of session views\n * @returns ResponseResult with navigation and UI instructions\n */\n public async processResponse(\n cardRecord: CardRecord,\n cardHistory: Promise<CardHistory<CardRecord>>,\n studySessionItem: StudySessionItem,\n courseRegistrationDoc: CourseRegistrationDoc,\n currentCard: StudySessionRecord,\n courseId: string,\n cardId: string,\n maxAttemptsPerView: number,\n maxSessionViews: number,\n sessionViews: number\n ): Promise<ResponseResult> {\n // Handle non-question records (simple dismiss)\n if (!isQuestionRecord(cardRecord)) {\n return {\n nextCardAction: 'dismiss-success',\n shouldLoadNextCard: true,\n isCorrect: true, // non-question records are considered \"correct\"\n shouldClearFeedbackShadow: true,\n };\n }\n\n // Debug logging for response processing\n // logger.debug('[ResponseProcessor] Processing response', {\n // cardId,\n // courseId,\n // isCorrect: cardRecord.isCorrect,\n // performance: cardRecord.performance,\n // priorAttempts: cardRecord.priorAttemps,\n // currentSessionViews: sessionViews,\n // maxSessionViews,\n // maxAttemptsPerView,\n // currentCardRecordsLength: currentCard.records.length,\n // studySessionSourceType: studySessionItem.contentSourceType,\n // studySessionSourceID: studySessionItem.contentSourceID,\n // studySessionItemId: studySessionItem.cardID,\n // studySessionItemType: studySessionItem.contentSourceType,\n\n // cardRecordTimestamp: cardRecord.timeStamp,\n // cardRecordResponseTime: cardRecord.timeSpent,\n // });\n\n try {\n const history = await cardHistory;\n\n // Debug logging for card history\n // logger.debug('[ResponseProcessor] History loaded:', {\n // cardId,\n // historyRecordsCount: history.records.length,\n // historyRecords: history.records.map((record) => ({\n // timeStamp: record.timeStamp,\n // isCorrect: 'isCorrect' in record ? record.isCorrect : 'N/A',\n // performance: 'performance' in record ? record.performance : 'N/A',\n // priorAttempts: 'priorAttemps' in record ? record.priorAttemps : 'N/A',\n // })),\n // firstInteraction: history.records.length === 1,\n // lastRecord: history.records[history.records.length - 1],\n // });\n\n // Handle correct responses\n if (cardRecord.isCorrect) {\n return this.processCorrectResponse(\n cardRecord,\n history,\n studySessionItem,\n courseRegistrationDoc,\n currentCard,\n courseId,\n cardId\n );\n } else {\n // Handle incorrect responses\n return this.processIncorrectResponse(\n cardRecord,\n history,\n courseRegistrationDoc,\n currentCard,\n courseId,\n cardId,\n maxAttemptsPerView,\n maxSessionViews,\n sessionViews\n );\n }\n } catch (e: unknown) {\n logger.error('[ResponseProcessor] Failed to load card history', { e, cardId });\n throw e;\n }\n }\n\n /**\n * Handles processing for correct responses: SRS scheduling and ELO updates.\n */\n private processCorrectResponse(\n cardRecord: QuestionRecord,\n history: CardHistory<CardRecord>,\n studySessionItem: StudySessionItem,\n courseRegistrationDoc: CourseRegistrationDoc,\n currentCard: StudySessionRecord,\n courseId: string,\n cardId: string\n ): ResponseResult {\n // Only schedule and update ELO for first-time attempts\n if (cardRecord.priorAttemps === 0) {\n // Schedule the card for future review based on performance (async, non-blocking)\n void this.srsService.scheduleReview(history, studySessionItem);\n\n // Update ELO ratings\n if (history.records.length === 1) {\n // First interaction with this card - standard ELO update (async, non-blocking)\n const userScore = 0.5 + (cardRecord.performance as number) / 2;\n void this.eloService.updateUserAndCardElo(\n userScore,\n courseId,\n cardId,\n courseRegistrationDoc,\n currentCard\n );\n } else {\n // Multiple interactions - reduce K-factor to limit ELO volatility (async, non-blocking)\n const k = Math.ceil(32 / history.records.length);\n const userScore = 0.5 + (cardRecord.performance as number) / 2;\n void this.eloService.updateUserAndCardElo(\n userScore,\n courseId,\n cardId,\n courseRegistrationDoc,\n currentCard,\n k\n );\n }\n\n logger.info(\n '[ResponseProcessor] Processed correct response with SRS scheduling and ELO update'\n );\n\n return {\n nextCardAction: 'dismiss-success',\n shouldLoadNextCard: true,\n isCorrect: true,\n performanceScore: cardRecord.performance as number,\n shouldClearFeedbackShadow: true,\n };\n } else {\n logger.info(\n '[ResponseProcessor] Processed correct response (retry attempt - no scheduling/ELO)'\n );\n\n return {\n nextCardAction: 'marked-failed',\n shouldLoadNextCard: true,\n isCorrect: true,\n performanceScore: cardRecord.performance as number,\n shouldClearFeedbackShadow: true,\n };\n }\n }\n\n /**\n * Handles processing for incorrect responses: ELO updates only.\n */\n private processIncorrectResponse(\n cardRecord: QuestionRecord,\n history: CardHistory<CardRecord>,\n courseRegistrationDoc: CourseRegistrationDoc,\n currentCard: StudySessionRecord,\n courseId: string,\n cardId: string,\n maxAttemptsPerView: number,\n maxSessionViews: number,\n sessionViews: number\n ): ResponseResult {\n // Update ELO for first-time failures (not subsequent attempts on same card) (async, non-blocking)\n if (history.records.length !== 1 && cardRecord.priorAttemps === 0) {\n void this.eloService.updateUserAndCardElo(\n 0, // Failed response = 0 score\n courseId,\n cardId,\n courseRegistrationDoc,\n currentCard\n );\n logger.info('[ResponseProcessor] Processed incorrect response with ELO update');\n } else {\n logger.info('[ResponseProcessor] Processed incorrect response (no ELO update needed)');\n }\n\n // Determine navigation based on attempt limits\n if (currentCard.records.length >= maxAttemptsPerView) {\n if (sessionViews >= maxSessionViews) {\n // Too many session views - dismiss completely with ELO penalty (async, non-blocking)\n void this.eloService.updateUserAndCardElo(\n 0,\n courseId,\n cardId,\n courseRegistrationDoc,\n currentCard\n );\n return {\n nextCardAction: 'dismiss-failed',\n shouldLoadNextCard: true,\n isCorrect: false,\n shouldClearFeedbackShadow: true,\n };\n } else {\n // Mark as failed for later retry\n return {\n nextCardAction: 'marked-failed',\n shouldLoadNextCard: true,\n isCorrect: false,\n shouldClearFeedbackShadow: true,\n };\n }\n } else {\n // Allow more attempts on same card\n return {\n nextCardAction: 'none',\n shouldLoadNextCard: false,\n isCorrect: false,\n shouldClearFeedbackShadow: true,\n };\n }\n }\n}\n","import {\n displayableDataToViewData,\n CardData,\n DisplayableData,\n isCourseElo,\n toCourseElo,\n} from '@vue-skuilder/common';\nimport { StudySessionItem } from '@db/impl/couch';\nimport { logger } from '@db/util/logger';\nimport { ItemQueue } from '../ItemQueue';\nimport { CourseDBInterface } from '@db/core';\n\nexport interface HydratedCard<TView = unknown> {\n item: StudySessionItem;\n view: TView;\n data: any[];\n}\n\n// ItemQueue now imported from separate file\n\n/**\n * Service responsible for managing a queue of hydrated (ready-to-display) cards.\n * Handles pre-fetching card data, caching failed cards, and maintaining optimal buffer size.\n */\nexport class CardHydrationService<TView = unknown> {\n private hydratedQ: ItemQueue<HydratedCard<TView>> = new ItemQueue<HydratedCard<TView>>();\n private failedCardCache: Map<string, HydratedCard<TView>> = new Map();\n private hydrationInProgress: boolean = false;\n private readonly BUFFER_SIZE = 5;\n\n constructor(\n private getViewComponent: (viewId: string) => TView,\n private getCourseDB: (courseId: string) => CourseDBInterface,\n private selectNextItemToHydrate: () => StudySessionItem | null,\n private removeItemFromQueue: (item: StudySessionItem) => void,\n private hasAvailableCards: () => boolean\n ) {}\n\n /**\n * Get the next hydrated card from the queue.\n * @returns Hydrated card or null if none available\n */\n public dequeueHydratedCard(): HydratedCard<TView> | null {\n return this.hydratedQ.dequeue((item) => item.item.cardID);\n }\n\n /**\n * Check if hydration should be triggered and start background hydration if needed.\n */\n public async ensureHydratedCards(): Promise<void> {\n // Trigger background hydration to maintain cache (async, non-blocking)\n if (this.hydratedQ.length < 3) {\n void this.fillHydratedQueue();\n }\n }\n\n /**\n * Wait for a hydrated card to become available.\n * @returns Promise that resolves to a hydrated card or null\n */\n public async waitForHydratedCard(): Promise<HydratedCard<TView> | null> {\n // If no hydrated card but source cards available, start hydration\n if (this.hydratedQ.length === 0 && this.hasAvailableCards()) {\n void this.fillHydratedQueue(); // Start hydration in background\n }\n\n // Wait for a card to become available in hydratedQ\n while (this.hydratedQ.length === 0 && this.hasAvailableCards()) {\n await new Promise((resolve) => setTimeout(resolve, 25)); // Short polling interval\n }\n\n return this.dequeueHydratedCard();\n }\n\n /**\n * Get current hydrated queue length.\n */\n public get hydratedCount(): number {\n return this.hydratedQ.length;\n }\n\n /**\n * Get current failed card cache size.\n */\n public get failedCacheSize(): number {\n return this.failedCardCache.size;\n }\n\n /**\n * Fill the hydrated queue up to BUFFER_SIZE with pre-fetched cards.\n */\n private async fillHydratedQueue(): Promise<void> {\n if (this.hydrationInProgress) {\n return; // Prevent concurrent hydration\n }\n\n this.hydrationInProgress = true;\n\n try {\n while (this.hydratedQ.length < this.BUFFER_SIZE) {\n const nextItem = this.selectNextItemToHydrate();\n if (!nextItem) {\n return; // No more cards to hydrate\n }\n\n try {\n // Check cache first for failed cards\n if (this.failedCardCache.has(nextItem.cardID)) {\n const cachedCard = this.failedCardCache.get(nextItem.cardID)!;\n this.hydratedQ.add(cachedCard, cachedCard.item.cardID);\n this.failedCardCache.delete(nextItem.cardID);\n } else {\n // Hydrate new card using original logic pattern\n const courseDB = this.getCourseDB(nextItem.courseID);\n const cardData = await courseDB.getCourseDoc<CardData>(nextItem.cardID);\n\n if (!isCourseElo(cardData.elo)) {\n cardData.elo = toCourseElo(cardData.elo);\n }\n\n const view = this.getViewComponent(cardData.id_view);\n const dataDocs = await Promise.all(\n cardData.id_displayable_data.map((id: string) =>\n courseDB.getCourseDoc<DisplayableData>(id, {\n attachments: true,\n binary: true,\n })\n )\n );\n\n const data = dataDocs.map(displayableDataToViewData).reverse();\n\n this.hydratedQ.add(\n {\n item: nextItem,\n view,\n data,\n },\n nextItem.cardID\n );\n }\n } catch (e) {\n logger.error(`Error hydrating card ${nextItem.cardID}:`, e);\n } finally {\n // Remove the item from the original queue, regardless of success/failure/cache\n this.removeItemFromQueue(nextItem);\n }\n }\n } finally {\n this.hydrationInProgress = false;\n }\n }\n\n /**\n * Cache a failed card for quick re-access.\n */\n public cacheFailedCard(card: HydratedCard<TView>): void {\n this.failedCardCache.set(card.item.cardID, card);\n }\n}\n","export class ItemQueue<T> {\n private q: T[] = [];\n private seenCardIds: string[] = [];\n private _dequeueCount: number = 0;\n public get dequeueCount(): number {\n return this._dequeueCount;\n }\n\n public add(item: T, cardId: string) {\n if (this.seenCardIds.find((d) => d === cardId)) {\n return; // do not re-add a card to the same queue\n }\n\n this.seenCardIds.push(cardId);\n this.q.push(item);\n }\n\n public addAll(items: T[], cardIdExtractor: (item: T) => string) {\n items.forEach((i) => this.add(i, cardIdExtractor(i)));\n }\n\n public get length() {\n return this.q.length;\n }\n\n public peek(index: number): T {\n return this.q[index];\n }\n\n public dequeue(cardIdExtractor?: (item: T) => string): T | null {\n if (this.q.length !== 0) {\n this._dequeueCount++;\n const item = this.q.splice(0, 1)[0];\n\n // Remove cardId from seenCardIds when dequeuing to allow re-queueing\n if (cardIdExtractor) {\n const cardId = cardIdExtractor(item);\n const index = this.seenCardIds.indexOf(cardId);\n if (index > -1) {\n this.seenCardIds.splice(index, 1);\n }\n }\n\n return item;\n } else {\n return null;\n }\n }\n\n public get toString(): string {\n return (\n `${typeof this.q[0]}:\\n` +\n this.q\n .map((i) => `\\t${(i as any).courseID}+${(i as any).cardID}: ${(i as any).status}`)\n .join('\\n')\n );\n }\n}","import { SrsService } from './services/SrsService';\nimport { EloService } from './services/EloService';\nimport { ResponseProcessor } from './services/ResponseProcessor';\nimport { CardHydrationService, HydratedCard } from './services/CardHydrationService';\nimport { ItemQueue } from './ItemQueue';\nimport {\n isReview,\n StudyContentSource,\n StudySessionFailedItem,\n StudySessionItem,\n StudySessionNewItem,\n StudySessionReviewItem,\n} from '@db/impl/couch';\n\nimport { CardRecord, CardHistory, CourseRegistrationDoc } from '@db/core';\nimport { Loggable } from '@db/util';\nimport { ScheduledCard } from '@db/core/types/user';\nimport { WeightedCard, getCardOrigin } from '@db/core/navigators';\n\nfunction randomInt(min: number, max: number): number {\n return Math.floor(Math.random() * (max - min + 1)) + min;\n}\n\nexport interface StudySessionRecord {\n card: {\n course_id: string;\n card_id: string;\n card_elo: number;\n };\n item: StudySessionItem;\n records: CardRecord[];\n}\n\nimport { DataLayerProvider } from '@db/core';\n\nexport type SessionAction =\n | 'dismiss-success'\n | 'dismiss-failed'\n | 'marked-failed'\n | 'dismiss-error';\n\nexport interface ResponseResult {\n // Navigation\n nextCardAction: Exclude<SessionAction, 'dismiss-error'> | 'none';\n shouldLoadNextCard: boolean;\n\n // UI Data (let view decide how to render)\n isCorrect: boolean;\n performanceScore?: number; // for shadow color calculation\n\n // Cleanup\n shouldClearFeedbackShadow: boolean;\n}\n\ninterface SessionServices {\n response: ResponseProcessor;\n}\n\nexport class SessionController<TView = unknown> extends Loggable {\n _className = 'SessionController';\n\n public services: SessionServices;\n private srsService: SrsService;\n private eloService: EloService;\n private hydrationService: CardHydrationService<TView>;\n\n private sources: StudyContentSource[];\n // dataLayer and getViewComponent now injected into CardHydrationService\n private _sessionRecord: StudySessionRecord[] = [];\n public set sessionRecord(r: StudySessionRecord[]) {\n this._sessionRecord = r;\n }\n\n // Session card stores\n private _currentCard: HydratedCard<TView> | null = null;\n\n private reviewQ: ItemQueue<StudySessionReviewItem> = new ItemQueue<StudySessionReviewItem>();\n private newQ: ItemQueue<StudySessionNewItem> = new ItemQueue<StudySessionNewItem>();\n private failedQ: ItemQueue<StudySessionFailedItem> = new ItemQueue<StudySessionFailedItem>();\n // END Session card stores\n\n private startTime: Date;\n private endTime: Date;\n private _secondsRemaining: number;\n public get secondsRemaining(): number {\n return this._secondsRemaining;\n }\n public get report(): string {\n return `${this.reviewQ.dequeueCount} reviews, ${this.newQ.dequeueCount} new cards`;\n }\n public get detailedReport(): string {\n return this.newQ.toString + '\\n' + this.reviewQ.toString + '\\n' + this.failedQ.toString;\n }\n // @ts-expect-error NodeJS.Timeout type not available in browser context\n private _intervalHandle: NodeJS.Timeout;\n\n /**\n *\n */\n constructor(\n sources: StudyContentSource[],\n time: number,\n dataLayer: DataLayerProvider,\n getViewComponent: (viewId: string) => TView\n ) {\n super();\n\n this.srsService = new SrsService(dataLayer.getUserDB());\n this.eloService = new EloService(dataLayer, dataLayer.getUserDB());\n\n this.hydrationService = new CardHydrationService<TView>(\n getViewComponent,\n (courseId: string) => dataLayer.getCourseDB(courseId),\n () => this._selectNextItemToHydrate(),\n (item: StudySessionItem) => this.removeItemFromQueue(item),\n () => this.hasAvailableCards()\n );\n\n this.services = {\n response: new ResponseProcessor(this.srsService, this.eloService),\n };\n\n this.sources = sources;\n this.startTime = new Date();\n this._secondsRemaining = time;\n this.endTime = new Date(this.startTime.valueOf() + 1000 * this._secondsRemaining);\n\n this.log(`Session constructed:\n startTime: ${this.startTime}\n endTime: ${this.endTime}`);\n }\n\n private tick() {\n this._secondsRemaining = Math.floor((this.endTime.valueOf() - Date.now()) / 1000);\n // this.log(this.secondsRemaining);\n\n if (this._secondsRemaining <= 0) {\n clearInterval(this._intervalHandle);\n }\n }\n\n /**\n * Returns a rough, erring toward conservative, guess at\n * the amount of time the failed cards queue will require\n * to clean up.\n *\n * (seconds)\n */\n private estimateCleanupTime(): number {\n let time: number = 0;\n for (let i = 0; i < this.failedQ.length; i++) {\n const c = this.failedQ.peek(i);\n // this.log(`Failed card ${c.qualifiedID} found`)\n\n const record = this._sessionRecord.find((r) => r.item.cardID === c.cardID);\n let cardTime = 0;\n\n if (record) {\n // this.log(`Card Record Found...`);\n for (let j = 0; j < record.records.length; j++) {\n cardTime += record.records[j].timeSpent;\n }\n cardTime = cardTime / record.records.length;\n time += cardTime;\n }\n }\n\n const ret: number = time / 1000;\n this.log(`Failed card cleanup estimate: ${Math.round(ret)}`);\n return ret;\n }\n\n /**\n * Extremely rough, conservative, estimate of amound of time to complete\n * all scheduled reviews\n */\n private estimateReviewTime(): number {\n const ret = 5 * this.reviewQ.length;\n this.log(`Review card time estimate: ${ret}`);\n return ret;\n }\n\n public async prepareSession() {\n try {\n // Use new getWeightedCards API if available, fall back to legacy methods\n const hasWeightedCards = this.sources.some((s) => typeof s.getWeightedCards === 'function');\n\n if (hasWeightedCards) {\n await this.getWeightedContent();\n } else {\n // Legacy path: separate calls for reviews and new cards\n await Promise.all([this.getScheduledReviews(), this.getNewCards()]);\n }\n } catch (e) {\n this.error('Error preparing study session:', e);\n }\n\n await this.hydrationService.ensureHydratedCards();\n\n this._intervalHandle = setInterval(() => {\n this.tick();\n }, 1000);\n }\n\n public addTime(seconds: number) {\n this.endTime = new Date(this.endTime.valueOf() + 1000 * seconds);\n }\n\n public get failedCount(): number {\n return this.failedQ.length;\n }\n\n public toString() {\n return `Session: ${this.reviewQ.length} Reviews, ${this.newQ.length} New, ${this.failedQ.length} failed`;\n }\n public reportString() {\n return `${this.reviewQ.dequeueCount} Reviews, ${this.newQ.dequeueCount} New, ${this.failedQ.dequeueCount} failed`;\n }\n\n /**\n * Returns debug information about the current session state.\n * Used by SessionControllerDebug component for runtime inspection.\n */\n public getDebugInfo() {\n // Check if sources support weighted cards\n const supportsWeightedCards = this.sources.some(\n (s) => typeof s.getWeightedCards === 'function'\n );\n\n const extractQueueItems = (queue: ItemQueue<any>, limit: number = 10) => {\n const items = [];\n for (let i = 0; i < Math.min(queue.length, limit); i++) {\n const item = queue.peek(i);\n items.push({\n courseID: item.courseID || 'unknown',\n cardID: item.cardID || 'unknown',\n status: item.status || 'unknown',\n });\n }\n return items;\n };\n\n const extractHydratedItems = () => {\n // We can't easily iterate the hydrated queue without dequeuing,\n // so we'll just report the count via hydratedCache.count below\n\n const items: any[] = [];\n return items;\n };\n\n return {\n api: {\n mode: supportsWeightedCards ? 'weighted' : 'legacy',\n description: supportsWeightedCards\n ? 'Using getWeightedCards() API with scored candidates'\n : 'Using legacy getNewCards()/getPendingReviews() API',\n },\n reviewQueue: {\n length: this.reviewQ.length,\n dequeueCount: this.reviewQ.dequeueCount,\n items: extractQueueItems(this.reviewQ),\n },\n newQueue: {\n length: this.newQ.length,\n dequeueCount: this.newQ.dequeueCount,\n items: extractQueueItems(this.newQ),\n },\n failedQueue: {\n length: this.failedQ.length,\n dequeueCount: this.failedQ.dequeueCount,\n items: extractQueueItems(this.failedQ),\n },\n hydratedCache: {\n count: this.hydrationService.hydratedCount,\n failedCacheSize: this.hydrationService.failedCacheSize,\n items: extractHydratedItems(),\n },\n };\n }\n\n /**\n * Fetch content using the new getWeightedCards API.\n *\n * This method uses getWeightedCards() to get scored candidates, then uses the\n * scores to determine ordering. For reviews, we still need the full ScheduledCard\n * data from getPendingReviews(), so we fetch both and use scores for ordering.\n *\n * The hybrid approach:\n * 1. Fetch weighted cards to get scoring/ordering information\n * 2. Fetch full review data via legacy getPendingReviews()\n * 3. Order reviews by their weighted scores\n * 4. Add new cards ordered by their weighted scores\n */\n private async getWeightedContent() {\n const limit = 20; // Initial batch size per source\n\n // Collect weighted cards for scoring, and full review data for queue population\n const allWeighted: WeightedCard[] = [];\n const allReviews: (StudySessionReviewItem & ScheduledCard)[] = [];\n const allNewCards: StudySessionNewItem[] = [];\n\n for (const source of this.sources) {\n try {\n // Always fetch full review data (we need ScheduledCard fields)\n const reviews = await source.getPendingReviews().catch((error) => {\n this.error(`Failed to get reviews for source:`, error);\n return [];\n });\n allReviews.push(...reviews);\n\n // Fetch weighted cards for scoring if available\n if (typeof source.getWeightedCards === 'function') {\n const weighted = await source.getWeightedCards(limit);\n allWeighted.push(...weighted);\n } else {\n // Fallback: fetch new cards directly and assign score=1.0\n const newCards = await source.getNewCards(limit);\n allNewCards.push(...newCards);\n\n // Create pseudo-weighted entries for ordering\n allWeighted.push(\n ...newCards.map((c) => ({\n cardId: c.cardID,\n courseId: c.courseID,\n score: 1.0,\n provenance: [\n {\n strategy: 'legacy',\n strategyName: 'Legacy Fallback',\n strategyId: 'legacy-fallback',\n action: 'generated' as const,\n score: 1.0,\n reason: 'Fallback to legacy getNewCards(), new card',\n },\n ],\n })),\n ...reviews.map((r) => ({\n cardId: r.cardID,\n courseId: r.courseID,\n score: 1.0,\n provenance: [\n {\n strategy: 'legacy',\n strategyName: 'Legacy Fallback',\n strategyId: 'legacy-fallback',\n action: 'generated' as const,\n score: 1.0,\n reason: 'Fallback to legacy getPendingReviews(), review',\n },\n ],\n }))\n );\n }\n } catch (error) {\n this.error(`Failed to get content from source:`, error);\n }\n }\n\n // Build a score lookup map from weighted cards\n const scoreMap = new Map<string, number>();\n for (const w of allWeighted) {\n const key = `${w.courseId}::${w.cardId}`;\n scoreMap.set(key, w.score);\n }\n\n // Sort reviews by score (from weighted cards) descending\n const scoredReviews = allReviews.map((r) => ({\n review: r,\n score: scoreMap.get(`${r.courseID}::${r.cardID}`) ?? 1.0,\n }));\n scoredReviews.sort((a, b) => b.score - a.score);\n\n // Add reviews to queue in score order\n let report = 'Weighted content session created with:\\n';\n for (const { review, score } of scoredReviews) {\n this.reviewQ.add(review, review.cardID);\n report += `Review: ${review.courseID}::${review.cardID} (score: ${score.toFixed(2)})\\n`;\n }\n\n // Get new cards from weighted list (filter out reviews)\n const newCardWeighted = allWeighted\n .filter((w) => getCardOrigin(w) === 'new')\n .sort((a, b) => b.score - a.score);\n\n // Add new cards to queue in score order\n for (const card of newCardWeighted) {\n const newItem: StudySessionNewItem = {\n cardID: card.cardId,\n courseID: card.courseId,\n contentSourceType: 'course',\n contentSourceID: card.courseId,\n status: 'new',\n };\n this.newQ.add(newItem, card.cardId);\n report += `New: ${card.courseId}::${card.cardId} (score: ${card.score.toFixed(2)})\\n`;\n }\n\n this.log(report);\n }\n\n /**\n * @deprecated Use getWeightedContent() instead. This method is kept for backward\n * compatibility with sources that don't support getWeightedCards().\n */\n private async getScheduledReviews() {\n const reviews = await Promise.all(\n this.sources.map((c) =>\n c.getPendingReviews().catch((error) => {\n this.error(`Failed to get reviews for source ${c}:`, error);\n return [];\n })\n )\n );\n\n const dueCards: (StudySessionReviewItem & ScheduledCard)[] = [];\n\n while (reviews.length != 0 && reviews.some((r) => r.length > 0)) {\n // pick a random review source\n const index = randomInt(0, reviews.length - 1);\n const source = reviews[index];\n\n if (source.length === 0) {\n reviews.splice(index, 1);\n continue;\n } else {\n dueCards.push(source.shift()!);\n }\n }\n\n let report = 'Review session created with:\\n';\n this.reviewQ.addAll(dueCards, (c) => c.cardID);\n report += dueCards.map((card) => `Card ${card.courseID}::${card.cardID} `).join('\\n');\n this.log(report);\n }\n\n /**\n * @deprecated Use getWeightedContent() instead. This method is kept for backward\n * compatibility with sources that don't support getWeightedCards().\n */\n private async getNewCards(n: number = 10) {\n const perCourse = Math.ceil(n / this.sources.length);\n const newContent = await Promise.all(this.sources.map((c) => c.getNewCards(perCourse)));\n\n // [ ] is this a noop?\n newContent.forEach((newContentFromSource) => {\n newContentFromSource.filter((c) => {\n return this._sessionRecord.find((record) => record.card.card_id === c.cardID) === undefined;\n });\n });\n\n while (n > 0 && newContent.some((nc) => nc.length > 0)) {\n for (let i = 0; i < newContent.length; i++) {\n if (newContent[i].length > 0) {\n const item = newContent[i].splice(0, 1)[0];\n this.log(`Adding new card: ${item.courseID}::${item.cardID}`);\n this.newQ.add(item, item.cardID);\n n--;\n }\n }\n }\n }\n\n private _selectNextItemToHydrate(): StudySessionItem | null {\n const choice = Math.random();\n let newBound: number = 0.1;\n let reviewBound: number = 0.75;\n\n if (this.reviewQ.length === 0 && this.failedQ.length === 0 && this.newQ.length === 0) {\n // all queues empty - session is over (and course is complete?)\n return null;\n }\n\n if (this._secondsRemaining < 2 && this.failedQ.length === 0) {\n // session is over!\n return null;\n }\n\n // If timer expired, only return failed cards\n if (this._secondsRemaining <= 0) {\n if (this.failedQ.length > 0) {\n return this.failedQ.peek(0);\n } else {\n return null; // No more failed cards, session over\n }\n }\n\n // supply new cards at start of session\n if (this.newQ.dequeueCount < this.sources.length && this.newQ.length) {\n return this.newQ.peek(0);\n }\n\n const cleanupTime = this.estimateCleanupTime();\n const reviewTime = this.estimateReviewTime();\n const availableTime = this._secondsRemaining - (cleanupTime + reviewTime);\n\n // if time-remaing vs (reviewQ + failureQ) looks good,\n // lean toward newQ\n if (availableTime > 20) {\n newBound = 0.5;\n reviewBound = 0.9;\n }\n // else if time-remaining vs failureQ looks good,\n // lean toward reviewQ\n else if (this._secondsRemaining - cleanupTime > 20) {\n newBound = 0.05;\n reviewBound = 0.9;\n }\n // else (time-remaining vs failureQ looks bad!)\n // lean heavily toward failureQ\n else {\n newBound = 0.01;\n reviewBound = 0.1;\n }\n\n // exclude possibility of drawing from empty queues\n if (this.failedQ.length === 0) {\n reviewBound = 1;\n }\n if (this.reviewQ.length === 0) {\n newBound = reviewBound;\n }\n\n if (choice < newBound && this.newQ.length) {\n return this.newQ.peek(0);\n } else if (choice < reviewBound && this.reviewQ.length) {\n return this.reviewQ.peek(0);\n } else if (this.failedQ.length) {\n return this.failedQ.peek(0);\n } else {\n this.log(`No more cards available for the session!`);\n return null;\n }\n }\n\n public async nextCard(\n action: SessionAction = 'dismiss-success'\n ): Promise<HydratedCard<TView> | null> {\n // dismiss (or sort to failedQ) the current card\n this.dismissCurrentCard(action);\n\n if (this._secondsRemaining <= 0 && this.failedQ.length === 0) {\n this._currentCard = null;\n return null;\n }\n\n let card = this.hydrationService.dequeueHydratedCard();\n\n // If no hydrated card but source cards available, wait for hydration\n if (!card && this.hasAvailableCards()) {\n card = await this.hydrationService.waitForHydratedCard();\n }\n\n // Trigger background hydration to maintain cache (async, non-blocking)\n await this.hydrationService.ensureHydratedCards();\n\n if (card) {\n this._currentCard = card;\n } else {\n this._currentCard = null;\n }\n\n return card;\n }\n\n /**\n * Public API for processing user responses to cards.\n * @param cardRecord User's response record\n * @param cardHistory Promise resolving to the card's history\n * @param courseRegistrationDoc User's course registration document\n * @param currentCard Current study session record\n * @param courseId Course identifier\n * @param cardId Card identifier\n * @param maxAttemptsPerView Maximum attempts allowed per view\n * @param maxSessionViews Maximum session views for this card\n * @param sessionViews Current number of session views\n * @returns ResponseResult with navigation and UI instructions\n */\n public async submitResponse(\n cardRecord: CardRecord,\n cardHistory: Promise<CardHistory<CardRecord>>,\n courseRegistrationDoc: CourseRegistrationDoc,\n currentCard: StudySessionRecord,\n courseId: string,\n cardId: string,\n maxAttemptsPerView: number,\n maxSessionViews: number,\n sessionViews: number\n ): Promise<ResponseResult> {\n const studySessionItem: StudySessionItem = {\n ...currentCard.item,\n };\n\n return await this.services.response.processResponse(\n cardRecord,\n cardHistory,\n studySessionItem,\n courseRegistrationDoc,\n currentCard,\n courseId,\n cardId,\n maxAttemptsPerView,\n maxSessionViews,\n sessionViews\n );\n }\n\n private dismissCurrentCard(action: SessionAction = 'dismiss-success') {\n if (this._currentCard) {\n // this.log(`Running dismissCurrentCard on ${this._currentCard!.qualifiedID}`);\n // if (action.includes('dismiss')) {\n // if (this._currentCard.status === 'review' ||\n // this._currentCard.status === 'failed-review') {\n // removeScheduledCardReview(this.user.getUsername(),\n // (this._currentCard as StudySessionReviewItem).reviewID);\n // this.log(`Dismissed review card: ${this._currentCard.qualifiedID}`)\n // }\n // }\n\n if (action === 'dismiss-success') {\n // schedule a review - currently done in Study.vue\n } else if (action === 'marked-failed') {\n this.hydrationService.cacheFailedCard(this._currentCard);\n\n let failedItem: StudySessionFailedItem;\n\n if (isReview(this._currentCard.item)) {\n failedItem = {\n cardID: this._currentCard.item.cardID,\n courseID: this._currentCard.item.courseID,\n contentSourceID: this._currentCard.item.contentSourceID,\n contentSourceType: this._currentCard.item.contentSourceType,\n status: 'failed-review',\n reviewID: this._currentCard.item.reviewID,\n };\n } else {\n failedItem = {\n cardID: this._currentCard.item.cardID,\n courseID: this._currentCard.item.courseID,\n contentSourceID: this._currentCard.item.contentSourceID,\n contentSourceType: this._currentCard.item.contentSourceType,\n status: 'failed-new',\n };\n }\n\n this.failedQ.add(failedItem, failedItem.cardID);\n } else if (action === 'dismiss-error') {\n // some error logging?\n } else if (action === 'dismiss-failed') {\n // handled by Study.vue\n }\n }\n }\n\n private hasAvailableCards(): boolean {\n return this.reviewQ.length > 0 || this.newQ.length > 0 || this.failedQ.length > 0;\n }\n\n /**\n * Helper method for CardHydrationService to remove items from appropriate queue.\n */\n private removeItemFromQueue(item: StudySessionItem): void {\n if (this.reviewQ.peek(0) === item) {\n this.reviewQ.dequeue((queueItem) => queueItem.cardID);\n } else if (this.newQ.peek(0) === item) {\n this.newQ.dequeue((queueItem) => queueItem.cardID);\n } else {\n this.failedQ.dequeue((queueItem) => queueItem.cardID);\n }\n }\n}\n","export * from './Loggable';\nexport * from './packer';\nexport * from './migrator';\nexport * from './dataDirectory';\nexport * from './tuiLogger';\n","// packages/db/src/util/packer/CouchDBToStaticPacker.ts\n\nimport { CardData, DocType, Tag } from '../../core/types/types-legacy';\nimport { logger } from '../logger';\n// CourseConfig interface - simplified for packer use\n\nimport { CourseConfig } from '@vue-skuilder/common';\nimport {\n ChunkMetadata,\n DesignDocument,\n IndexMetadata,\n PackedCourseData,\n PackerConfig,\n StaticCourseManifest,\n AttachmentData,\n} from './types';\nimport { FileSystemAdapter } from '../migrator/FileSystemAdapter';\n\nexport class CouchDBToStaticPacker {\n private config: PackerConfig;\n private sourceDB: PouchDB.Database | null = null;\n\n constructor(config: Partial<PackerConfig> = {}) {\n this.config = {\n chunkSize: 1000,\n includeAttachments: true,\n ...config,\n };\n }\n\n /**\n * Pack a CouchDB course database into static data structures\n */\n async packCourse(sourceDB: PouchDB.Database, courseId: string): Promise<PackedCourseData> {\n logger.info(`Starting static pack for course: ${courseId}`);\n this.sourceDB = sourceDB;\n\n const manifest: StaticCourseManifest = {\n version: '1.0.0',\n courseId,\n courseName: '',\n courseConfig: null,\n lastUpdated: new Date().toISOString(),\n documentCount: 0,\n chunks: [],\n indices: [],\n designDocs: [],\n };\n\n // 1. Extract course config\n const courseConfig = await this.extractCourseConfig(sourceDB);\n manifest.courseName = courseConfig.name;\n manifest.courseConfig = courseConfig;\n\n // 2. Extract and process design documents\n manifest.designDocs = await this.extractDesignDocs(sourceDB);\n\n // 3. Extract all documents by type and create chunks\n const docsByType = await this.extractDocumentsByType(sourceDB);\n\n // 4. Extract attachments if enabled\n const attachments = new Map<string, AttachmentData>();\n if (this.config.includeAttachments) {\n await this.extractAllAttachments(docsByType, attachments);\n }\n\n // 5. Create chunks and prepare chunk data\n const chunks = new Map<string, any[]>();\n for (const [docType, docs] of Object.entries(docsByType)) {\n const chunkMetadata = this.createChunks(docs, docType as DocType);\n manifest.chunks.push(...chunkMetadata);\n manifest.documentCount += docs.length;\n\n // Prepare chunk data\n this.prepareChunkData(chunkMetadata, docs, chunks);\n }\n\n // 6. Build indices\n const indices = new Map<string, any>();\n manifest.indices = await this.buildIndices(docsByType, manifest.designDocs, indices);\n\n return {\n manifest,\n chunks,\n indices,\n attachments,\n };\n }\n\n /**\n * Pack a CouchDB course database and write the static files to disk\n */\n async packCourseToFiles(\n sourceDB: PouchDB.Database, \n courseId: string, \n outputDir: string, \n fsAdapter: FileSystemAdapter\n ): Promise<{ \n manifest: StaticCourseManifest; \n filesWritten: number; \n attachmentsFound: number; \n }> {\n logger.info(`Packing course ${courseId} to files in ${outputDir}`);\n \n // First, pack the course data\n const packedData = await this.packCourse(sourceDB, courseId);\n \n // Write the files using the FileSystemAdapter\n const filesWritten = await this.writePackedDataToFiles(packedData, outputDir, fsAdapter);\n \n return {\n manifest: packedData.manifest,\n filesWritten,\n attachmentsFound: packedData.attachments ? packedData.attachments.size : 0,\n };\n }\n\n /**\n * Write packed course data to files using FileSystemAdapter\n */\n private async writePackedDataToFiles(\n packedData: PackedCourseData,\n outputDir: string,\n fsAdapter: FileSystemAdapter\n ): Promise<number> {\n let totalFiles = 0;\n \n // Ensure output directory exists\n await fsAdapter.ensureDir(outputDir);\n \n // Write manifest\n const manifestPath = fsAdapter.joinPath(outputDir, 'manifest.json');\n await fsAdapter.writeJson(manifestPath, packedData.manifest, { spaces: 2 });\n totalFiles++;\n logger.info(`Wrote manifest: ${manifestPath}`);\n \n // Create subdirectories\n const chunksDir = fsAdapter.joinPath(outputDir, 'chunks');\n const indicesDir = fsAdapter.joinPath(outputDir, 'indices');\n await fsAdapter.ensureDir(chunksDir);\n await fsAdapter.ensureDir(indicesDir);\n \n // Write chunks\n for (const [chunkId, chunkData] of packedData.chunks) {\n const chunkPath = fsAdapter.joinPath(chunksDir, `${chunkId}.json`);\n await fsAdapter.writeJson(chunkPath, chunkData);\n totalFiles++;\n }\n logger.info(`Wrote ${packedData.chunks.size} chunk files`);\n \n // Write indices\n for (const [indexName, indexData] of packedData.indices) {\n const indexPath = fsAdapter.joinPath(indicesDir, `${indexName}.json`);\n await fsAdapter.writeJson(indexPath, indexData, { spaces: 2 });\n totalFiles++;\n }\n logger.info(`Wrote ${packedData.indices.size} index files`);\n \n // Write attachments\n if (packedData.attachments && packedData.attachments.size > 0) {\n for (const [attachmentPath, attachmentData] of packedData.attachments) {\n const fullAttachmentPath = fsAdapter.joinPath(outputDir, attachmentPath);\n \n // Ensure attachment directory exists\n const attachmentDir = fsAdapter.dirname(fullAttachmentPath);\n await fsAdapter.ensureDir(attachmentDir);\n \n // Write binary file\n await fsAdapter.writeFile(fullAttachmentPath, attachmentData.buffer);\n totalFiles++;\n }\n logger.info(`Wrote ${packedData.attachments.size} attachment files`);\n }\n \n return totalFiles;\n }\n\n private async extractCourseConfig(db: PouchDB.Database): Promise<CourseConfig> {\n try {\n return await db.get<CourseConfig>('CourseConfig');\n } catch (error) {\n logger.error('Failed to extract course config:', error);\n throw new Error('Course config not found');\n }\n }\n\n private async extractDesignDocs(db: PouchDB.Database): Promise<DesignDocument[]> {\n const result = await db.allDocs({\n startkey: '_design/',\n endkey: '_design/\\ufff0',\n include_docs: true,\n });\n\n return result.rows.map((row) => ({\n _id: row.id,\n views: (row.doc as any).views || {},\n }));\n }\n\n private async extractDocumentsByType(db: PouchDB.Database): Promise<Record<DocType, any[]>> {\n const allDocs = await db.allDocs({ include_docs: true });\n const docsByType: Record<string, any[]> = {};\n\n for (const row of allDocs.rows) {\n if (row.id.startsWith('_')) continue; // Skip design docs\n\n const doc = row.doc as any;\n if (doc.docType) {\n if (!docsByType[doc.docType]) {\n docsByType[doc.docType] = [];\n }\n docsByType[doc.docType].push(doc);\n }\n }\n\n return docsByType as Record<DocType, any[]>;\n }\n\n private createChunks(docs: any[], docType: DocType): ChunkMetadata[] {\n const chunks: ChunkMetadata[] = [];\n const sortedDocs = docs.sort((a, b) => a._id.localeCompare(b._id));\n\n for (let i = 0; i < sortedDocs.length; i += this.config.chunkSize) {\n const chunk = sortedDocs.slice(i, i + this.config.chunkSize);\n const chunkId = `${docType}-${String(Math.floor(i / this.config.chunkSize)).padStart(4, '0')}`;\n\n chunks.push({\n id: chunkId,\n docType,\n startKey: chunk[0]._id,\n endKey: chunk[chunk.length - 1]._id,\n documentCount: chunk.length,\n path: `chunks/${chunkId}.json`,\n });\n }\n\n return chunks;\n }\n\n private prepareChunkData(\n chunkMetadata: ChunkMetadata[],\n docs: any[],\n chunks: Map<string, any[]>\n ): void {\n const sortedDocs = docs.sort((a, b) => a._id.localeCompare(b._id));\n\n for (const chunk of chunkMetadata) {\n const chunkDocs = sortedDocs.filter(\n (doc) => doc._id >= chunk.startKey && doc._id <= chunk.endKey\n );\n\n // Clean documents for storage\n const cleanedDocs = chunkDocs.map((doc) => {\n const cleaned = { ...doc };\n delete cleaned._rev; // Remove revision info\n \n if (this.config.includeAttachments && cleaned._attachments) {\n // Transform attachment stubs to file paths\n cleaned._attachments = this.transformAttachmentStubs(cleaned._attachments, cleaned._id);\n } else if (!this.config.includeAttachments) {\n delete cleaned._attachments;\n }\n \n return cleaned;\n });\n\n chunks.set(chunk.id, cleanedDocs);\n }\n }\n\n private async buildIndices(\n docsByType: Record<DocType, any[]>,\n designDocs: DesignDocument[],\n indices: Map<string, any>\n ): Promise<IndexMetadata[]> {\n const indexMetadata: IndexMetadata[] = [];\n\n // Build ELO index\n if (docsByType[DocType.CARD]) {\n const eloIndexMeta = await this.buildEloIndex(\n docsByType[DocType.CARD] as CardData[],\n indices\n );\n indexMetadata.push(eloIndexMeta);\n }\n\n // Build tag indices\n if (docsByType[DocType.TAG]) {\n const tagIndexMeta = await this.buildTagIndex(docsByType[DocType.TAG] as Tag[], indices);\n indexMetadata.push(tagIndexMeta);\n }\n\n // Build indices from design documents using CouchDB view queries\n for (const designDoc of designDocs) {\n for (const [viewName, viewDef] of Object.entries(designDoc.views)) {\n if (viewDef.map) {\n logger.info(`Processing view: ${designDoc._id}/${viewName}`);\n const indexMeta = await this.buildViewIndex(viewName, designDoc, indices);\n if (indexMeta) {\n indexMetadata.push(indexMeta);\n logger.info(`Successfully built index: ${indexMeta.name}`);\n } else {\n logger.warn(`Skipped view index: ${designDoc._id}/${viewName}`);\n }\n }\n }\n }\n\n return indexMetadata;\n }\n\n private async buildEloIndex(\n cards: CardData[],\n indices: Map<string, any>\n ): Promise<IndexMetadata> {\n // Build a B-tree like structure for ELO queries\n const eloIndex: Array<{ elo: number; cardId: string }> = [];\n\n for (const card of cards) {\n if (card.elo?.global?.score) {\n eloIndex.push({\n elo: card.elo.global.score,\n cardId: (card as any)._id,\n });\n }\n }\n\n // Sort by ELO for efficient range queries\n eloIndex.sort((a, b) => a.elo - b.elo);\n\n // Create buckets for faster lookup\n const buckets: Record<number, string[]> = {};\n const bucketSize = 50; // ELO points per bucket\n\n for (const entry of eloIndex) {\n const bucket = Math.floor(entry.elo / bucketSize) * bucketSize;\n if (!buckets[bucket]) buckets[bucket] = [];\n buckets[bucket].push(entry.cardId);\n }\n\n // Store the index data\n indices.set('elo', {\n sorted: eloIndex,\n buckets: buckets,\n stats: {\n min: eloIndex[0]?.elo || 0,\n max: eloIndex[eloIndex.length - 1]?.elo || 0,\n count: eloIndex.length,\n },\n });\n\n return {\n name: 'elo',\n type: 'btree',\n path: 'indices/elo.json',\n };\n }\n\n private async buildTagIndex(tags: Tag[], indices: Map<string, any>): Promise<IndexMetadata> {\n // Build inverted index for tags\n const tagIndex: Record<\n string,\n {\n cardIds: string[];\n snippet: string;\n count: number;\n }\n > = {};\n\n for (const tag of tags) {\n tagIndex[tag.name] = {\n cardIds: tag.taggedCards,\n snippet: tag.snippet,\n count: tag.taggedCards.length,\n };\n }\n\n // Also build a reverse index (card -> tags)\n const cardToTags: Record<string, string[]> = {};\n for (const tag of tags) {\n for (const cardId of tag.taggedCards) {\n if (!cardToTags[cardId]) cardToTags[cardId] = [];\n cardToTags[cardId].push(tag.name);\n }\n }\n\n indices.set('tags', {\n byTag: tagIndex,\n byCard: cardToTags,\n });\n\n return {\n name: 'tags',\n type: 'hash',\n path: 'indices/tags.json',\n };\n }\n\n /**\n * Build view index by querying CouchDB views directly instead of parsing map functions\n */\n private async buildViewIndex(\n viewName: string,\n designDoc: DesignDocument,\n indices: Map<string, any>\n ): Promise<IndexMetadata | null> {\n if (!this.sourceDB) {\n logger.error('Source database not available for view querying');\n return null;\n }\n\n try {\n const designDocId = designDoc._id; // e.g., \"_design/elo\"\n const designDocName = designDocId.replace('_design/', ''); // Extract just \"elo\"\n const viewPath = `${designDocName}/${viewName}`;\n \n logger.info(`Querying CouchDB view: ${viewPath}`);\n \n // Query the view directly from CouchDB using PouchDB format: \"designDocName/viewName\"\n const viewResults = await this.sourceDB.query(viewPath, {\n include_docs: false,\n });\n\n if (!viewResults.rows || viewResults.rows.length === 0) {\n logger.warn(`View ${viewPath} returned no results`);\n return null;\n }\n\n logger.info(`Successfully queried view ${viewPath}: ${viewResults.rows.length} results`);\n\n // Format the results for static consumption\n const formattedResults = this.formatViewResults(viewName, viewResults.rows, designDoc);\n \n const indexName = `view-${designDoc._id.replace('_design/', '')}-${viewName}`;\n indices.set(indexName, formattedResults);\n\n return {\n name: indexName,\n type: 'view',\n path: `indices/${indexName}.json`,\n };\n } catch (error) {\n logger.error(`Failed to query view ${designDoc._id}/${viewName}:`, error);\n // Return null to gracefully skip this view rather than failing the entire pack\n return null;\n }\n }\n\n /**\n * Format CouchDB view results for static consumption\n */\n private formatViewResults(\n viewName: string,\n viewRows: Array<{ key: any; value: any; id: string }>,\n designDoc: DesignDocument\n ): any {\n const baseResult = {\n type: 'couchdb-view',\n viewName,\n designDoc: designDoc._id,\n results: viewRows,\n metadata: {\n resultCount: viewRows.length,\n generatedAt: new Date().toISOString(),\n },\n };\n\n // Apply view-specific formatting\n switch (viewName) {\n case 'elo':\n return this.formatEloViewIndex(viewRows, baseResult);\n case 'getTags':\n return this.formatTagsViewIndex(viewRows, baseResult);\n case 'cardsByInexperience':\n return this.formatInexperienceViewIndex(viewRows, baseResult);\n default:\n return this.formatGenericViewIndex(viewRows, baseResult);\n }\n }\n\n /**\n * Format ELO view results - convert to sorted array format\n */\n private formatEloViewIndex(\n viewRows: Array<{ key: any; value: any; id: string }>,\n baseResult: any\n ): any {\n // Sort by ELO score (key) for efficient range queries\n const sortedResults = viewRows.sort((a, b) => {\n if (typeof a.key === 'number' && typeof b.key === 'number') {\n return a.key - b.key;\n }\n return 0;\n });\n\n return {\n ...baseResult,\n sorted: sortedResults,\n stats: {\n min: sortedResults[0]?.key || 0,\n max: sortedResults[sortedResults.length - 1]?.key || 0,\n count: sortedResults.length,\n },\n };\n }\n\n /**\n * Format tags view results - convert to tag mapping format\n */\n private formatTagsViewIndex(\n viewRows: Array<{ key: any; value: any; id: string }>,\n baseResult: any\n ): any {\n // Group by tag name (key)\n const tagMap: Record<string, string[]> = {};\n \n for (const row of viewRows) {\n const tagName = row.key;\n if (typeof tagName === 'string') {\n if (!tagMap[tagName]) {\n tagMap[tagName] = [];\n }\n tagMap[tagName].push(row.id);\n }\n }\n\n return {\n ...baseResult,\n byTag: tagMap,\n tagCount: Object.keys(tagMap).length,\n };\n }\n\n /**\n * Format inexperience view results - convert to experience-based format\n */\n private formatInexperienceViewIndex(\n viewRows: Array<{ key: any; value: any; id: string }>,\n baseResult: any\n ): any {\n // Sort by inexperience level (key) - lower numbers = less experience\n const sortedResults = viewRows.sort((a, b) => {\n if (typeof a.key === 'number' && typeof b.key === 'number') {\n return a.key - b.key;\n }\n return 0;\n });\n\n return {\n ...baseResult,\n sorted: sortedResults,\n stats: {\n minInexperience: sortedResults[0]?.key || 0,\n maxInexperience: sortedResults[sortedResults.length - 1]?.key || 0,\n count: sortedResults.length,\n },\n };\n }\n\n /**\n * Format generic view results - fallback for unknown views\n */\n private formatGenericViewIndex(\n _viewRows: Array<{ key: any; value: any; id: string }>,\n baseResult: any\n ): any {\n return {\n ...baseResult,\n // Keep results as-is for unknown view types\n };\n }\n\n /**\n * Extract all attachments from documents and download binary data\n */\n private async extractAllAttachments(\n docsByType: Record<DocType, any[]>,\n attachments: Map<string, AttachmentData>\n ): Promise<void> {\n logger.info('Extracting attachments...');\n \n const allDocs: any[] = [];\n for (const docs of Object.values(docsByType)) {\n allDocs.push(...docs);\n }\n\n const docsWithAttachments = allDocs.filter(doc => doc._attachments && Object.keys(doc._attachments).length > 0);\n \n if (docsWithAttachments.length === 0) {\n logger.info('No attachments found');\n return;\n }\n\n logger.info(`Found ${docsWithAttachments.length} documents with attachments`);\n\n // Process attachments concurrently\n const extractionPromises = docsWithAttachments.map(doc => \n this.extractDocumentAttachments(doc, attachments)\n );\n\n await Promise.all(extractionPromises);\n \n logger.info(`Extracted ${attachments.size} attachment files`);\n }\n\n /**\n * Extract attachments for a single document\n */\n private async extractDocumentAttachments(\n doc: any,\n attachments: Map<string, AttachmentData>\n ): Promise<void> {\n if (!doc._attachments || !this.sourceDB) {\n return;\n }\n\n const docId = doc._id;\n \n for (const [attachmentName, metadata] of Object.entries(doc._attachments as Record<string, any>)) {\n try {\n // Download attachment binary data\n const attachmentResponse = await this.sourceDB.getAttachment(docId, attachmentName);\n \n // Convert to buffer\n let buffer: Buffer;\n if (attachmentResponse instanceof ArrayBuffer) {\n buffer = Buffer.from(attachmentResponse);\n } else if (Buffer.isBuffer(attachmentResponse)) {\n buffer = attachmentResponse;\n } else {\n // For browser environments, the response might be a Blob\n const blob = attachmentResponse as Blob;\n buffer = Buffer.from(await blob.arrayBuffer());\n }\n\n // Generate filename with proper extension\n const extension = this.getFileExtension(metadata.content_type);\n const filename = `${attachmentName}${extension}`;\n const attachmentPath = `attachments/${docId}/${filename}`;\n \n // Store attachment data\n attachments.set(attachmentPath, {\n docId,\n attachmentName,\n filename,\n path: attachmentPath,\n contentType: metadata.content_type,\n length: metadata.length || buffer.length,\n digest: metadata.digest,\n buffer,\n });\n\n logger.debug(`Extracted attachment: ${attachmentPath}`);\n } catch (error) {\n logger.error(`Failed to extract attachment ${docId}/${attachmentName}:`, error);\n throw new Error(`Failed to extract attachment ${docId}/${attachmentName}: ${error}`);\n }\n }\n }\n\n /**\n * Transform attachment stubs to include file paths\n */\n private transformAttachmentStubs(\n attachments: Record<string, any>,\n docId: string\n ): Record<string, any> {\n const transformed: Record<string, any> = {};\n \n for (const [attachmentName, metadata] of Object.entries(attachments)) {\n const extension = this.getFileExtension(metadata.content_type);\n const filename = `${attachmentName}${extension}`;\n const attachmentPath = `attachments/${docId}/${filename}`;\n \n transformed[attachmentName] = {\n path: attachmentPath,\n content_type: metadata.content_type,\n length: metadata.length,\n digest: metadata.digest,\n stub: false, // No longer a stub - we have the actual file\n };\n }\n \n return transformed;\n }\n\n /**\n * Get file extension from content type\n */\n private getFileExtension(contentType: string): string {\n const extensionMap: Record<string, string> = {\n 'image/jpeg': '.jpg',\n 'image/jpg': '.jpg', \n 'image/png': '.png',\n 'image/gif': '.gif',\n 'image/webp': '.webp',\n 'audio/mpeg': '.mp3',\n 'audio/mp3': '.mp3',\n 'audio/wav': '.wav',\n 'audio/ogg': '.ogg',\n 'video/mp4': '.mp4',\n 'video/webm': '.webm',\n 'application/pdf': '.pdf',\n 'text/plain': '.txt',\n 'application/json': '.json',\n };\n \n return extensionMap[contentType] || '';\n }\n}\n","// packages/db/src/util/migrator/StaticToCouchDBMigrator.ts\n\nimport { logger } from '../logger';\nimport { StaticCourseManifest, ChunkMetadata, DesignDocument } from '../packer/types';\nimport {\n MigrationOptions,\n MigrationResult,\n DEFAULT_MIGRATION_OPTIONS,\n DocumentCounts,\n RestoreProgress,\n AggregatedDocument,\n RestoreResults,\n AttachmentUploadResult,\n} from './types';\nimport { validateStaticCourse, validateMigration } from './validation';\nimport { FileSystemAdapter, FileSystemError } from './FileSystemAdapter';\n\n// Fallback for environments without FileSystemAdapter (backward compatibility)\nlet nodeFS: any = null;\nlet nodePath: any = null;\ntry {\n if (typeof window === 'undefined' && typeof process !== 'undefined' && process.versions?.node) {\n nodeFS = eval('require')('fs');\n nodePath = eval('require')('path');\n nodeFS.promises = nodeFS.promises || eval('require')('fs').promises;\n }\n} catch {\n // fs not available, will use fetch\n}\n\nexport class StaticToCouchDBMigrator {\n private options: MigrationOptions;\n private progressCallback?: (progress: RestoreProgress) => void;\n private fs?: FileSystemAdapter;\n\n constructor(options: Partial<MigrationOptions> = {}, fileSystemAdapter?: FileSystemAdapter) {\n this.options = {\n ...DEFAULT_MIGRATION_OPTIONS,\n ...options,\n };\n this.fs = fileSystemAdapter;\n }\n\n /**\n * Set a progress callback to receive updates during migration\n */\n setProgressCallback(callback: (progress: RestoreProgress) => void): void {\n this.progressCallback = callback;\n }\n\n /**\n * Migrate a static course to CouchDB\n */\n async migrateCourse(staticPath: string, targetDB: PouchDB.Database): Promise<MigrationResult> {\n const startTime = Date.now();\n const result: MigrationResult = {\n success: false,\n documentsRestored: 0,\n attachmentsRestored: 0,\n designDocsRestored: 0,\n courseConfigRestored: 0,\n errors: [] as string[],\n warnings: [] as string[],\n migrationTime: 0,\n };\n\n try {\n logger.info(`Starting migration from ${staticPath} to CouchDB`);\n\n // Phase 1: Validate static course\n this.reportProgress('manifest', 0, 1, 'Validating static course...');\n const validation = await validateStaticCourse(staticPath, this.fs);\n if (!validation.valid) {\n result.errors.push(...validation.errors);\n throw new Error(`Static course validation failed: ${validation.errors.join(', ')}`);\n }\n result.warnings.push(...validation.warnings);\n\n // Phase 2: Load manifest\n this.reportProgress('manifest', 1, 1, 'Loading course manifest...');\n const manifest = await this.loadManifest(staticPath);\n logger.info(`Loaded manifest for course: ${manifest.courseId} (${manifest.courseName})`);\n\n // Phase 3: Restore design documents\n this.reportProgress(\n 'design_docs',\n 0,\n manifest.designDocs.length,\n 'Restoring design documents...'\n );\n const designDocResults = await this.restoreDesignDocuments(manifest.designDocs, targetDB);\n result.designDocsRestored = designDocResults.restored;\n result.errors.push(...designDocResults.errors);\n result.warnings.push(...designDocResults.warnings);\n\n // Phase 3.5: Restore CourseConfig\n this.reportProgress('course_config', 0, 1, 'Restoring CourseConfig document...');\n const courseConfigResults = await this.restoreCourseConfig(manifest, targetDB);\n result.courseConfigRestored = courseConfigResults.restored;\n result.errors.push(...courseConfigResults.errors);\n result.warnings.push(...courseConfigResults.warnings);\n this.reportProgress('course_config', 1, 1, 'CourseConfig document restored');\n\n // Phase 4: Aggregate and restore documents\n const expectedCounts = this.calculateExpectedCounts(manifest);\n this.reportProgress(\n 'documents',\n 0,\n manifest.documentCount,\n 'Aggregating documents from chunks...'\n );\n const documents = await this.aggregateDocuments(staticPath, manifest);\n\n // Filter out CourseConfig documents to prevent conflicts with Phase 3.5\n const filteredDocuments = documents.filter((doc) => doc._id !== 'CourseConfig');\n if (documents.length !== filteredDocuments.length) {\n result.warnings.push(\n `Filtered out ${documents.length - filteredDocuments.length} CourseConfig document(s) from chunks to prevent conflicts`\n );\n }\n\n this.reportProgress(\n 'documents',\n filteredDocuments.length,\n manifest.documentCount,\n 'Uploading documents to CouchDB...'\n );\n const docResults = await this.uploadDocuments(filteredDocuments, targetDB);\n result.documentsRestored = docResults.restored;\n result.errors.push(...docResults.errors);\n result.warnings.push(...docResults.warnings);\n\n // Phase 5: Upload attachments\n const docsWithAttachments = documents.filter(\n (doc) => doc._attachments && Object.keys(doc._attachments).length > 0\n );\n this.reportProgress('attachments', 0, docsWithAttachments.length, 'Uploading attachments...');\n const attachmentResults = await this.uploadAttachments(\n staticPath,\n docsWithAttachments,\n targetDB\n );\n result.attachmentsRestored = attachmentResults.restored;\n result.errors.push(...attachmentResults.errors);\n result.warnings.push(...attachmentResults.warnings);\n\n // Phase 6: Validation (if enabled)\n if (this.options.validateRoundTrip) {\n this.reportProgress('validation', 0, 1, 'Validating migration...');\n const validationResult = await validateMigration(targetDB, expectedCounts, manifest);\n if (!validationResult.valid) {\n result.warnings.push('Migration validation found issues');\n validationResult.issues.forEach((issue) => {\n if (issue.type === 'error') {\n result.errors.push(`Validation: ${issue.message}`);\n } else {\n result.warnings.push(`Validation: ${issue.message}`);\n }\n });\n }\n this.reportProgress('validation', 1, 1, 'Migration validation completed');\n }\n\n // Success!\n result.success = result.errors.length === 0;\n result.migrationTime = Date.now() - startTime;\n\n logger.info(`Migration completed in ${result.migrationTime}ms`);\n logger.info(`Documents restored: ${result.documentsRestored}`);\n logger.info(`Attachments restored: ${result.attachmentsRestored}`);\n logger.info(`Design docs restored: ${result.designDocsRestored}`);\n logger.info(`CourseConfig restored: ${result.courseConfigRestored}`);\n\n if (result.errors.length > 0) {\n logger.error(`Migration completed with ${result.errors.length} errors`);\n }\n if (result.warnings.length > 0) {\n logger.warn(`Migration completed with ${result.warnings.length} warnings`);\n }\n } catch (error) {\n result.success = false;\n result.migrationTime = Date.now() - startTime;\n const errorMessage = error instanceof Error ? error.message : String(error);\n result.errors.push(`Migration failed: ${errorMessage}`);\n logger.error('Migration failed:', error);\n\n // Cleanup on failure if requested\n if (this.options.cleanupOnFailure) {\n try {\n await this.cleanupFailedMigration(targetDB);\n } catch (cleanupError) {\n logger.error('Failed to cleanup after migration failure:', cleanupError);\n result.warnings.push('Failed to cleanup after migration failure');\n }\n }\n }\n\n return result;\n }\n\n /**\n * Load and parse the manifest file\n */\n private async loadManifest(staticPath: string): Promise<StaticCourseManifest> {\n try {\n let manifestContent: string;\n let manifestPath: string;\n\n if (this.fs) {\n // Use injected file system adapter (preferred)\n manifestPath = this.fs.joinPath(staticPath, 'manifest.json');\n manifestContent = await this.fs.readFile(manifestPath);\n } else {\n // Fallback to legacy behavior for backward compatibility\n manifestPath =\n nodeFS && nodePath\n ? nodePath.join(staticPath, 'manifest.json')\n : `${staticPath}/manifest.json`;\n\n if (nodeFS && this.isLocalPath(staticPath)) {\n // Node.js file system access\n manifestContent = await nodeFS.promises.readFile(manifestPath, 'utf8');\n } else {\n // Browser/fetch access\n const response = await fetch(manifestPath);\n if (!response.ok) {\n throw new Error(`Failed to fetch manifest: ${response.status} ${response.statusText}`);\n }\n manifestContent = await response.text();\n }\n }\n\n const manifest: StaticCourseManifest = JSON.parse(manifestContent);\n\n // Basic validation\n if (!manifest.version || !manifest.courseId || !manifest.chunks) {\n throw new Error('Invalid manifest structure');\n }\n\n return manifest;\n } catch (error) {\n const errorMessage =\n error instanceof FileSystemError\n ? error.message\n : `Failed to load manifest: ${error instanceof Error ? error.message : String(error)}`;\n throw new Error(errorMessage);\n }\n }\n\n /**\n * Restore design documents to CouchDB\n */\n private async restoreDesignDocuments(\n designDocs: DesignDocument[],\n db: PouchDB.Database\n ): Promise<{ restored: number; errors: string[]; warnings: string[] }> {\n const result = { restored: 0, errors: [] as string[], warnings: [] as string[] };\n\n for (let i = 0; i < designDocs.length; i++) {\n const designDoc = designDocs[i];\n this.reportProgress('design_docs', i, designDocs.length, `Restoring ${designDoc._id}...`);\n\n try {\n // Check if design document already exists\n let existingDoc;\n try {\n existingDoc = await db.get(designDoc._id);\n } catch {\n // Document doesn't exist, which is fine\n }\n\n // Prepare the document for insertion\n const docToInsert: any = {\n _id: designDoc._id,\n views: designDoc.views,\n };\n\n // If document exists, include the revision for update\n if (existingDoc) {\n docToInsert._rev = existingDoc._rev;\n logger.debug(`Updating existing design document: ${designDoc._id}`);\n } else {\n logger.debug(`Creating new design document: ${designDoc._id}`);\n }\n\n await db.put(docToInsert);\n result.restored++;\n } catch (error) {\n const errorMessage = `Failed to restore design document ${designDoc._id}: ${error instanceof Error ? error.message : String(error)}`;\n result.errors.push(errorMessage);\n logger.error(errorMessage);\n }\n }\n\n this.reportProgress(\n 'design_docs',\n designDocs.length,\n designDocs.length,\n `Restored ${result.restored} design documents`\n );\n return result;\n }\n\n /**\n * Aggregate documents from all chunks\n */\n private async aggregateDocuments(\n staticPath: string,\n manifest: StaticCourseManifest\n ): Promise<AggregatedDocument[]> {\n const allDocuments: AggregatedDocument[] = [];\n const documentMap = new Map<string, AggregatedDocument>(); // For deduplication\n\n for (let i = 0; i < manifest.chunks.length; i++) {\n const chunk = manifest.chunks[i];\n this.reportProgress(\n 'documents',\n allDocuments.length,\n manifest.documentCount,\n `Loading chunk ${chunk.id}...`\n );\n\n try {\n const documents = await this.loadChunk(staticPath, chunk);\n\n for (const doc of documents) {\n if (!doc._id) {\n logger.warn(`Document without _id found in chunk ${chunk.id}, skipping`);\n continue;\n }\n\n // Handle potential duplicates (shouldn't happen, but be safe)\n if (documentMap.has(doc._id)) {\n logger.warn(`Duplicate document ID found: ${doc._id}, using latest version`);\n }\n\n documentMap.set(doc._id, doc);\n }\n } catch (error) {\n throw new Error(\n `Failed to load chunk ${chunk.id}: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n }\n\n // Convert map to array\n allDocuments.push(...documentMap.values());\n\n logger.info(\n `Aggregated ${allDocuments.length} unique documents from ${manifest.chunks.length} chunks`\n );\n return allDocuments;\n }\n\n /**\n * Load documents from a single chunk file\n */\n private async loadChunk(staticPath: string, chunk: ChunkMetadata): Promise<any[]> {\n try {\n let chunkContent: string;\n let chunkPath: string;\n\n if (this.fs) {\n // Use injected file system adapter (preferred)\n chunkPath = this.fs.joinPath(staticPath, chunk.path);\n chunkContent = await this.fs.readFile(chunkPath);\n } else {\n // Fallback to legacy behavior for backward compatibility\n chunkPath =\n nodeFS && nodePath\n ? nodePath.join(staticPath, chunk.path)\n : `${staticPath}/${chunk.path}`;\n\n if (nodeFS && this.isLocalPath(staticPath)) {\n // Node.js file system access\n chunkContent = await nodeFS.promises.readFile(chunkPath, 'utf8');\n } else {\n // Browser/fetch access\n const response = await fetch(chunkPath);\n if (!response.ok) {\n throw new Error(`Failed to fetch chunk: ${response.status} ${response.statusText}`);\n }\n chunkContent = await response.text();\n }\n }\n\n const documents = JSON.parse(chunkContent);\n\n if (!Array.isArray(documents)) {\n throw new Error('Chunk file does not contain an array of documents');\n }\n\n return documents;\n } catch (error) {\n const errorMessage =\n error instanceof FileSystemError\n ? error.message\n : `Failed to load chunk: ${error instanceof Error ? error.message : String(error)}`;\n throw new Error(errorMessage);\n }\n }\n\n /**\n * Upload documents to CouchDB in batches\n */\n private async uploadDocuments(\n documents: AggregatedDocument[],\n db: PouchDB.Database\n ): Promise<{ restored: number; errors: string[]; warnings: string[] }> {\n const result = { restored: 0, errors: [] as string[], warnings: [] as string[] };\n const batchSize = this.options.chunkBatchSize;\n\n for (let i = 0; i < documents.length; i += batchSize) {\n const batch = documents.slice(i, i + batchSize);\n this.reportProgress(\n 'documents',\n i,\n documents.length,\n `Uploading batch ${Math.floor(i / batchSize) + 1}...`\n );\n\n try {\n // Prepare documents for bulk insert\n const docsToInsert = batch.map((doc) => {\n const cleanDoc = { ...doc };\n // Remove _rev if present (CouchDB will assign new revision)\n delete cleanDoc._rev;\n // Remove _attachments - these are uploaded separately in Phase 5\n delete cleanDoc._attachments;\n\n return cleanDoc;\n });\n\n const bulkResult = await db.bulkDocs(docsToInsert);\n\n // Process results\n for (let j = 0; j < bulkResult.length; j++) {\n const docResult = bulkResult[j];\n const originalDoc = batch[j];\n\n if ('error' in docResult) {\n const errorMessage = `Failed to upload document ${originalDoc._id}: ${docResult.error} - ${docResult.reason}`;\n result.errors.push(errorMessage);\n logger.error(errorMessage);\n } else {\n result.restored++;\n }\n }\n } catch (error) {\n let errorMessage: string;\n if (error instanceof Error) {\n errorMessage = `Failed to upload document batch starting at index ${i}: ${error.message}`;\n } else if (error && typeof error === 'object' && 'message' in error) {\n errorMessage = `Failed to upload document batch starting at index ${i}: ${(error as any).message}`;\n } else {\n errorMessage = `Failed to upload document batch starting at index ${i}: ${JSON.stringify(error)}`;\n }\n result.errors.push(errorMessage);\n logger.error(errorMessage);\n }\n }\n\n this.reportProgress(\n 'documents',\n documents.length,\n documents.length,\n `Uploaded ${result.restored} documents`\n );\n return result;\n }\n\n /**\n * Upload attachments from filesystem to CouchDB\n */\n private async uploadAttachments(\n staticPath: string,\n documents: AggregatedDocument[],\n db: PouchDB.Database\n ): Promise<{ restored: number; errors: string[]; warnings: string[] }> {\n const result = { restored: 0, errors: [] as string[], warnings: [] as string[] };\n let processedDocs = 0;\n\n for (const doc of documents) {\n this.reportProgress(\n 'attachments',\n processedDocs,\n documents.length,\n `Processing attachments for ${doc._id}...`\n );\n processedDocs++;\n\n if (!doc._attachments) {\n continue;\n }\n\n for (const [attachmentName, attachmentMeta] of Object.entries(doc._attachments)) {\n try {\n const uploadResult = await this.uploadSingleAttachment(\n staticPath,\n doc._id,\n attachmentName,\n attachmentMeta as any,\n db\n );\n\n if (uploadResult.success) {\n result.restored++;\n } else {\n result.errors.push(uploadResult.error || 'Unknown attachment upload error');\n }\n } catch (error) {\n const errorMessage = `Failed to upload attachment ${doc._id}/${attachmentName}: ${error instanceof Error ? error.message : String(error)}`;\n result.errors.push(errorMessage);\n logger.error(errorMessage);\n }\n }\n }\n\n this.reportProgress(\n 'attachments',\n documents.length,\n documents.length,\n `Uploaded ${result.restored} attachments`\n );\n return result;\n }\n\n /**\n * Upload a single attachment file\n */\n private async uploadSingleAttachment(\n staticPath: string,\n docId: string,\n attachmentName: string,\n attachmentMeta: any,\n db: PouchDB.Database\n ): Promise<AttachmentUploadResult> {\n const result: AttachmentUploadResult = {\n success: false,\n attachmentName,\n docId,\n };\n\n try {\n // Get the file path from the attachment metadata\n if (!attachmentMeta.path) {\n result.error = 'Attachment metadata missing file path';\n return result;\n }\n\n // Load the attachment data\n let attachmentData: ArrayBuffer | Buffer;\n let attachmentPath: string;\n\n if (this.fs) {\n // Use injected file system adapter (preferred)\n attachmentPath = this.fs.joinPath(staticPath, attachmentMeta.path);\n attachmentData = await this.fs.readBinary(attachmentPath);\n } else {\n // Fallback to legacy behavior for backward compatibility\n attachmentPath =\n nodeFS && nodePath\n ? nodePath.join(staticPath, attachmentMeta.path)\n : `${staticPath}/${attachmentMeta.path}`;\n\n if (nodeFS && this.isLocalPath(staticPath)) {\n // Node.js file system access\n attachmentData = await nodeFS.promises.readFile(attachmentPath);\n } else {\n // Browser/fetch access\n const response = await fetch(attachmentPath);\n if (!response.ok) {\n result.error = `Failed to fetch attachment: ${response.status} ${response.statusText}`;\n return result;\n }\n attachmentData = await response.arrayBuffer();\n }\n }\n\n // Get current document revision (needed for putAttachment)\n const doc = await db.get(docId);\n \n // Upload to CouchDB\n await db.putAttachment(\n docId,\n attachmentName,\n doc._rev,\n attachmentData as any, // PouchDB accepts both ArrayBuffer and Buffer\n attachmentMeta.content_type\n );\n\n result.success = true;\n } catch (error) {\n result.error = error instanceof Error ? error.message : String(error);\n }\n\n return result;\n }\n\n /**\n * Restore CourseConfig document from manifest\n */\n private async restoreCourseConfig(\n manifest: StaticCourseManifest,\n targetDB: PouchDB.Database\n ): Promise<RestoreResults> {\n const results: RestoreResults = {\n restored: 0,\n errors: [],\n warnings: [],\n };\n\n try {\n // Validate courseConfig exists\n if (!manifest.courseConfig) {\n results.warnings.push(\n 'No courseConfig found in manifest, skipping CourseConfig document creation'\n );\n return results;\n }\n\n // Create CourseConfig document\n const courseConfigDoc: { [key: string]: any; _id: string; _rev?: string } = {\n _id: 'CourseConfig',\n ...manifest.courseConfig,\n courseID: manifest.courseId,\n };\n delete courseConfigDoc._rev;\n\n // Upload to CouchDB\n await targetDB.put(courseConfigDoc);\n results.restored = 1;\n\n logger.info(`CourseConfig document created for course: ${manifest.courseId}`);\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : JSON.stringify(error);\n results.errors.push(`Failed to restore CourseConfig: ${errorMessage}`);\n logger.error('CourseConfig restoration failed:', error);\n }\n\n return results;\n }\n\n /**\n * Calculate expected document counts from manifest\n */\n private calculateExpectedCounts(manifest: StaticCourseManifest): DocumentCounts {\n const counts: DocumentCounts = {};\n\n // Count documents by type from chunks\n for (const chunk of manifest.chunks) {\n counts[chunk.docType] = (counts[chunk.docType] || 0) + chunk.documentCount;\n }\n\n // Count design documents\n if (manifest.designDocs.length > 0) {\n counts['_design'] = manifest.designDocs.length;\n }\n\n return counts;\n }\n\n /**\n * Clean up database after failed migration\n */\n private async cleanupFailedMigration(db: PouchDB.Database): Promise<void> {\n logger.info('Cleaning up failed migration...');\n\n try {\n // Get all documents and delete them\n const allDocs = await db.allDocs();\n const docsToDelete = allDocs.rows.map((row) => ({\n _id: row.id,\n _rev: row.value.rev,\n _deleted: true,\n }));\n\n if (docsToDelete.length > 0) {\n await db.bulkDocs(docsToDelete);\n logger.info(`Cleaned up ${docsToDelete.length} documents from failed migration`);\n }\n } catch (error) {\n logger.error('Failed to cleanup documents:', error);\n throw error;\n }\n }\n\n /**\n * Report progress to callback if available\n */\n private reportProgress(\n phase: RestoreProgress['phase'],\n current: number,\n total: number,\n message: string\n ): void {\n if (this.progressCallback) {\n this.progressCallback({\n phase,\n current,\n total,\n message,\n });\n }\n }\n\n /**\n * Check if a path is a local file path (vs URL)\n */\n private isLocalPath(path: string): boolean {\n return !path.startsWith('http://') && !path.startsWith('https://');\n }\n}\n","// packages/db/src/util/migrator/types.ts\n\nexport interface MigrationOptions {\n chunkBatchSize: number;\n validateRoundTrip: boolean;\n cleanupOnFailure: boolean;\n timeout: number; // milliseconds\n}\n\nexport interface MigrationResult {\n success: boolean;\n documentsRestored: number;\n attachmentsRestored: number;\n designDocsRestored: number;\n courseConfigRestored: number;\n errors: string[];\n warnings: string[];\n migrationTime: number;\n tempDatabaseName?: string;\n}\n\nexport interface ValidationResult {\n valid: boolean;\n documentCountMatch: boolean;\n attachmentIntegrity: boolean;\n viewFunctionality: boolean;\n issues: ValidationIssue[];\n}\n\nexport interface ValidationIssue {\n type: 'error' | 'warning';\n category: 'documents' | 'attachments' | 'views' | 'metadata' | 'course_config';\n message: string;\n details?: any;\n}\n\nexport interface DocumentCounts {\n [docType: string]: number;\n}\n\nexport interface RestoreProgress {\n phase: 'manifest' | 'design_docs' | 'course_config' | 'documents' | 'attachments' | 'validation';\n current: number;\n total: number;\n message: string;\n}\n\nexport interface StaticCourseValidation {\n valid: boolean;\n manifestExists: boolean;\n chunksExist: boolean;\n attachmentsExist: boolean;\n errors: string[];\n warnings: string[];\n courseId?: string;\n courseName?: string;\n}\n\nexport interface AggregatedDocument {\n _id: string;\n _attachments?: Record<string, any>;\n docType: string;\n [key: string]: any;\n}\n\nexport interface RestoreResults {\n restored: number;\n errors: string[];\n warnings: string[];\n}\n\nexport interface AttachmentUploadResult {\n success: boolean;\n attachmentName: string;\n docId: string;\n error?: string;\n}\n\nexport const DEFAULT_MIGRATION_OPTIONS: MigrationOptions = {\n chunkBatchSize: 100,\n validateRoundTrip: false,\n cleanupOnFailure: true,\n timeout: 300000, // 5 minutes\n};\n","// packages/db/src/util/migrator/validation.ts\n\nimport { logger } from '../logger';\nimport { StaticCourseValidation, ValidationResult, DocumentCounts, ValidationIssue } from './types';\nimport { StaticCourseManifest } from '../packer/types';\nimport { FileSystemAdapter, FileSystemError } from './FileSystemAdapter';\n\n// Check if we're in Node.js environment and fs is available\nlet nodeFS: any = null;\ntry {\n if (typeof window === 'undefined' && typeof process !== 'undefined' && process.versions?.node) {\n nodeFS = eval('require')('fs');\n nodeFS.promises = nodeFS.promises || eval('require')('fs').promises;\n }\n} catch {\n // fs not available\n}\n\n/**\n * Validate that a static course directory contains all required files\n */\nexport async function validateStaticCourse(\n staticPath: string,\n fs?: FileSystemAdapter\n): Promise<StaticCourseValidation> {\n const validation: StaticCourseValidation = {\n valid: true,\n manifestExists: false,\n chunksExist: false,\n attachmentsExist: false,\n errors: [],\n warnings: [],\n };\n\n try {\n // Check if path exists and is directory\n if (fs) {\n // Use injected file system adapter (preferred)\n const stats = await fs.stat(staticPath);\n if (!stats.isDirectory()) {\n validation.errors.push(`Path is not a directory: ${staticPath}`);\n validation.valid = false;\n return validation;\n }\n } else if (!nodeFS) {\n // Fallback validation failed\n validation.errors.push('File system access not available - validation skipped');\n validation.valid = false;\n return validation;\n } else {\n // Legacy fallback\n const stats = await nodeFS.promises.stat(staticPath);\n if (!stats.isDirectory()) {\n validation.errors.push(`Path is not a directory: ${staticPath}`);\n validation.valid = false;\n return validation;\n }\n }\n\n // Check for manifest.json\n let manifestPath: string = `${staticPath}/manifest.json`;\n try {\n if (fs) {\n // Use injected file system adapter (preferred)\n manifestPath = fs.joinPath(staticPath, 'manifest.json');\n if (await fs.exists(manifestPath)) {\n validation.manifestExists = true;\n\n // Parse manifest to get course info\n const manifestContent = await fs.readFile(manifestPath);\n const manifest: StaticCourseManifest = JSON.parse(manifestContent);\n validation.courseId = manifest.courseId;\n validation.courseName = manifest.courseName;\n\n // Validate manifest structure\n if (\n !manifest.version ||\n !manifest.courseId ||\n !manifest.chunks ||\n !Array.isArray(manifest.chunks)\n ) {\n validation.errors.push('Invalid manifest structure');\n validation.valid = false;\n }\n } else {\n validation.errors.push(`Manifest not found: ${manifestPath}`);\n validation.valid = false;\n }\n } else {\n // Legacy fallback\n manifestPath = `${staticPath}/manifest.json`;\n await nodeFS.promises.access(manifestPath);\n validation.manifestExists = true;\n\n // Parse manifest to get course info\n const manifestContent = await nodeFS.promises.readFile(manifestPath, 'utf8');\n const manifest: StaticCourseManifest = JSON.parse(manifestContent);\n validation.courseId = manifest.courseId;\n validation.courseName = manifest.courseName;\n\n // Validate manifest structure\n if (\n !manifest.version ||\n !manifest.courseId ||\n !manifest.chunks ||\n !Array.isArray(manifest.chunks)\n ) {\n validation.errors.push('Invalid manifest structure');\n validation.valid = false;\n }\n }\n } catch (error) {\n const errorMessage =\n error instanceof FileSystemError\n ? error.message\n : `Manifest not found or invalid: ${manifestPath}`;\n validation.errors.push(errorMessage);\n validation.valid = false;\n }\n\n // Check for chunks directory\n let chunksPath: string = `${staticPath}/chunks`;\n try {\n if (fs) {\n // Use injected file system adapter (preferred)\n chunksPath = fs.joinPath(staticPath, 'chunks');\n if (await fs.exists(chunksPath)) {\n const chunksStats = await fs.stat(chunksPath);\n if (chunksStats.isDirectory()) {\n validation.chunksExist = true;\n } else {\n validation.errors.push(`Chunks path is not a directory: ${chunksPath}`);\n validation.valid = false;\n }\n } else {\n validation.errors.push(`Chunks directory not found: ${chunksPath}`);\n validation.valid = false;\n }\n } else {\n // Legacy fallback\n chunksPath = `${staticPath}/chunks`;\n const chunksStats = await nodeFS.promises.stat(chunksPath);\n if (chunksStats.isDirectory()) {\n validation.chunksExist = true;\n } else {\n validation.errors.push(`Chunks path is not a directory: ${chunksPath}`);\n validation.valid = false;\n }\n }\n } catch (error) {\n const errorMessage =\n error instanceof FileSystemError\n ? error.message\n : `Chunks directory not found: ${chunksPath}`;\n validation.errors.push(errorMessage);\n validation.valid = false;\n }\n\n // Check for attachments directory (optional - course might not have attachments)\n let attachmentsPath: string;\n try {\n if (fs) {\n // Use injected file system adapter (preferred)\n attachmentsPath = fs.joinPath(staticPath, 'attachments');\n if (await fs.exists(attachmentsPath)) {\n const attachmentsStats = await fs.stat(attachmentsPath);\n if (attachmentsStats.isDirectory()) {\n validation.attachmentsExist = true;\n }\n } else {\n // Attachments directory is optional\n validation.warnings.push(\n `Attachments directory not found: ${attachmentsPath} (this is OK if course has no attachments)`\n );\n }\n } else {\n // Legacy fallback\n attachmentsPath = `${staticPath}/attachments`;\n const attachmentsStats = await nodeFS.promises.stat(attachmentsPath);\n if (attachmentsStats.isDirectory()) {\n validation.attachmentsExist = true;\n }\n }\n } catch (error) {\n // Attachments directory is optional\n attachmentsPath = attachmentsPath! || `${staticPath}/attachments`;\n const warningMessage =\n error instanceof FileSystemError\n ? error.message\n : `Attachments directory not found: ${attachmentsPath} (this is OK if course has no attachments)`;\n validation.warnings.push(warningMessage);\n }\n } catch (error) {\n validation.errors.push(\n `Failed to validate static course: ${error instanceof Error ? error.message : String(error)}`\n );\n validation.valid = false;\n }\n\n return validation;\n}\n\n/**\n * Validate the result of a migration by checking document counts and integrity\n */\nexport async function validateMigration(\n targetDB: PouchDB.Database,\n expectedCounts: DocumentCounts,\n manifest: StaticCourseManifest\n): Promise<ValidationResult> {\n const validation: ValidationResult = {\n valid: true,\n documentCountMatch: false,\n attachmentIntegrity: false,\n viewFunctionality: false,\n issues: [],\n };\n\n try {\n logger.info('Starting migration validation...');\n\n // 1. Validate document counts\n const actualCounts = await getActualDocumentCounts(targetDB);\n validation.documentCountMatch = compareDocumentCounts(\n expectedCounts,\n actualCounts,\n validation.issues\n );\n\n // 2. Validate CourseConfig document\n await validateCourseConfig(targetDB, manifest, validation.issues);\n\n // 3. Validate design documents and views\n validation.viewFunctionality = await validateViews(targetDB, manifest, validation.issues);\n\n // 4. Validate attachment integrity (sample check)\n validation.attachmentIntegrity = await validateAttachmentIntegrity(targetDB, validation.issues);\n\n // Overall validation result\n validation.valid =\n validation.documentCountMatch &&\n validation.viewFunctionality &&\n validation.attachmentIntegrity;\n\n logger.info(`Migration validation completed. Valid: ${validation.valid}`);\n if (validation.issues.length > 0) {\n logger.info(`Validation issues: ${validation.issues.length}`);\n validation.issues.forEach((issue) => {\n if (issue.type === 'error') {\n logger.error(`${issue.category}: ${issue.message}`);\n } else {\n logger.warn(`${issue.category}: ${issue.message}`);\n }\n });\n }\n } catch (error) {\n validation.valid = false;\n validation.issues.push({\n type: 'error',\n category: 'metadata',\n message: `Validation failed: ${error instanceof Error ? error.message : String(error)}`,\n });\n }\n\n return validation;\n}\n\n/**\n * Get actual document counts by type from the database\n */\nasync function getActualDocumentCounts(db: PouchDB.Database): Promise<DocumentCounts> {\n const counts: DocumentCounts = {};\n\n try {\n const allDocs = await db.allDocs({ include_docs: true });\n\n for (const row of allDocs.rows) {\n if (row.id.startsWith('_design/')) {\n // Count design documents separately\n counts['_design'] = (counts['_design'] || 0) + 1;\n continue;\n }\n\n const doc = row.doc as any;\n if (doc && doc.docType) {\n counts[doc.docType] = (counts[doc.docType] || 0) + 1;\n } else {\n // Documents without docType\n counts['unknown'] = (counts['unknown'] || 0) + 1;\n }\n }\n } catch (error) {\n logger.error('Failed to get actual document counts:', error);\n }\n\n return counts;\n}\n\n/**\n * Compare expected vs actual document counts\n */\nfunction compareDocumentCounts(\n expected: DocumentCounts,\n actual: DocumentCounts,\n issues: ValidationIssue[]\n): boolean {\n let countsMatch = true;\n\n // Check each expected document type\n for (const [docType, expectedCount] of Object.entries(expected)) {\n const actualCount = actual[docType] || 0;\n\n if (actualCount !== expectedCount) {\n countsMatch = false;\n issues.push({\n type: 'error',\n category: 'documents',\n message: `Document count mismatch for ${docType}: expected ${expectedCount}, got ${actualCount}`,\n });\n }\n }\n\n // Check for unexpected document types\n for (const [docType, actualCount] of Object.entries(actual)) {\n if (!expected[docType] && docType !== '_design') {\n issues.push({\n type: 'warning',\n category: 'documents',\n message: `Unexpected document type found: ${docType} (${actualCount} documents)`,\n });\n }\n }\n\n return countsMatch;\n}\n\n/**\n * Validate that CourseConfig document exists and is properly structured\n */\nasync function validateCourseConfig(\n db: PouchDB.Database,\n manifest: StaticCourseManifest,\n issues: ValidationIssue[]\n): Promise<void> {\n try {\n // Check if CourseConfig document exists\n const courseConfig = await db.get('CourseConfig');\n if (!courseConfig) {\n issues.push({\n type: 'error',\n category: 'course_config',\n message: 'CourseConfig document not found after migration',\n });\n return;\n }\n\n // Verify courseID field is present\n if (!(courseConfig as any).courseID) {\n issues.push({\n type: 'warning',\n category: 'course_config',\n message: 'CourseConfig document missing courseID field',\n });\n }\n\n // Verify courseID matches manifest\n if ((courseConfig as any).courseID !== manifest.courseId) {\n issues.push({\n type: 'warning',\n category: 'course_config',\n message: `CourseConfig courseID mismatch: expected ${manifest.courseId}, got ${(courseConfig as any).courseID}`,\n });\n }\n\n logger.debug('CourseConfig document validation passed');\n } catch (error) {\n if ((error as any).status === 404) {\n issues.push({\n type: 'error',\n category: 'course_config',\n message: 'CourseConfig document not found in database',\n });\n } else {\n issues.push({\n type: 'error',\n category: 'course_config',\n message: `Failed to validate CourseConfig document: ${error instanceof Error ? error.message : String(error)}`,\n });\n }\n }\n}\n\n/**\n * Validate that design documents and views are working correctly\n */\nasync function validateViews(\n db: PouchDB.Database,\n manifest: StaticCourseManifest,\n issues: ValidationIssue[]\n): Promise<boolean> {\n let viewsValid = true;\n\n try {\n // Check that design documents exist\n for (const designDoc of manifest.designDocs) {\n try {\n const doc = await db.get(designDoc._id);\n if (!doc) {\n viewsValid = false;\n issues.push({\n type: 'error',\n category: 'views',\n message: `Design document not found: ${designDoc._id}`,\n });\n continue;\n }\n\n // Test each view in the design document\n for (const viewName of Object.keys(designDoc.views)) {\n try {\n const viewPath = `${designDoc._id}/${viewName}`;\n await db.query(viewPath, { limit: 1 });\n // If we get here, the view is accessible (even if it returns no results)\n } catch (viewError) {\n viewsValid = false;\n issues.push({\n type: 'error',\n category: 'views',\n message: `View not accessible: ${designDoc._id}/${viewName} - ${viewError}`,\n });\n }\n }\n } catch (error) {\n viewsValid = false;\n issues.push({\n type: 'error',\n category: 'views',\n message: `Failed to validate design document ${designDoc._id}: ${error}`,\n });\n }\n }\n } catch (error) {\n viewsValid = false;\n issues.push({\n type: 'error',\n category: 'views',\n message: `View validation failed: ${error instanceof Error ? error.message : String(error)}`,\n });\n }\n\n return viewsValid;\n}\n\n/**\n * Validate attachment integrity by checking a sample of attachments\n */\nasync function validateAttachmentIntegrity(\n db: PouchDB.Database,\n issues: ValidationIssue[]\n): Promise<boolean> {\n let attachmentsValid = true;\n\n try {\n // Get documents with attachments (sample check)\n const allDocs = await db.allDocs({\n include_docs: true,\n limit: 10, // Sample first 10 documents for performance\n });\n\n let attachmentCount = 0;\n let validAttachments = 0;\n\n for (const row of allDocs.rows) {\n const doc = row.doc as any;\n if (doc && doc._attachments) {\n for (const [attachmentName, _attachmentMeta] of Object.entries(doc._attachments)) {\n attachmentCount++;\n\n try {\n // Try to access the attachment\n const attachment = await db.getAttachment(doc._id, attachmentName);\n if (attachment) {\n validAttachments++;\n }\n } catch (attachmentError) {\n attachmentsValid = false;\n issues.push({\n type: 'error',\n category: 'attachments',\n message: `Attachment not accessible: ${doc._id}/${attachmentName} - ${attachmentError}`,\n });\n }\n }\n }\n }\n\n if (attachmentCount === 0) {\n // No attachments found - this is OK\n issues.push({\n type: 'warning',\n category: 'attachments',\n message: 'No attachments found in sampled documents',\n });\n } else {\n logger.info(`Validated ${validAttachments}/${attachmentCount} sampled attachments`);\n }\n } catch (error) {\n attachmentsValid = false;\n issues.push({\n type: 'error',\n category: 'attachments',\n message: `Attachment validation failed: ${error instanceof Error ? error.message : String(error)}`,\n });\n }\n\n return attachmentsValid;\n}\n","// packages/db/src/util/migrator/FileSystemAdapter.ts\n\n/**\n * Abstraction for file system operations needed by the migrator.\n * This allows dependency injection of file system operations,\n * avoiding bundling issues with Node.js fs module.\n */\nexport interface FileSystemAdapter {\n /**\n * Read a text file and return its contents as a string\n */\n readFile(filePath: string): Promise<string>;\n\n /**\n * Read a binary file and return its contents as a Buffer\n */\n readBinary(filePath: string): Promise<Buffer>;\n\n /**\n * Check if a file or directory exists\n */\n exists(filePath: string): Promise<boolean>;\n\n /**\n * Get file/directory statistics\n */\n stat(filePath: string): Promise<FileStats>;\n\n /**\n * Write text data to a file\n */\n writeFile(filePath: string, data: string | Buffer): Promise<void>;\n\n /**\n * Write JSON data to a file with formatting\n */\n writeJson(filePath: string, data: any, options?: { spaces?: number }): Promise<void>;\n\n /**\n * Ensure a directory exists, creating it and parent directories if needed\n */\n ensureDir(dirPath: string): Promise<void>;\n\n /**\n * Join path segments into a complete path\n */\n joinPath(...segments: string[]): string;\n\n /**\n * Get the directory name of a path\n */\n dirname(filePath: string): string;\n\n /**\n * Check if a path is absolute\n */\n isAbsolute(filePath: string): boolean;\n}\n\nexport interface FileStats {\n isDirectory(): boolean;\n isFile(): boolean;\n size: number;\n}\n\n/**\n * Error thrown when file system operations fail\n */\nexport class FileSystemError extends Error {\n constructor(\n message: string,\n public readonly operation: string,\n public readonly filePath: string,\n public readonly cause?: Error\n ) {\n super(message);\n this.name = 'FileSystemError';\n }\n}","export * from './SessionController';\nexport * from './SpacedRepetition';\nexport * from './TagFilteredContentSource';\n"],"mappings":"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","names":["DocType","logger","fs","path","logger","log","moment","AggregationMode","toCourseElo","toCourseElo","types_exports","init_types","toCourseElo","toCourseElo","DEFAULT_MIN_COUNT","moment","Navigators","NavigatorRole","blankCourseElo","toCourseElo","filterAllDocsByPrefix","getCourseDB","moment","init_classroomDB","getStartAndEndKeys","REVIEW_TIME_FORMAT","getCourseDB","init_adminDB","init_classroomDB","getStartAndEndKeys","fetch","Status","log","fetch","moment","process","isNodeEnvironment","getCourseDB","filterAllDocsByPrefix","getStartAndEndKeys","REVIEW_TIME_FORMAT","init_adminDB","init_classroomDB","Status","moment","uuid","log","record","init_adminDB","init_classroomDB","isNodeEnvironment","path","Status","init_courseDB","elo","init_courseDB","CouchDBSyncStrategy","CouchDataLayerProvider","StaticDataLayerProvider","hasActiveFilter","init_classroomDB","init_courseDB","init_courseDB","Status","init_types","init_types","moment","moment","moment","toCourseElo","toCourseElo","getCourseDB","toCourseElo","nodeFS","fs","nodeFS","path"]}
|
|
1
|
+
{"version":3,"sources":["../src/core/interfaces/adminDB.ts","../src/core/interfaces/classroomDB.ts","../src/impl/common/SyncStrategy.ts","../src/util/logger.ts","../src/core/types/types-legacy.ts","../src/core/util/index.ts","../src/impl/couch/pouchdb-setup.ts","../src/util/dataDirectory.ts","../src/impl/common/userDBHelpers.ts","../src/util/Loggable.ts","../src/impl/couch/updateQueue.ts","../src/impl/couch/user-course-relDB.ts","../src/impl/couch/clientCache.ts","../src/impl/couch/courseAPI.ts","../src/impl/couch/courseLookupDB.ts","../src/core/navigators/index.ts","../src/core/navigators/Pipeline.ts","../src/core/navigators/generators/CompositeGenerator.ts","../src/core/navigators/PipelineAssembler.ts","../src/core/navigators/generators/elo.ts","../src/core/navigators/generators/srs.ts","../src/core/navigators/filters/eloDistance.ts","../src/core/navigators/defaults.ts","../src/impl/couch/courseDB.ts","../src/impl/couch/classroomDB.ts","../src/impl/couch/adminDB.ts","../src/impl/couch/auth.ts","../src/impl/couch/CouchDBSyncStrategy.ts","../src/impl/couch/index.ts","../src/impl/common/BaseUserDB.ts","../src/impl/common/index.ts","../src/impl/couch/PouchDataLayerProvider.ts","../src/impl/static/StaticDataUnpacker.ts","../src/impl/static/courseDB.ts","../src/impl/static/coursesDB.ts","../src/impl/static/NoOpSyncStrategy.ts","../src/impl/static/StaticDataLayerProvider.ts","../src/factory.ts","../src/study/TagFilteredContentSource.ts","../src/core/interfaces/contentSource.ts","../src/core/interfaces/courseDB.ts","../src/core/interfaces/dataLayerProvider.ts","../src/core/interfaces/userDB.ts","../src/core/interfaces/index.ts","../src/core/types/user.ts","../src/core/types/strategyState.ts","../src/core/bulkImport/cardProcessor.ts","../src/core/bulkImport/types.ts","../src/core/bulkImport/index.ts","../src/core/index.ts","../src/index.ts","../src/study/services/SrsService.ts","../src/study/SpacedRepetition.ts","../src/study/services/EloService.ts","../src/study/services/ResponseProcessor.ts","../src/study/services/CardHydrationService.ts","../src/study/ItemQueue.ts","../src/study/SessionController.ts","../src/util/index.ts","../src/util/packer/CouchDBToStaticPacker.ts","../src/util/migrator/StaticToCouchDBMigrator.ts","../src/util/migrator/types.ts","../src/util/migrator/validation.ts","../src/util/migrator/FileSystemAdapter.ts","../src/study/SourceMixer.ts","../src/study/index.ts"],"sourcesContent":["import { ClassroomConfig, CourseConfig } from '@vue-skuilder/common';\n\n/**\n * Admin functionality\n */\nexport interface AdminDBInterface {\n /**\n * Get all users\n */\n // eslint-disable-next-line @typescript-eslint/no-empty-object-type\n getUsers(): Promise<PouchDB.Core.Document<{}>[]>;\n\n /**\n * Get all courses\n */\n getCourses(): Promise<CourseConfig[]>;\n\n /**\n * Remove a course\n */\n removeCourse(id: string): Promise<PouchDB.Core.Response>;\n\n /**\n * Get all classrooms\n */\n getClassrooms(): Promise<(ClassroomConfig & { _id: string })[]>;\n}\n","import { ClassroomConfig } from '@vue-skuilder/common';\n\n/**\n * Classroom management\n */\nexport interface ClassroomDBInterface {\n /**\n * Get classroom config\n */\n getConfig(): ClassroomConfig;\n\n /**\n * Get assigned content\n */\n getAssignedContent(): Promise<AssignedContent[]>;\n}\n\nexport interface TeacherClassroomDBInterface extends ClassroomDBInterface {\n /**\n * For teacher interfaces: assign content\n */\n assignContent?(content: AssignedContent): Promise<boolean>;\n\n /**\n * For teacher interfaces: remove content\n */\n removeContent?(content: AssignedContent): Promise<void>;\n}\n\n/**\n * Student-facing classroom interface.\n * Content is accessed via StudyContentSource.getWeightedCards().\n */\nexport type StudentClassroomDBInterface = ClassroomDBInterface;\n\nexport type AssignedContent = AssignedCourse | AssignedTag | AssignedCard;\n\nexport interface AssignedTag extends ContentBase {\n type: 'tag';\n tagID: string;\n}\nexport interface AssignedCourse extends ContentBase {\n type: 'course';\n}\nexport interface AssignedCard extends ContentBase {\n type: 'card';\n cardID: string;\n}\n\ninterface ContentBase {\n type: 'course' | 'tag' | 'card';\n /**\n * Username of the assigning teacher.\n */\n assignedBy: string;\n /**\n * Date the content was assigned.\n */\n assignedOn: moment.Moment;\n /**\n * A 'due' date for this assigned content, for scheduling content\n * in advance. Content will not be actively pushed to students until\n * this date.\n */\n activeOn: moment.Moment;\n courseID: string;\n}\n","// packages/db/src/impl/common/SyncStrategy.ts\n\nimport type { AccountCreationResult, AuthenticationResult } from './types';\n\n/**\n * Strategy interface for handling user data synchronization\n * Different implementations handle remote sync vs local-only storage\n */\nexport interface SyncStrategy {\n /**\n * Set up the remote database for a user\n * @param username The username to set up remote DB for\n * @returns PouchDB database instance (may be same as local for no-op)\n */\n setupRemoteDB(username: string): PouchDB.Database;\n\n /**\n * Get the database to use for write operations (local-first approach)\n * @param username The username to get write DB for\n * @returns PouchDB database instance for write operations\n */\n getWriteDB?(username: string): PouchDB.Database;\n\n /**\n * Start synchronization between local and remote databases\n * @param localDB The local PouchDB instance\n * @param remoteDB The remote PouchDB instance\n */\n startSync(localDB: PouchDB.Database, remoteDB: PouchDB.Database): void;\n\n /**\n * Stop synchronization (optional - for cleanup)\n */\n stopSync?(): void;\n\n /**\n * Whether this strategy supports account creation\n */\n canCreateAccount(): boolean;\n\n /**\n * Whether this strategy supports authentication\n */\n canAuthenticate(): boolean;\n\n /**\n * Create a new user account (if supported)\n * @param username The username for the new account\n * @param password The password for the new account\n */\n createAccount?(username: string, password: string): Promise<AccountCreationResult>;\n\n /**\n * Authenticate a user (if supported)\n * @param username The username to authenticate\n * @param password The password to authenticate with\n */\n authenticate?(username: string, password: string): Promise<AuthenticationResult>;\n\n /**\n * Log out the current user (if supported)\n */\n logout?(): Promise<AuthenticationResult>;\n\n /**\n * Get the current logged-in username\n * Returns the username if logged in, or a default guest username\n */\n getCurrentUsername(): Promise<string>;\n}\n\n/**\n * Base class for sync strategies with common functionality\n */\nexport abstract class BaseSyncStrategy implements SyncStrategy {\n abstract setupRemoteDB(username: string): PouchDB.Database;\n abstract startSync(localDB: PouchDB.Database, remoteDB: PouchDB.Database): void;\n abstract canCreateAccount(): boolean;\n abstract canAuthenticate(): boolean;\n abstract getCurrentUsername(): Promise<string>;\n\n stopSync?(): void {\n // Default no-op implementation\n }\n\n async createAccount(_username: string, _password: string): Promise<AccountCreationResult> {\n throw new Error('Account creation not supported by this sync strategy');\n }\n\n async authenticate(_username: string, _password: string): Promise<AuthenticationResult> {\n throw new Error('Authentication not supported by this sync strategy');\n }\n\n async logout(): Promise<AuthenticationResult> {\n throw new Error('Logout not supported by this sync strategy');\n }\n}\n","/**\n * Simple logging utility for @vue-skuilder/db package\n *\n * This utility provides environment-aware logging with ESLint suppressions\n * to resolve console statement violations while maintaining logging functionality.\n */\n\nconst isDevelopment = typeof process !== 'undefined' && process.env.NODE_ENV === 'development';\n\nexport const logger = {\n /**\n * Debug-level logging - only shown in development\n */\n debug: (message: string, ...args: any[]): void => {\n if (isDevelopment) {\n // eslint-disable-next-line no-console\n console.debug(`[DB:DEBUG] ${message}`, ...args);\n }\n },\n\n /**\n * Info-level logging - general information\n */\n info: (message: string, ...args: any[]): void => {\n // eslint-disable-next-line no-console\n console.info(`[DB:INFO] ${message}`, ...args);\n },\n\n /**\n * Warning-level logging - potential issues\n */\n warn: (message: string, ...args: any[]): void => {\n // eslint-disable-next-line no-console\n console.warn(`[DB:WARN] ${message}`, ...args);\n },\n\n /**\n * Error-level logging - serious problems\n */\n error: (message: string, ...args: any[]): void => {\n // eslint-disable-next-line no-console\n console.error(`[DB:ERROR] ${message}`, ...args);\n },\n\n /**\n * Log function for backward compatibility with existing log() usage\n */\n log: (message: string, ...args: any[]): void => {\n if (isDevelopment) {\n // eslint-disable-next-line no-console\n console.log(`[DB:LOG] ${message}`, ...args);\n }\n },\n};\n","import { CourseElo, Answer, Evaluation } from '@vue-skuilder/common';\nimport { Moment } from 'moment';\nimport { logger } from '../../util/logger';\n\nexport const GuestUsername: string = 'sk-guest-';\n\nexport const log = (message: string): void => {\n logger.log(message);\n};\n\nexport enum DocType {\n DISPLAYABLE_DATA = 'DISPLAYABLE_DATA',\n CARD = 'CARD',\n DATASHAPE = 'DATASHAPE',\n QUESTIONTYPE = 'QUESTION',\n VIEW = 'VIEW',\n PEDAGOGY = 'PEDAGOGY',\n CARDRECORD = 'CARDRECORD',\n SCHEDULED_CARD = 'SCHEDULED_CARD',\n TAG = 'TAG',\n NAVIGATION_STRATEGY = 'NAVIGATION_STRATEGY',\n STRATEGY_STATE = 'STRATEGY_STATE',\n}\n\nexport interface QualifiedCardID {\n courseID: string;\n cardID: string;\n}\n\n/**\n * Interface for all data on course content and pedagogy stored\n * in the c/pouch database.\n */\nexport interface SkuilderCourseData {\n course: string;\n docType: DocType;\n}\n\nexport interface Tag extends SkuilderCourseData {\n docType: DocType.TAG;\n name: string;\n snippet: string; // 200 char description of the tag\n wiki: string; // 3000 char md-friendly description\n taggedCards: PouchDB.Core.DocumentId[];\n author: string;\n}\nexport interface TagStub {\n name: string;\n snippet: string;\n count: number; // the number of cards that have this tag applied\n}\n\nexport interface CardData extends SkuilderCourseData {\n docType: DocType.CARD;\n id_displayable_data: PouchDB.Core.DocumentId[];\n id_view: PouchDB.Core.DocumentId;\n elo: CourseElo;\n author: string;\n}\n\n/** A list of populated courses in the DB */\nexport interface CourseListData extends PouchDB.Core.Response {\n courses: string[];\n}\n\n/**\n * The data used to hydrate viewable components (questions, info, etc)\n */\nexport interface DisplayableData extends SkuilderCourseData {\n docType: DocType.DISPLAYABLE_DATA;\n author?: string;\n id_datashape: PouchDB.Core.DocumentId;\n data: Field[];\n _attachments?: { [index: string]: PouchDB.Core.FullAttachment };\n}\n\nexport interface Field {\n data: unknown;\n name: string;\n}\n\nexport interface DataShapeData extends SkuilderCourseData {\n docType: DocType.DATASHAPE;\n _id: PouchDB.Core.DocumentId;\n questionTypes: PouchDB.Core.DocumentId[];\n}\n\nexport interface QuestionData extends SkuilderCourseData {\n docType: DocType.QUESTIONTYPE;\n _id: PouchDB.Core.DocumentId;\n viewList: string[];\n dataShapeList: PouchDB.Core.DocumentId[];\n}\n\nexport const DocTypePrefixes = {\n [DocType.CARD]: 'c',\n [DocType.DISPLAYABLE_DATA]: 'dd',\n [DocType.TAG]: 'TAG',\n [DocType.CARDRECORD]: 'cardH',\n [DocType.SCHEDULED_CARD]: 'card_review_',\n // Add other doctypes here as they get prefixed IDs\n [DocType.DATASHAPE]: 'DATASHAPE',\n [DocType.QUESTIONTYPE]: 'QUESTION',\n [DocType.VIEW]: 'VIEW',\n [DocType.PEDAGOGY]: 'PEDAGOGY',\n [DocType.NAVIGATION_STRATEGY]: 'NAVIGATION_STRATEGY',\n [DocType.STRATEGY_STATE]: 'STRATEGY_STATE',\n} as const;\n\nexport interface CardHistory<T extends CardRecord> {\n _id: PouchDB.Core.DocumentId;\n /**\n * The CouchDB id of the card\n */\n cardID: PouchDB.Core.DocumentId;\n\n /**\n * The ID of the course\n */\n courseID: string;\n\n /**\n * The to-date largest interval between successful\n * card reviews. `0` indicates no successful reviews.\n */\n bestInterval: number;\n\n /**\n * The number of times that a card has been\n * failed in review\n */\n lapses: number;\n\n /**\n * The number of consecutive successful impressions\n * on this card\n */\n streak: number;\n\n records: T[];\n}\n\nexport interface CardRecord {\n /**\n * The CouchDB id of the card\n */\n cardID: string;\n /**\n * The ID of the course\n */\n courseID: string;\n /**\n * Number of milliseconds that the user spent before dismissing\n * the card (ie, \"I've read this\" or \"here is my answer\")\n *\n * //TODO: this (sometimes?) wants to be replaced with a rich\n * recording of user activity in working the question\n */\n timeSpent: number;\n /**\n * The date-time that the card was rendered. timeStamp + timeSpent will give the\n * time of user submission.\n */\n timeStamp: Moment;\n}\n\nexport interface QuestionRecord extends CardRecord, Evaluation {\n userAnswer: Answer;\n /**\n * The number of incorrect user submissions prededing this submisstion.\n *\n * eg, if a user is asked 7*6=__, submitting 46, 48, 42 will result in three\n * records being created having 0, 1, and 2 as their recorded 'priorAttempts' values\n */\n priorAttemps: number;\n}\n","import { DocType, DocTypePrefixes, CardHistory, CardRecord, QuestionRecord } from '../types/types-legacy';\n\nexport function areQuestionRecords(h: CardHistory<CardRecord>): h is CardHistory<QuestionRecord> {\n return isQuestionRecord(h.records[0]);\n}\n\nexport function isQuestionRecord(c: CardRecord): c is QuestionRecord {\n return (c as QuestionRecord).userAnswer !== undefined;\n}\n\nexport function getCardHistoryID(courseID: string, cardID: string): PouchDB.Core.DocumentId {\n return `${DocTypePrefixes[DocType.CARDRECORD]}-${courseID}-${cardID}`;\n}\n\nexport function parseCardHistoryID(id: string): {\n courseID: string;\n cardID: string;\n} {\n const split = id.split('-');\n let error: string = '';\n error += split.length === 3 ? '' : `\\n\\tgiven ID has incorrect number of '-' characters`;\n error +=\n split[0] === DocTypePrefixes[DocType.CARDRECORD] ? '' : `\n\tgiven ID does not start with ${DocTypePrefixes[DocType.CARDRECORD]}`;\n\n if (split.length === 3 && split[0] === DocTypePrefixes[DocType.CARDRECORD]) {\n return {\n courseID: split[1],\n cardID: split[2],\n };\n } else {\n throw new Error('parseCardHistory Error:' + error);\n }\n}\n\ninterface PouchDBError extends Error {\n error?: string;\n reason?: string;\n}\n\nexport function docIsDeleted(e: PouchDBError): boolean {\n return Boolean(e?.error === 'not_found' && e?.reason === 'deleted');\n}\n","import PouchDB from 'pouchdb';\nimport PouchDBFind from 'pouchdb-find';\nimport PouchDBAuth from '@nilock2/pouchdb-authentication';\n\n// Register plugins\nPouchDB.plugin(PouchDBFind);\nPouchDB.plugin(PouchDBAuth);\n\n// Disable PouchDB debug logging to prevent interference with CLI prompts\n// Debug logging (like DerivedLogger.emit) will still go to the TUI log file\n// if initializeTuiLogging() has been called, but won't clutter terminal output\nif (typeof PouchDB.debug !== 'undefined') {\n PouchDB.debug.disable();\n}\n\n// Configure PouchDB globally\nPouchDB.defaults({\n // ajax: {\n // timeout: 60000,\n // },\n});\n\nexport default PouchDB;\n","// Cross-platform data directory utilities for PouchDB\n// Provides OS-appropriate application data directories\n\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport * as os from 'os';\nimport { logger } from './logger';\nimport { ENV } from '@db/factory';\n\n/**\n * Get the application data directory for the current platform\n * Uses ~/.tuilder as requested by user for simplicity\n */\nexport function getAppDataDirectory(): string {\n if (ENV.LOCAL_STORAGE_PREFIX) {\n return path.join(os.homedir(), `.tuilder`, ENV.LOCAL_STORAGE_PREFIX);\n } else {\n return path.join(os.homedir(), '.tuilder');\n }\n}\n\n/**\n * Ensure the application data directory exists\n * Creates directory recursively if it doesn't exist\n */\nexport async function ensureAppDataDirectory(): Promise<string> {\n const appDataDir = getAppDataDirectory();\n \n try {\n await fs.promises.mkdir(appDataDir, { recursive: true });\n } catch (err: any) {\n if (err.code !== 'EEXIST') {\n throw new Error(`Failed to create app data directory ${appDataDir}: ${err.message}`);\n }\n }\n \n return appDataDir;\n}\n\n/**\n * Get the full path for a PouchDB database file\n * @param dbName - The database name (e.g., 'userdb-Colin')\n */\nexport function getDbPath(dbName: string): string {\n return path.join(getAppDataDirectory(), dbName);\n}\n\n/**\n * Initialize data directory for PouchDB usage\n * Should be called once at application startup\n */\nexport async function initializeDataDirectory(): Promise<void> {\n await ensureAppDataDirectory();\n \n // Log initialization\n logger.info(`PouchDB data directory initialized: ${getAppDataDirectory()}`);\n}","// packages/db/src/impl/common/userDBHelpers.ts\n\nimport moment from 'moment';\nimport { DocType, DocTypePrefixes } from '@db/core';\nimport { logger } from '../../util/logger';\nimport { ScheduledCard } from '@db/core/types/user';\n\nexport const REVIEW_TIME_FORMAT: string = 'YYYY-MM-DD--kk:mm:ss-SSS';\n\nimport pouch from '../couch/pouchdb-setup';\nimport { getDbPath } from '../../util/dataDirectory';\n\nconst log = (s: any) => {\n logger.info(s);\n};\n\nexport function hexEncode(str: string): string {\n let hex: string;\n let returnStr: string = '';\n\n for (let i = 0; i < str.length; i++) {\n hex = str.charCodeAt(i).toString(16);\n returnStr += ('000' + hex).slice(3);\n }\n\n return returnStr;\n}\n\nexport function filterAllDocsByPrefix<T>(\n db: PouchDB.Database,\n prefix: string,\n opts?: PouchDB.Core.AllDocsOptions\n) {\n // see couchdb docs 6.2.2:\n // Guide to Views -> Views Collation -> String Ranges\n const options: PouchDB.Core.AllDocsWithinRangeOptions = {\n startkey: prefix,\n endkey: prefix + '\\ufff0',\n include_docs: true,\n };\n\n if (opts) {\n Object.assign(options, opts);\n }\n return db.allDocs<T>(options);\n}\n\nexport function getStartAndEndKeys(key: string): {\n startkey: string;\n endkey: string;\n} {\n return {\n startkey: key,\n endkey: key + '\\ufff0',\n };\n}\n\nexport function updateGuestAccountExpirationDate(guestDB: PouchDB.Database<object>) {\n const currentTime = moment.utc();\n const expirationDate: string = currentTime.add(2, 'months').toISOString();\n const expiryDocID: string = 'GuestAccountExpirationDate';\n\n void guestDB\n .get(expiryDocID)\n .then((doc) => {\n return guestDB.put({\n _id: expiryDocID,\n _rev: doc._rev,\n date: expirationDate,\n });\n })\n .catch(() => {\n return guestDB.put({\n _id: expiryDocID,\n date: expirationDate,\n });\n });\n}\n\n/**\n * Get local user database with appropriate adapter for environment\n */\nexport function getLocalUserDB(username: string): PouchDB.Database {\n // // Choose adapter based on environment\n //\n // Not certain of this is required. Let's let pouch's auto detection\n // handle it until we specifically know we need to intervene.\n //\n // let adapter: string;\n // if (typeof window !== 'undefined') {\n // // Browser environment - use IndexedDB\n // adapter = 'idb';\n // } else {\n // // Node.js environment (tests) - use memory adapter\n // adapter = 'memory';\n // }\n\n const dbName = `userdb-${username}`;\n \n // Use proper data directory in Node.js, browser will use IndexedDB\n if (typeof window === 'undefined') {\n // Node.js environment - use filesystem with proper app data directory\n return new pouch(getDbPath(dbName), {});\n } else {\n // Browser environment - use default (IndexedDB)\n return new pouch(dbName, {});\n }\n}\n\n/**\n * Schedule a card review (strategy-agnostic version)\n */\nexport function scheduleCardReviewLocal(\n userDB: PouchDB.Database,\n review: {\n card_id: PouchDB.Core.DocumentId;\n time: moment.Moment;\n course_id: string;\n scheduledFor: ScheduledCard['scheduledFor'];\n schedulingAgentId: ScheduledCard['schedulingAgentId'];\n }\n) {\n const now = moment.utc();\n logger.info(`Scheduling for review in: ${review.time.diff(now, 'h') / 24} days`);\n void userDB.put<ScheduledCard>({\n _id: DocTypePrefixes[DocType.SCHEDULED_CARD] + review.time.format(REVIEW_TIME_FORMAT),\n cardId: review.card_id,\n reviewTime: review.time.toISOString(),\n courseId: review.course_id,\n scheduledAt: now.toISOString(),\n scheduledFor: review.scheduledFor,\n schedulingAgentId: review.schedulingAgentId,\n });\n}\n\n/**\n * Remove a scheduled card review (strategy-agnostic version)\n */\nexport async function removeScheduledCardReviewLocal(\n userDB: PouchDB.Database,\n reviewDocID: string\n) {\n const reviewDoc = await userDB.get(reviewDocID);\n userDB\n .remove(reviewDoc)\n .then((res) => {\n if (res.ok) {\n log(`Removed Review Doc: ${reviewDocID}`);\n }\n })\n .catch((err) => {\n log(`Failed to remove Review Doc: ${reviewDocID},\\n${JSON.stringify(err)}`);\n });\n}\n","export abstract class Loggable {\n protected abstract readonly _className: string;\n protected log(...args: unknown[]): void {\n // eslint-disable-next-line no-console\n console.log(`LOG-${this._className}@${new Date()}:`, ...args);\n }\n protected error(...args: unknown[]): void {\n // eslint-disable-next-line no-console\n console.error(`ERROR-${this._className}@${new Date()}:`, ...args);\n }\n}\n","import { Loggable } from '../../util/Loggable';\nimport { logger } from '../../util/logger';\n\nexport type Update<T> = Partial<T> | ((x: T) => T);\n\nexport default class UpdateQueue extends Loggable {\n _className: string = 'UpdateQueue';\n private pendingUpdates: {\n [index: string]: Update<unknown>[];\n } = {};\n private inprogressUpdates: {\n [index: string]: boolean;\n } = {};\n\n private readDB: PouchDB.Database; // Database for read operations\n private writeDB: PouchDB.Database; // Database for write operations (local-first)\n\n /**\n * Queues an update for a document and applies it with conflict resolution.\n *\n * @param id - Document ID to update\n * @param update - Partial object or function that transforms the document\n * @returns Promise resolving to the updated document\n *\n * @throws {PouchError} with status 404 if document doesn't exist\n *\n * @remarks\n * **Error Handling Pattern:**\n * - This method does NOT create documents if they don't exist\n * - Callers are responsible for handling 404 errors and creating documents\n * - This design maintains separation of concerns (UpdateQueue handles conflicts, callers handle lifecycle)\n *\n * @example\n * ```typescript\n * try {\n * await updateQueue.update(docId, (doc) => ({ ...doc, field: newValue }));\n * } catch (e) {\n * if ((e as PouchError).status === 404) {\n * // Create the document with initial values\n * await db.put({ _id: docId, field: newValue, ...initialFields });\n * }\n * }\n * ```\n */\n public update<T extends PouchDB.Core.Document<object>>(\n id: PouchDB.Core.DocumentId,\n update: Update<T>\n ) {\n logger.debug(`Update requested on doc: ${id}`);\n if (this.pendingUpdates[id]) {\n this.pendingUpdates[id].push(update);\n } else {\n this.pendingUpdates[id] = [update];\n }\n return this.applyUpdates<T>(id);\n }\n\n constructor(readDB: PouchDB.Database, writeDB?: PouchDB.Database) {\n super();\n // PouchDB.debug.enable('*');\n this.readDB = readDB;\n this.writeDB = writeDB || readDB; // Default to readDB if writeDB not provided\n logger.debug(`UpdateQ initialized...`);\n void this.readDB.info().then((i) => {\n logger.debug(`db info: ${JSON.stringify(i)}`);\n });\n }\n\n private async applyUpdates<T extends PouchDB.Core.Document<object>>(\n id: string\n ): Promise<T & PouchDB.Core.GetMeta & PouchDB.Core.RevisionIdMeta> {\n logger.debug(`Applying updates on doc: ${id}`);\n if (this.inprogressUpdates[id]) {\n // Poll instead of recursing to avoid infinite recursion\n while (this.inprogressUpdates[id]) {\n await new Promise(resolve => setTimeout(resolve, Math.random() * 50));\n }\n return this.applyUpdates<T>(id);\n } else {\n if (this.pendingUpdates[id] && this.pendingUpdates[id].length > 0) {\n this.inprogressUpdates[id] = true;\n\n const MAX_RETRIES = 5;\n for (let i = 0; i < MAX_RETRIES; i++) {\n try {\n const doc = await this.readDB.get<T>(id);\n\n // Create a new doc object to apply updates to for this attempt\n let updatedDoc = { ...doc };\n\n // Note: This loop is not fully safe if updates are functions that depend on a specific doc state\n // that might change between retries. But for simple object merges, it's okay.\n const updatesToApply = [...this.pendingUpdates[id]];\n for (const update of updatesToApply) {\n if (typeof update === 'function') {\n updatedDoc = { ...updatedDoc, ...update(updatedDoc) };\n } else {\n updatedDoc = {\n ...updatedDoc,\n ...update,\n };\n }\n }\n\n await this.writeDB.put<T>(updatedDoc);\n\n // Success! Remove the updates we just applied.\n this.pendingUpdates[id].splice(0, updatesToApply.length);\n\n if (this.pendingUpdates[id].length === 0) {\n this.inprogressUpdates[id] = false;\n delete this.inprogressUpdates[id];\n } else {\n // More updates came in, run again.\n return this.applyUpdates<T>(id);\n }\n return updatedDoc as any; // success, exit loop and function\n } catch (e: any) {\n if (e.name === 'conflict' && i < MAX_RETRIES - 1) {\n logger.warn(`Conflict on update for doc ${id}, retry #${i + 1}`);\n await new Promise((res) => setTimeout(res, 50 * Math.random()));\n // continue to next iteration of the loop\n } else if (e.name === 'not_found' && i === 0) {\n // Document not present - throw to caller for initialization\n logger.warn(`Update failed for ${id} - does not exist. Throwing to caller.`);\n delete this.inprogressUpdates[id];\n throw e; // Let caller handle\n } else {\n // Max retries reached or a non-conflict error\n delete this.inprogressUpdates[id];\n if (this.pendingUpdates[id]) {\n delete this.pendingUpdates[id];\n }\n logger.error(`Error on attemped update (retry ${i}): ${JSON.stringify(e)}`);\n throw e; // Let caller handle\n }\n }\n }\n // This should be unreachable, but it satisfies the compiler that a value is always returned or an error thrown.\n throw new Error(`UpdateQueue failed for doc ${id} after ${MAX_RETRIES} retries.`);\n } else {\n throw new Error(`Empty Updates Queue Triggered`);\n }\n }\n }\n}\n","import {\n ScheduledCard,\n UserCourseSetting,\n UserCourseSettings,\n UsrCrsDataInterface,\n} from '@db/core';\n\nimport moment, { Moment } from 'moment';\n\nimport { UserDBInterface } from '@db/core';\nimport { logger } from '../../util/logger';\n\nexport class UsrCrsData implements UsrCrsDataInterface {\n private user: UserDBInterface;\n private _courseId: string;\n\n constructor(user: UserDBInterface, courseId: string) {\n this.user = user;\n this._courseId = courseId;\n }\n\n public async getReviewsForcast(daysCount: number) {\n const time = moment.utc().add(daysCount, 'days');\n return this.getReviewstoDate(time);\n }\n\n public async getPendingReviews() {\n const now = moment.utc();\n return this.getReviewstoDate(now);\n }\n\n public async getScheduledReviewCount(): Promise<number> {\n return (await this.getPendingReviews()).length;\n }\n\n public async getCourseSettings(): Promise<UserCourseSettings> {\n const regDoc = await this.user.getCourseRegistrationsDoc();\n const crsDoc = regDoc.courses.find((c) => c.courseID === this._courseId);\n\n if (crsDoc && crsDoc.settings) {\n return crsDoc.settings;\n } else {\n logger.warn(`no settings found during lookup on course ${this._courseId}`);\n return {};\n }\n }\n public updateCourseSettings(updates: UserCourseSetting[]): void {\n // TODO: Add updateCourseSettings method to UserDBInterface\n // For now, we'll need to cast to access the concrete implementation\n if ('updateCourseSettings' in this.user) {\n void (this.user as any).updateCourseSettings(this._courseId, updates);\n }\n }\n\n private async getReviewstoDate(targetDate: Moment) {\n // Use the interface method instead of direct database access\n const allReviews = await this.user.getPendingReviews(this._courseId);\n\n logger.debug(\n `Fetching ${this.user.getUsername()}'s scheduled reviews for course ${this._courseId}.`\n );\n\n return allReviews.filter((review: ScheduledCard) => {\n const reviewTime = moment.utc(review.reviewTime);\n return targetDate.isAfter(reviewTime);\n });\n }\n}\n","// todo: something good here instead\n\nconst CLIENT_CACHE: {\n [k: string]: unknown;\n} = {};\n\nexport async function GET_CACHED<K>(k: string, f?: (x: string) => Promise<K>): Promise<K> {\n if (CLIENT_CACHE[k]) {\n // console.log('returning a cached item');\n return CLIENT_CACHE[k] as K;\n }\n\n CLIENT_CACHE[k] = f ? await f(k) : await GET_ITEM(k);\n return GET_CACHED(k);\n}\n\nasync function GET_ITEM(k: string): Promise<unknown> {\n throw new Error(`No implementation found for GET_CACHED(${k})`);\n}\n","import pouch from './pouchdb-setup';\nimport { createPouchDBConfig } from '.';\nimport { ENV } from '@db/factory';\n// import { DataShape } from '../..base-course/Interfaces/DataShape';\nimport { NameSpacer, ShapeDescriptor } from '@vue-skuilder/common';\nimport { CourseConfig, DataShape } from '@vue-skuilder/common';\nimport { CourseElo, blankCourseElo, toCourseElo } from '@vue-skuilder/common';\nimport { CourseDB, createTag } from './courseDB';\nimport { CardData, DisplayableData, DocType, Tag, DocTypePrefixes } from '../../core/types/types-legacy';\nimport { prepareNote55 } from '@vue-skuilder/common';\nimport { BaseUser } from '../common';\nimport { logger } from '@db/util/logger';\nimport { v4 as uuidv4 } from 'uuid';\n\n/**\n *\n * @param courseID id of the course (quilt) being added to\n * @param codeCourse\n * @param shape\n * @param data the datashape data - data required for this shape\n * @param author\n * @param uploads optional additional media uploads: img0, img1, ..., aud0, aud1,...\n * @returns\n */\nexport async function addNote55(\n courseID: string,\n codeCourse: string,\n shape: DataShape,\n data: unknown,\n author: string,\n tags: string[],\n uploads?: { [x: string]: PouchDB.Core.FullAttachment },\n elo: CourseElo = blankCourseElo()\n): Promise<PouchDB.Core.Response> {\n const db = getCourseDB(courseID);\n const payload = prepareNote55(courseID, codeCourse, shape, data, author, tags, uploads);\n const _id = `${DocTypePrefixes[DocType.DISPLAYABLE_DATA]}-${uuidv4()}`;\n const result = await db.put<DisplayableData>({ ...payload, _id });\n\n const dataShapeId = NameSpacer.getDataShapeString({\n course: codeCourse,\n dataShape: shape.name,\n });\n\n if (result.ok) {\n try {\n // create cards\n await createCards(courseID, dataShapeId, result.id, tags, elo, author);\n } catch (error) {\n // Handle CouchDB errors which often have a 'reason' property\n let errorMessage = 'Unknown error';\n if (error instanceof Error) {\n errorMessage = error.message;\n } else if (error && typeof error === 'object' && 'reason' in error) {\n errorMessage = error.reason as string;\n } else if (error && typeof error === 'object' && 'message' in error) {\n errorMessage = error.message as string;\n } else {\n errorMessage = String(error);\n }\n\n logger.error(`[addNote55] Failed to create cards for note ${result.id}: ${errorMessage}`);\n // Add info to result to indicate card creation failed\n (result as any).cardCreationFailed = true;\n (result as any).cardCreationError = errorMessage;\n }\n } else {\n logger.error(`[addNote55] Error adding note. Result: ${JSON.stringify(result)}`);\n }\n\n return result;\n}\n\nasync function createCards(\n courseID: string,\n datashapeID: PouchDB.Core.DocumentId,\n noteID: PouchDB.Core.DocumentId,\n tags: string[],\n elo: CourseElo = blankCourseElo(),\n author: string\n): Promise<void> {\n const cfg = await getCredentialledCourseConfig(courseID);\n const dsDescriptor = NameSpacer.getDataShapeDescriptor(datashapeID);\n let questionViewTypes: string[] = [];\n\n for (const ds of cfg.dataShapes) {\n if (ds.name === datashapeID) {\n questionViewTypes = ds.questionTypes;\n }\n }\n\n if (questionViewTypes.length === 0) {\n const errorMsg = `No questionViewTypes found for datashapeID: ${datashapeID} in course config. Cards cannot be created.`;\n logger.error(errorMsg);\n throw new Error(errorMsg);\n }\n\n for (const questionView of questionViewTypes) {\n await createCard(questionView, courseID, dsDescriptor, noteID, tags, elo, author);\n }\n}\n\nasync function createCard(\n questionViewName: string,\n courseID: string,\n dsDescriptor: ShapeDescriptor,\n noteID: string,\n tags: string[],\n elo: CourseElo = blankCourseElo(),\n author: string\n): Promise<void> {\n const qDescriptor = NameSpacer.getQuestionDescriptor(questionViewName);\n const cfg = await getCredentialledCourseConfig(courseID);\n\n for (const rQ of cfg.questionTypes) {\n if (rQ.name === questionViewName) {\n for (const view of rQ.viewList) {\n await addCard(\n courseID,\n dsDescriptor.course,\n [noteID],\n NameSpacer.getViewString({\n course: qDescriptor.course,\n questionType: qDescriptor.questionType,\n view,\n }),\n elo,\n tags,\n author\n );\n }\n }\n }\n}\n\n/**\n *\n * Adds a card to the DB. This function is called\n * as a side effect of adding either a View or\n * DisplayableData item.\n * @param course The name of the course that the card belongs to\n * @param id_displayable_data C/PouchDB ID of the data used to hydrate the view\n * @param id_view C/PouchDB ID of the view used to display the card\n *\n * @package\n */\nasync function addCard(\n courseID: string,\n course: string,\n id_displayable_data: PouchDB.Core.DocumentId[],\n id_view: PouchDB.Core.DocumentId,\n elo: CourseElo,\n tags: string[],\n author: string\n): Promise<PouchDB.Core.Response> {\n const db = getCourseDB(courseID);\n const _id = `${DocTypePrefixes[DocType.CARD]}-${uuidv4()}`;\n const card = await db.put<CardData>({\n _id,\n course,\n id_displayable_data,\n id_view,\n docType: DocType.CARD,\n elo: elo || toCourseElo(990 + Math.round(20 * Math.random())),\n author,\n });\n for (const tag of tags) {\n logger.info(`adding tag: ${tag} to card ${card.id}`);\n await addTagToCard(courseID, card.id, tag, author, false);\n }\n return card;\n}\n\nexport async function getCredentialledCourseConfig(courseID: string): Promise<CourseConfig> {\n try {\n const db = getCourseDB(courseID);\n const ret = await db.get<CourseConfig>('CourseConfig');\n ret.courseID = courseID;\n logger.info(`Returning course config: ${JSON.stringify(ret)}`);\n return ret;\n } catch (e) {\n logger.error(`Error fetching config for ${courseID}:`, e);\n throw e;\n }\n}\n\n/**\n Assciates a tag with a card.\n\n NB: DB stores tags as separate documents, with a list of card IDs.\n Consider renaming to `addCardToTag` to reflect this.\n\n NB: tags are created if they don't already exist\n\n @param updateELO whether to update the ELO of the card with the new tag. Default true.\n @package\n*/\nexport async function addTagToCard(\n courseID: string,\n cardID: string,\n tagID: string,\n author: string,\n updateELO: boolean = true\n): Promise<PouchDB.Core.Response> {\n // todo: possible future perf. hit if tags have large #s of taggedCards.\n // In this case, should be converted to a server-request\n const prefixedTagID = getTagID(tagID);\n const courseDB = getCourseDB(courseID);\n const courseApi = new CourseDB(courseID, async () => {\n const dummySyncStrategy = {\n setupRemoteDB: () => null as any,\n startSync: () => {},\n canCreateAccount: () => false,\n canAuthenticate: () => false,\n getCurrentUsername: async () => 'DummyUser',\n };\n return BaseUser.Dummy(dummySyncStrategy);\n });\n try {\n logger.info(`Applying tag ${tagID} to card ${courseID + '-' + cardID}...`);\n const tag = await courseDB.get<Tag>(prefixedTagID);\n if (!tag.taggedCards.includes(cardID)) {\n tag.taggedCards.push(cardID);\n\n if (updateELO) {\n try {\n const eloData = await courseApi.getCardEloData([cardID]);\n const elo = eloData[0];\n elo.tags[tagID] = {\n count: 0,\n score: elo.global.score, // todo: or 1000?\n };\n await updateCardElo(courseID, cardID, elo);\n } catch (error) {\n logger.error('Failed to update ELO data for card:', cardID, error);\n }\n }\n\n return courseDB.put<Tag>(tag);\n } else throw new AlreadyTaggedErr(`Card ${cardID} is already tagged with ${tagID}`);\n } catch (e) {\n if (e instanceof AlreadyTaggedErr) {\n throw e;\n }\n\n await createTag(courseID, tagID, author);\n return addTagToCard(courseID, cardID, tagID, author, updateELO);\n }\n}\n\nasync function updateCardElo(courseID: string, cardID: string, elo: CourseElo) {\n if (elo) {\n // checking against null, undefined, NaN\n const cDB = getCourseDB(courseID);\n const card = await cDB.get<CardData>(cardID);\n logger.debug(`Replacing ${JSON.stringify(card.elo)} with ${JSON.stringify(elo)}`);\n card.elo = elo;\n return cDB.put(card); // race conditions - is it important? probably not (net-zero effect)\n }\n}\n\nclass AlreadyTaggedErr extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'AlreadyTaggedErr';\n }\n}\n\nexport function getTagID(tagName: string): string {\n const tagPrefix = DocType.TAG.valueOf() + '-';\n if (tagName.indexOf(tagPrefix) === 0) {\n return tagName;\n } else {\n return tagPrefix + tagName;\n }\n}\n\nexport function getCourseDB(courseID: string): PouchDB.Database {\n const dbName = `coursedb-${courseID}`;\n return new pouch(\n ENV.COUCHDB_SERVER_PROTOCOL + '://' + ENV.COUCHDB_SERVER_URL + dbName,\n createPouchDBConfig()\n );\n}\n","import pouch from './pouchdb-setup';\nimport { ENV } from '@db/factory';\nimport { logger } from '../../util/logger';\n\nconst courseLookupDBTitle = 'coursedb-lookup';\n\ninterface CourseLookupDoc {\n _id: string;\n _rev: string;\n name: string;\n disambiguator?: string;\n}\n\nlogger.debug(`COURSELOOKUP FILE RUNNING`);\n\n/**\n * A Lookup table of existant courses. Each docID in this DB correspondes to a\n * course database whose name is `coursedb-{docID}`\n */\nexport default class CourseLookup {\n // [ ] this db should be read only for public, admin-only for write\n // Cache for the PouchDB instance\n private static _dbInstance: PouchDB.Database | null = null;\n\n /**\n * Static getter for the PouchDB database instance.\n * Connects using ENV variables and caches the instance.\n * Throws an error if required ENV variables are not set.\n */\n private static get _db(): PouchDB.Database {\n // Return cached instance if available\n if (this._dbInstance) {\n return this._dbInstance;\n }\n\n // --- Check required environment variables ---\n if (ENV.COUCHDB_SERVER_URL === 'NOT_SET' || !ENV.COUCHDB_SERVER_URL) {\n throw new Error(\n 'CourseLookup.db: COUCHDB_SERVER_URL is not set. Ensure initializeDataLayer has been called with valid configuration.'\n );\n }\n if (ENV.COUCHDB_SERVER_PROTOCOL === 'NOT_SET' || !ENV.COUCHDB_SERVER_PROTOCOL) {\n throw new Error(\n 'CourseLookup.db: COUCHDB_SERVER_PROTOCOL is not set. Ensure initializeDataLayer has been called with valid configuration.'\n );\n }\n\n // --- Construct connection options ---\n const dbUrl = `${ENV.COUCHDB_SERVER_PROTOCOL}://${ENV.COUCHDB_SERVER_URL}/${courseLookupDBTitle}`;\n const options: PouchDB.Configuration.RemoteDatabaseConfiguration = {\n // fetch: (url, opts) => { // Optional: Add for debugging network requests\n // console.log('PouchDB fetch:', url, opts);\n // return pouch.fetch(url, opts);\n // }\n };\n\n // Add authentication if both username and password are provided\n if (ENV.COUCHDB_USERNAME && ENV.COUCHDB_PASSWORD) {\n options.auth = {\n username: ENV.COUCHDB_USERNAME,\n password: ENV.COUCHDB_PASSWORD,\n };\n logger.info(`CourseLookup: Connecting to ${dbUrl} with authentication.`);\n } else {\n logger.info(`CourseLookup: Connecting to ${dbUrl} without authentication.`);\n }\n\n // --- Create and cache the PouchDB instance ---\n try {\n this._dbInstance = new pouch(dbUrl, options);\n logger.info(`CourseLookup: Database instance created for ${courseLookupDBTitle}.`);\n return this._dbInstance;\n } catch (error) {\n logger.error(`CourseLookup: Failed to create PouchDB instance for ${dbUrl}`, error);\n // Reset cache attempt on failure\n this._dbInstance = null;\n // Re-throw the error to indicate connection failure\n throw new Error(\n `CourseLookup: Failed to initialize database connection: ${\n error instanceof Error ? error.message : String(error)\n }`\n );\n }\n }\n\n /**\n * Adds a new course to the lookup database, and returns the courseID\n * @param courseName\n * @returns\n */\n static async add(courseName: string): Promise<string> {\n const resp = await CourseLookup._db.post({\n name: courseName,\n });\n\n return resp.id;\n }\n\n /**\n * Adds a new course to the lookup database with a specific courseID\n * @param courseId The specific course ID to use\n * @param courseName The course name\n * @param disambiguator Optional disambiguator\n * @returns Promise<void>\n */\n static async addWithId(\n courseId: string,\n courseName: string,\n disambiguator?: string\n ): Promise<void> {\n const doc: Omit<CourseLookupDoc, '_rev'> = {\n _id: courseId,\n name: courseName,\n };\n\n if (disambiguator) {\n doc.disambiguator = disambiguator;\n }\n\n await CourseLookup._db.put(doc);\n }\n\n /**\n * Removes a course from the index\n * @param courseID\n */\n static async delete(courseID: string): Promise<PouchDB.Core.Response> {\n const doc = await CourseLookup._db.get(courseID);\n return await CourseLookup._db.remove(doc);\n }\n\n // [ ] rename to allCourses()\n static async allCourseWare(): Promise<CourseLookupDoc[]> {\n const resp = await CourseLookup._db.allDocs<CourseLookupDoc>({\n include_docs: true,\n });\n\n return resp.rows.map((row) => row.doc!);\n }\n\n static async updateDisambiguator(\n courseID: string,\n disambiguator?: string\n ): Promise<PouchDB.Core.Response> {\n const doc = await CourseLookup._db.get<CourseLookupDoc>(courseID);\n doc.disambiguator = disambiguator;\n return await CourseLookup._db.put(doc);\n }\n\n static async isCourse(courseID: string): Promise<boolean> {\n try {\n await CourseLookup._db.get(courseID);\n return true;\n } catch (error) {\n logger.info(`Courselookup failed:`, error);\n return false;\n }\n }\n}\n","import { StudyContentSource, UserDBInterface, CourseDBInterface } from '..';\n\n// Re-export filter types\nexport type { CardFilter, FilterContext, CardFilterFactory } from './filters/types';\n\n// Re-export generator types\nexport type { CardGenerator, GeneratorContext, CardGeneratorFactory } from './generators/types';\n\nimport { ContentNavigationStrategyData } from '../types/contentNavigationStrategy';\nimport { logger } from '../../util/logger';\n\n// ============================================================================\n// NAVIGATION STRATEGY API\n// ============================================================================\n//\n// This module defines the ContentNavigator base class and the WeightedCard type,\n// which form the foundation of the pluggable navigation strategy system.\n//\n// KEY CONCEPTS:\n//\n// 1. WeightedCard - A card with a suitability score (0-1) and provenance trail.\n// The provenance tracks how each strategy in the pipeline contributed to\n// the card's final score, ensuring transparency and debuggability.\n//\n// 2. ContentNavigator - Abstract base class for backward compatibility.\n// New code should use CardGenerator or CardFilter interfaces directly.\n//\n// 3. CardGenerator vs CardFilter:\n// - Generators (ELO, SRS) produce candidate cards with scores\n// - Filters (Hierarchy, Interference, Priority, EloDistance) transform scores\n//\n// 4. Pipeline architecture:\n// Pipeline(generator, [filter1, filter2, ...]) executes:\n// cards = generator.getWeightedCards()\n// cards = filter1.transform(cards, context)\n// cards = filter2.transform(cards, context)\n// return sorted(cards)\n//\n// 5. Provenance tracking - Each strategy adds an entry explaining its contribution.\n// This makes the system transparent and debuggable.\n//\n// ============================================================================\n\n/**\n * Tracks a single strategy's contribution to a card's final score.\n *\n * Each strategy in the pipeline adds a StrategyContribution entry to the\n * card's provenance array, creating an audit trail of scoring decisions.\n */\nexport interface StrategyContribution {\n /**\n * Strategy type (implementing class name).\n * Examples: 'elo', 'hierarchyDefinition', 'interferenceMitigator'\n */\n strategy: string;\n\n /**\n * Human-readable name identifying this specific strategy instance.\n * Extracted from ContentNavigationStrategyData.name.\n * Courses may have multiple instances of the same strategy type with\n * different configurations.\n *\n * Examples:\n * - \"ELO (default)\"\n * - \"Interference: b/d/p confusion\"\n * - \"Interference: phonetic confusables\"\n * - \"Priority: Common letters first\"\n */\n strategyName: string;\n\n /**\n * Unique database document ID for this strategy instance.\n * Extracted from ContentNavigationStrategyData._id.\n * Use this to fetch the full strategy configuration document.\n *\n * Examples:\n * - \"NAVIGATION_STRATEGY-ELO-default\"\n * - \"NAVIGATION_STRATEGY-interference-bdp\"\n * - \"NAVIGATION_STRATEGY-priority-common-letters\"\n */\n strategyId: string;\n\n /**\n * What the strategy did:\n * - 'generated': Strategy produced this card (generators only)\n * - 'passed': Strategy evaluated but didn't change score (transparent pass-through)\n * - 'boosted': Strategy increased the score\n * - 'penalized': Strategy decreased the score\n */\n action: 'generated' | 'passed' | 'boosted' | 'penalized';\n\n /** Score after this strategy's processing */\n score: number;\n\n /**\n * Human-readable explanation of the strategy's decision.\n *\n * Examples:\n * - \"ELO distance 75, new card\"\n * - \"Prerequisites met: letter-sounds\"\n * - \"Interferes with immature tag 'd' (decay 0.8)\"\n * - \"High-priority tag 's' (0.95) → boost 1.15x\"\n *\n * Required for transparency - silent adjusters are anti-patterns.\n */\n reason: string;\n}\n\n/**\n * A card with a suitability score and provenance trail.\n *\n * Scores range from 0-1:\n * - 1.0 = fully suitable\n * - 0.0 = hard filter (e.g., prerequisite not met)\n * - 0.5 = neutral\n * - Intermediate values = soft preference\n *\n * Provenance tracks the scoring pipeline:\n * - First entry: Generator that produced the card\n * - Subsequent entries: Filters that transformed the score\n * - Each entry includes action and human-readable reason\n */\nexport interface WeightedCard {\n cardId: string;\n courseId: string;\n /** Suitability score from 0-1 */\n score: number;\n /**\n * Audit trail of strategy contributions.\n * First entry is from the generator, subsequent entries from filters.\n */\n provenance: StrategyContribution[];\n /**\n * Pre-fetched tags. Populated by Pipeline before filters run.\n * Filters should use this instead of querying getAppliedTags() individually.\n */\n tags?: string[];\n /**\n * Review document ID (_id from ScheduledCard).\n * Present when this card originated from SRS review scheduling.\n * Used by SessionController to track review outcomes and maintain review state.\n */\n reviewID?: string;\n}\n\n/**\n * Extract card origin from provenance trail.\n *\n * The first provenance entry (from the generator) indicates whether\n * this is a new card, review, or failed card. We parse the reason\n * string to extract this information.\n *\n * @param card - Card with provenance trail\n * @returns Card origin ('new', 'review', or 'failed')\n */\nexport function getCardOrigin(card: WeightedCard): 'new' | 'review' | 'failed' {\n if (card.provenance.length === 0) {\n throw new Error('Card has no provenance - cannot determine origin');\n }\n\n const firstEntry = card.provenance[0];\n const reason = firstEntry.reason.toLowerCase();\n\n if (reason.includes('failed')) {\n return 'failed';\n }\n if (reason.includes('review')) {\n return 'review';\n }\n return 'new';\n}\n\nexport enum Navigators {\n ELO = 'elo',\n SRS = 'srs',\n HIERARCHY = 'hierarchyDefinition',\n INTERFERENCE = 'interferenceMitigator',\n RELATIVE_PRIORITY = 'relativePriority',\n USER_TAG_PREFERENCE = 'userTagPreference',\n}\n\n// ============================================================================\n// NAVIGATOR ROLE CLASSIFICATION\n// ============================================================================\n//\n// Navigators are classified as either generators or filters:\n// - Generators: Produce candidate cards (ELO, SRS)\n// - Filters: Transform/score candidates (Hierarchy, Interference, RelativePriority)\n//\n// This classification is used by PipelineAssembler to build pipelines:\n// 1. Instantiate generators (possibly into a CompositeGenerator)\n// 2. Instantiate filters\n// 3. Create Pipeline(generator, filters)\n//\n// ============================================================================\n\n/**\n * Role classification for navigation strategies.\n *\n * - GENERATOR: Produces candidate cards with initial scores\n * - FILTER: Transforms cards with score multipliers\n */\nexport enum NavigatorRole {\n GENERATOR = 'generator',\n FILTER = 'filter',\n}\n\n/**\n * Registry mapping navigator implementations to their roles.\n */\nexport const NavigatorRoles: Record<Navigators, NavigatorRole> = {\n [Navigators.ELO]: NavigatorRole.GENERATOR,\n [Navigators.SRS]: NavigatorRole.GENERATOR,\n [Navigators.HIERARCHY]: NavigatorRole.FILTER,\n [Navigators.INTERFERENCE]: NavigatorRole.FILTER,\n [Navigators.RELATIVE_PRIORITY]: NavigatorRole.FILTER,\n [Navigators.USER_TAG_PREFERENCE]: NavigatorRole.FILTER,\n};\n\n/**\n * Check if a navigator implementation is a generator.\n *\n * @param impl - Navigator implementation name (e.g., 'elo', 'hierarchyDefinition')\n * @returns true if the navigator is a generator, false otherwise\n */\nexport function isGenerator(impl: string): boolean {\n return NavigatorRoles[impl as Navigators] === NavigatorRole.GENERATOR;\n}\n\n/**\n * Check if a navigator implementation is a filter.\n *\n * @param impl - Navigator implementation name (e.g., 'elo', 'hierarchyDefinition')\n * @returns true if the navigator is a filter, false otherwise\n */\nexport function isFilter(impl: string): boolean {\n return NavigatorRoles[impl as Navigators] === NavigatorRole.FILTER;\n}\n\n/**\n * Abstract base class for navigation strategies.\n *\n * This class exists primarily for backward compatibility with legacy code.\n * New code should use CardGenerator or CardFilter interfaces directly.\n *\n * The class implements StudyContentSource for compatibility with SessionController.\n * Once SessionController migrates to use getWeightedCards() exclusively,\n * the legacy methods can be removed.\n */\nexport abstract class ContentNavigator implements StudyContentSource {\n /** User interface for this navigation session */\n protected user: UserDBInterface;\n\n /** Course interface for this navigation session */\n protected course: CourseDBInterface;\n\n /** Human-readable name for this strategy instance (from ContentNavigationStrategyData.name) */\n protected strategyName?: string;\n\n /** Unique document ID for this strategy instance (from ContentNavigationStrategyData._id) */\n protected strategyId?: string;\n\n /**\n * Constructor for standard navigators.\n * Call this from subclass constructors to initialize common fields.\n *\n * Note: CompositeGenerator and Pipeline call super() without args, then set\n * user/course fields directly if needed.\n */\n constructor(\n user?: UserDBInterface,\n course?: CourseDBInterface,\n strategyData?: ContentNavigationStrategyData\n ) {\n this.user = user!;\n this.course = course!;\n if (strategyData) {\n this.strategyName = strategyData.name;\n this.strategyId = strategyData._id;\n }\n }\n\n // ============================================================================\n // STRATEGY STATE HELPERS\n // ============================================================================\n //\n // These methods allow strategies to persist their own state (user preferences,\n // learned patterns, temporal tracking) in the user database.\n //\n // ============================================================================\n\n /**\n * Unique key identifying this strategy for state storage.\n *\n * Defaults to the constructor name (e.g., \"UserTagPreferenceFilter\").\n * Override in subclasses if multiple instances of the same strategy type\n * need separate state storage.\n */\n protected get strategyKey(): string {\n return this.constructor.name;\n }\n\n /**\n * Get this strategy's persisted state for the current course.\n *\n * @returns The strategy's data payload, or null if no state exists\n * @throws Error if user or course is not initialized\n */\n protected async getStrategyState<T>(): Promise<T | null> {\n if (!this.user || !this.course) {\n throw new Error(\n `Cannot get strategy state: navigator not properly initialized. ` +\n `Ensure user and course are provided to constructor.`\n );\n }\n return this.user.getStrategyState<T>(this.course.getCourseID(), this.strategyKey);\n }\n\n /**\n * Persist this strategy's state for the current course.\n *\n * @param data - The strategy's data payload to store\n * @throws Error if user or course is not initialized\n */\n protected async putStrategyState<T>(data: T): Promise<void> {\n if (!this.user || !this.course) {\n throw new Error(\n `Cannot put strategy state: navigator not properly initialized. ` +\n `Ensure user and course are provided to constructor.`\n );\n }\n return this.user.putStrategyState<T>(this.course.getCourseID(), this.strategyKey, data);\n }\n\n /**\n * Factory method to create navigator instances dynamically.\n *\n * @param user - User interface\n * @param course - Course interface\n * @param strategyData - Strategy configuration document\n * @returns the runtime object used to steer a study session.\n */\n static async create(\n user: UserDBInterface,\n course: CourseDBInterface,\n strategyData: ContentNavigationStrategyData\n ): Promise<ContentNavigator> {\n const implementingClass = strategyData.implementingClass;\n let NavigatorImpl;\n\n // Try different extension variations\n const variations = ['.ts', '.js', ''];\n const dirs = ['filters', 'generators'];\n\n for (const ext of variations) {\n for (const dir of dirs) {\n const loadFrom = `./${dir}/${implementingClass}${ext}`;\n try {\n const module = await import(loadFrom);\n NavigatorImpl = module.default;\n break; // Break the loop if loading succeeds\n } catch (e) {\n // Continue to next variation if this one fails\n logger.debug(`Failed to load extension from ${loadFrom}:`, e);\n }\n }\n }\n\n if (!NavigatorImpl) {\n throw new Error(`Could not load navigator implementation for: ${implementingClass}`);\n }\n\n return new NavigatorImpl(user, course, strategyData);\n }\n\n /**\n * Get cards with suitability scores and provenance trails.\n *\n * **This is the PRIMARY API for navigation strategies.**\n *\n * Returns cards ranked by suitability score (0-1). Higher scores indicate\n * better candidates for presentation. Each card includes a provenance trail\n * documenting how strategies contributed to the final score.\n *\n * ## Implementation Required\n * All navigation strategies MUST override this method. The base class does\n * not provide a default implementation.\n *\n * ## For Generators\n * Override this method to generate candidates and compute scores based on\n * your strategy's logic (e.g., ELO proximity, review urgency). Create the\n * initial provenance entry with action='generated'.\n *\n * ## For Filters\n * Filters should implement the CardFilter interface instead and be composed\n * via Pipeline. Filters do not directly implement getWeightedCards().\n *\n * @param limit - Maximum cards to return\n * @returns Cards sorted by score descending, with provenance trails\n */\n async getWeightedCards(_limit: number): Promise<WeightedCard[]> {\n throw new Error(`${this.constructor.name} must implement getWeightedCards(). `);\n }\n}\n","import { toCourseElo } from '@vue-skuilder/common';\nimport type { CourseDBInterface } from '../interfaces/courseDB';\nimport type { UserDBInterface } from '../interfaces/userDB';\nimport { ContentNavigator } from './index';\nimport type { WeightedCard } from './index';\nimport type { CardFilter, FilterContext } from './filters/types';\nimport type { CardGenerator, GeneratorContext } from './generators/types';\nimport { logger } from '../../util/logger';\n\n// ============================================================================\n// PIPELINE LOGGING HELPERS\n// ============================================================================\n//\n// Focused logging functions that can be toggled by commenting single lines.\n// Use these to inspect pipeline behavior in development/production.\n//\n\n/**\n * Log pipeline configuration on construction.\n * Shows generator and filter chain structure.\n */\nfunction logPipelineConfig(generator: CardGenerator, filters: CardFilter[]): void {\n const filterList =\n filters.length > 0 ? '\\n - ' + filters.map((f) => f.name).join('\\n - ') : ' none';\n\n logger.info(\n `[Pipeline] Configuration:\\n` + ` Generator: ${generator.name}\\n` + ` Filters:${filterList}`\n );\n}\n\n/**\n * Log tag hydration results.\n * Shows effectiveness of batch query (how many cards/tags were hydrated).\n */\nfunction logTagHydration(cards: WeightedCard[], tagsByCard: Map<string, string[]>): void {\n const totalTags = Array.from(tagsByCard.values()).reduce((sum, tags) => sum + tags.length, 0);\n const cardsWithTags = Array.from(tagsByCard.values()).filter((tags) => tags.length > 0).length;\n\n logger.debug(\n `[Pipeline] Tag hydration: ${cards.length} cards, ` +\n `${cardsWithTags} have tags (${totalTags} total tags) - single batch query`\n );\n}\n\n/**\n * Log pipeline execution summary.\n * Shows complete flow from generator through filters to final results.\n */\nfunction logExecutionSummary(\n generatorName: string,\n generatedCount: number,\n filterCount: number,\n finalCount: number,\n topScores: number[]\n): void {\n const scoreDisplay =\n topScores.length > 0 ? topScores.map((s) => s.toFixed(2)).join(', ') : 'none';\n\n logger.info(\n `[Pipeline] Execution: ${generatorName} produced ${generatedCount} → ` +\n `${filterCount} filters → ${finalCount} results (top scores: ${scoreDisplay})`\n );\n}\n\n/**\n * Log provenance trails for cards.\n * Shows the complete scoring history for each card through the pipeline.\n * Useful for debugging why cards scored the way they did.\n */\nfunction logCardProvenance(cards: WeightedCard[], maxCards: number = 3): void {\n const cardsToLog = cards.slice(0, maxCards);\n\n logger.debug(`[Pipeline] Provenance for top ${cardsToLog.length} cards:`);\n\n for (const card of cardsToLog) {\n logger.debug(`[Pipeline] ${card.cardId} (final score: ${card.score.toFixed(3)}):`);\n\n for (const entry of card.provenance) {\n const scoreChange = entry.score.toFixed(3);\n const action = entry.action.padEnd(9); // Align columns\n logger.debug(\n `[Pipeline] ${action} ${scoreChange} - ${entry.strategyName}: ${entry.reason}`\n );\n }\n }\n}\n\n// ============================================================================\n// PIPELINE\n// ============================================================================\n//\n// Executes a navigation pipeline: generator → filters → sorted results.\n//\n// Architecture:\n// cards = generator.getWeightedCards(limit, context)\n// cards = filter1.transform(cards, context)\n// cards = filter2.transform(cards, context)\n// cards = filter3.transform(cards, context)\n// return sorted(cards).slice(0, limit)\n//\n// Benefits:\n// - Clear separation: generators produce, filters transform\n// - No nested instantiation complexity\n// - Filters don't need to know about each other\n// - Shared context built once, passed to all stages\n//\n// ============================================================================\n\n/**\n * A navigation pipeline that runs a generator and applies filters sequentially.\n *\n * Implements StudyContentSource for backward compatibility with SessionController.\n *\n * ## Usage\n *\n * ```typescript\n * const pipeline = new Pipeline(\n * compositeGenerator, // or single generator\n * [eloDistanceFilter, interferenceFilter],\n * user,\n * course\n * );\n *\n * const cards = await pipeline.getWeightedCards(20);\n * ```\n */\nexport class Pipeline extends ContentNavigator {\n private generator: CardGenerator;\n private filters: CardFilter[];\n\n /**\n * Create a new pipeline.\n *\n * @param generator - The generator (or CompositeGenerator) that produces candidates\n * @param filters - Filters to apply sequentially (order doesn't matter for multipliers)\n * @param user - User database interface\n * @param course - Course database interface\n */\n constructor(\n generator: CardGenerator,\n filters: CardFilter[],\n user: UserDBInterface,\n course: CourseDBInterface\n ) {\n super();\n this.generator = generator;\n this.filters = filters;\n this.user = user;\n this.course = course;\n\n course\n .getCourseConfig()\n .then((cfg) => {\n logger.debug(`[pipeline] Crated pipeline for ${cfg.name}`);\n })\n .catch((e) => {\n logger.error(`[pipeline] Failed to lookup courseCfg: ${e}`);\n });\n // Toggle pipeline configuration logging:\n logPipelineConfig(generator, filters);\n }\n\n /**\n * Get weighted cards by running generator and applying filters.\n *\n * 1. Build shared context (user ELO, etc.)\n * 2. Get candidates from generator (passing context)\n * 3. Batch hydrate tags for all candidates\n * 4. Apply each filter sequentially\n * 5. Remove zero-score cards\n * 6. Sort by score descending\n * 7. Return top N\n *\n * @param limit - Maximum number of cards to return\n * @returns Cards sorted by score descending\n */\n async getWeightedCards(limit: number): Promise<WeightedCard[]> {\n // Build shared context once\n const context = await this.buildContext();\n\n // Over-fetch from generator to account for filtering\n const overFetchMultiplier = 2 + this.filters.length * 0.5;\n const fetchLimit = Math.ceil(limit * overFetchMultiplier);\n\n logger.debug(\n `[Pipeline] Fetching ${fetchLimit} candidates from generator '${this.generator.name}'`\n );\n\n // Get candidates from generator, passing context\n let cards = await this.generator.getWeightedCards(fetchLimit, context);\n const generatedCount = cards.length;\n\n logger.debug(`[Pipeline] Generator returned ${generatedCount} candidates`);\n\n // Batch hydrate tags before filters run\n cards = await this.hydrateTags(cards);\n\n // Apply filters sequentially\n for (const filter of this.filters) {\n const beforeCount = cards.length;\n cards = await filter.transform(cards, context);\n logger.debug(`[Pipeline] Filter '${filter.name}': ${beforeCount} → ${cards.length} cards`);\n }\n\n // Remove zero-score cards (hard filtered)\n cards = cards.filter((c) => c.score > 0);\n\n // Sort by score descending\n cards.sort((a, b) => b.score - a.score);\n\n // Return top N\n const result = cards.slice(0, limit);\n\n // Toggle execution summary logging:\n const topScores = result.slice(0, 3).map((c) => c.score);\n logExecutionSummary(\n this.generator.name,\n generatedCount,\n this.filters.length,\n result.length,\n topScores\n );\n\n // Toggle provenance logging (shows scoring history for top cards):\n logCardProvenance(result, 3);\n\n return result;\n }\n\n /**\n * Batch hydrate tags for all cards.\n *\n * Fetches tags for all cards in a single database query and attaches them\n * to the WeightedCard objects. Filters can then use card.tags instead of\n * making individual getAppliedTags() calls.\n *\n * @param cards - Cards to hydrate\n * @returns Cards with tags populated\n */\n private async hydrateTags(cards: WeightedCard[]): Promise<WeightedCard[]> {\n if (cards.length === 0) {\n return cards;\n }\n\n const cardIds = cards.map((c) => c.cardId);\n const tagsByCard = await this.course!.getAppliedTagsBatch(cardIds);\n\n // Toggle tag hydration logging:\n logTagHydration(cards, tagsByCard);\n\n return cards.map((card) => ({\n ...card,\n tags: tagsByCard.get(card.cardId) ?? [],\n }));\n }\n\n /**\n * Build shared context for generator and filters.\n *\n * Called once per getWeightedCards() invocation.\n * Contains data that the generator and multiple filters might need.\n *\n * The context satisfies both GeneratorContext and FilterContext interfaces.\n */\n private async buildContext(): Promise<GeneratorContext & FilterContext> {\n let userElo = 1000; // Default ELO\n\n try {\n const courseReg = await this.user!.getCourseRegDoc(this.course!.getCourseID());\n const courseElo = toCourseElo(courseReg.elo);\n userElo = courseElo.global.score;\n } catch (e) {\n logger.debug(`[Pipeline] Could not get user ELO, using default: ${e}`);\n }\n\n return {\n user: this.user!,\n course: this.course!,\n userElo,\n };\n }\n\n /**\n * Get the course ID for this pipeline.\n */\n getCourseID(): string {\n return this.course!.getCourseID();\n }\n}\n","import { ContentNavigator } from '../index';\nimport type { WeightedCard } from '../index';\nimport type { ContentNavigationStrategyData } from '../../types/contentNavigationStrategy';\nimport type { CourseDBInterface } from '../../interfaces/courseDB';\nimport type { UserDBInterface } from '../../interfaces/userDB';\nimport type { CardGenerator, GeneratorContext } from './types';\nimport { logger } from '../../../util/logger';\n\n// ============================================================================\n// COMPOSITE GENERATOR\n// ============================================================================\n//\n// Composes multiple generator strategies into a single generator.\n//\n// Use case: When a course has multiple generators (e.g., ELO + SRS), this\n// class merges their outputs into a unified candidate list.\n//\n// Aggregation strategy:\n// - Cards appearing in multiple generators get a frequency boost\n// - Score = average(scores) * (1 + 0.1 * (appearances - 1))\n// - This rewards cards that multiple generators agree on\n//\n// ============================================================================\n\n/**\n * Aggregation modes for combining scores from multiple generators.\n */\nexport enum AggregationMode {\n /** Use the maximum score from any generator */\n MAX = 'max',\n /** Average all scores */\n AVERAGE = 'average',\n /** Average with frequency boost: avg * (1 + 0.1 * (n-1)) */\n FREQUENCY_BOOST = 'frequencyBoost',\n}\n\nconst DEFAULT_AGGREGATION_MODE = AggregationMode.FREQUENCY_BOOST;\nconst FREQUENCY_BOOST_FACTOR = 0.1;\n\n/**\n * Composes multiple generators into a single generator.\n *\n * Implements CardGenerator for use in Pipeline architecture.\n * Also extends ContentNavigator for backward compatibility.\n *\n * Fetches candidates from all generators, deduplicates by cardId,\n * and aggregates scores based on the configured mode.\n */\nexport default class CompositeGenerator extends ContentNavigator implements CardGenerator {\n /** Human-readable name for CardGenerator interface */\n name: string = 'Composite Generator';\n\n private generators: CardGenerator[];\n private aggregationMode: AggregationMode;\n\n constructor(\n generators: CardGenerator[],\n aggregationMode: AggregationMode = DEFAULT_AGGREGATION_MODE\n ) {\n super();\n this.generators = generators;\n this.aggregationMode = aggregationMode;\n\n if (generators.length === 0) {\n throw new Error('CompositeGenerator requires at least one generator');\n }\n\n logger.debug(\n `[CompositeGenerator] Created with ${generators.length} generators, mode: ${aggregationMode}`\n );\n }\n\n /**\n * Creates a CompositeGenerator from strategy data.\n *\n * This is a convenience factory for use by PipelineAssembler.\n */\n static async fromStrategies(\n user: UserDBInterface,\n course: CourseDBInterface,\n strategies: ContentNavigationStrategyData[],\n aggregationMode: AggregationMode = DEFAULT_AGGREGATION_MODE\n ): Promise<CompositeGenerator> {\n const generators = await Promise.all(\n strategies.map((s) => ContentNavigator.create(user, course, s))\n );\n // Cast is safe because we know these are generators\n return new CompositeGenerator(generators as unknown as CardGenerator[], aggregationMode);\n }\n\n /**\n * Get weighted cards from all generators, merge and deduplicate.\n *\n * Cards appearing in multiple generators receive a score boost.\n * Provenance tracks which generators produced each card and how scores were aggregated.\n *\n * This method supports both the legacy signature (limit only) and the\n * CardGenerator interface signature (limit, context).\n *\n * @param limit - Maximum number of cards to return\n * @param context - GeneratorContext passed to child generators (required when called via Pipeline)\n */\n async getWeightedCards(limit: number, context?: GeneratorContext): Promise<WeightedCard[]> {\n if (!context) {\n throw new Error(\n 'CompositeGenerator.getWeightedCards requires a GeneratorContext. ' +\n 'It should be called via Pipeline, not directly.'\n );\n }\n\n // Fetch from all generators in parallel\n const results = await Promise.all(\n this.generators.map((g) => g.getWeightedCards(limit, context))\n );\n\n // Group by cardId\n const byCardId = new Map<string, WeightedCard[]>();\n for (const cards of results) {\n for (const card of cards) {\n const existing = byCardId.get(card.cardId) || [];\n existing.push(card);\n byCardId.set(card.cardId, existing);\n }\n }\n\n // Aggregate scores\n const merged: WeightedCard[] = [];\n for (const [, cards] of byCardId) {\n const aggregatedScore = this.aggregateScores(cards);\n const finalScore = Math.min(1.0, aggregatedScore); // Clamp to [0, 1]\n\n // Merge provenance from all generators that produced this card\n const mergedProvenance = cards.flatMap((c) => c.provenance);\n\n // Determine action based on whether score changed\n const initialScore = cards[0].score;\n const action =\n finalScore > initialScore ? 'boosted' : finalScore < initialScore ? 'penalized' : 'passed';\n\n // Build reason explaining the aggregation\n const reason = this.buildAggregationReason(cards, finalScore);\n\n // Append composite provenance entry\n merged.push({\n ...cards[0],\n score: finalScore,\n provenance: [\n ...mergedProvenance,\n {\n strategy: 'composite',\n strategyName: 'Composite Generator',\n strategyId: 'COMPOSITE_GENERATOR',\n action,\n score: finalScore,\n reason,\n },\n ],\n });\n }\n\n // Sort by score descending and limit\n return merged.sort((a, b) => b.score - a.score).slice(0, limit);\n }\n\n /**\n * Build human-readable reason for score aggregation.\n */\n private buildAggregationReason(cards: WeightedCard[], finalScore: number): string {\n const count = cards.length;\n const scores = cards.map((c) => c.score.toFixed(2)).join(', ');\n\n if (count === 1) {\n return `Single generator, score ${finalScore.toFixed(2)}`;\n }\n\n const strategies = cards.map((c) => c.provenance[0]?.strategy || 'unknown').join(', ');\n\n switch (this.aggregationMode) {\n case AggregationMode.MAX:\n return `Max of ${count} generators (${strategies}): scores [${scores}] → ${finalScore.toFixed(2)}`;\n\n case AggregationMode.AVERAGE:\n return `Average of ${count} generators (${strategies}): scores [${scores}] → ${finalScore.toFixed(2)}`;\n\n case AggregationMode.FREQUENCY_BOOST: {\n const avg = cards.reduce((sum, c) => sum + c.score, 0) / count;\n const boost = 1 + FREQUENCY_BOOST_FACTOR * (count - 1);\n return `Frequency boost from ${count} generators (${strategies}): avg ${avg.toFixed(2)} × ${boost.toFixed(2)} → ${finalScore.toFixed(2)}`;\n }\n\n default:\n return `Aggregated from ${count} generators: ${finalScore.toFixed(2)}`;\n }\n }\n\n /**\n * Aggregate scores from multiple generators for the same card.\n */\n private aggregateScores(cards: WeightedCard[]): number {\n const scores = cards.map((c) => c.score);\n\n switch (this.aggregationMode) {\n case AggregationMode.MAX:\n return Math.max(...scores);\n\n case AggregationMode.AVERAGE:\n return scores.reduce((sum, s) => sum + s, 0) / scores.length;\n\n case AggregationMode.FREQUENCY_BOOST: {\n const avg = scores.reduce((sum, s) => sum + s, 0) / scores.length;\n const frequencyBoost = 1 + FREQUENCY_BOOST_FACTOR * (cards.length - 1);\n return avg * frequencyBoost;\n }\n\n default:\n return scores[0];\n }\n }\n}\n","import type { ContentNavigationStrategyData } from '../types/contentNavigationStrategy';\nimport { ContentNavigator, isGenerator, isFilter, Navigators } from './index';\nimport type { CardFilter } from './filters/types';\nimport type { CardGenerator } from './generators/types';\nimport { Pipeline } from './Pipeline';\nimport { DocType } from '../types/types-legacy';\nimport { logger } from '../../util/logger';\nimport type { CourseDBInterface } from '../interfaces/courseDB';\nimport type { UserDBInterface } from '../interfaces/userDB';\nimport CompositeGenerator from './generators/CompositeGenerator';\n\n// ============================================================================\n// PIPELINE ASSEMBLER\n// ============================================================================\n//\n// Assembles navigation strategies into a Pipeline instance.\n//\n// This class is DB-agnostic: it receives strategy documents and returns an\n// assembled, ready-to-use Pipeline. This separation enables:\n// 1. Use with different DB implementations (Couch, Static, etc.)\n// 2. Future dynamic/evolutionary strategy selection\n// 3. Easy unit testing without DB mocking\n//\n// Pipeline assembly:\n// 1. Separate strategies into generators and filters by role\n// 2. Instantiate generator(s) - wrap multiple in CompositeGenerator\n// 3. Instantiate filters\n// 4. Return Pipeline(generator, filters)\n//\n// ============================================================================\n\n/**\n * Input for pipeline assembly.\n */\nexport interface PipelineAssemblerInput {\n /** All strategy documents to assemble into a pipeline */\n strategies: ContentNavigationStrategyData[];\n /** User database interface (required for instantiation) */\n user: UserDBInterface;\n /** Course database interface (required for instantiation) */\n course: CourseDBInterface;\n}\n\n/**\n * Result of pipeline assembly.\n */\nexport interface PipelineAssemblyResult {\n /** The assembled pipeline, or null if assembly failed */\n pipeline: Pipeline | null;\n /** Generator strategies found (for informational purposes) */\n generatorStrategies: ContentNavigationStrategyData[];\n /** Filter strategies found (for informational purposes) */\n filterStrategies: ContentNavigationStrategyData[];\n /** Warnings encountered during assembly (logged but non-fatal) */\n warnings: string[];\n}\n\n/**\n * Assembles navigation strategies into a Pipeline.\n *\n * Instantiates generators and filters from strategy documents and\n * composes them into a ready-to-use Pipeline instance.\n */\nexport class PipelineAssembler {\n /**\n * Assembles a navigation pipeline from strategy documents.\n *\n * 1. Separates into generators and filters by role\n * 2. Validates at least one generator exists (or creates default ELO)\n * 3. Instantiates generators - wraps multiple in CompositeGenerator\n * 4. Instantiates filters\n * 5. Returns Pipeline(generator, filters)\n *\n * @param input - Strategy documents plus user/course interfaces\n * @returns Assembled pipeline and any warnings\n */\n async assemble(input: PipelineAssemblerInput): Promise<PipelineAssemblyResult> {\n const { strategies, user, course } = input;\n const warnings: string[] = [];\n\n if (strategies.length === 0) {\n return {\n pipeline: null,\n generatorStrategies: [],\n filterStrategies: [],\n warnings,\n };\n }\n\n // Separate generators from filters\n const generatorStrategies: ContentNavigationStrategyData[] = [];\n const filterStrategies: ContentNavigationStrategyData[] = [];\n\n for (const s of strategies) {\n if (isGenerator(s.implementingClass)) {\n generatorStrategies.push(s);\n } else if (isFilter(s.implementingClass)) {\n filterStrategies.push(s);\n } else {\n // Unknown strategy type — skip with warning\n warnings.push(`Unknown strategy type '${s.implementingClass}', skipping: ${s.name}`);\n }\n }\n\n // If no generator but filters exist, use default ELO generator\n if (generatorStrategies.length === 0) {\n if (filterStrategies.length > 0) {\n logger.debug(\n '[PipelineAssembler] No generator found, using default ELO with configured filters'\n );\n generatorStrategies.push(this.makeDefaultEloStrategy(course.getCourseID()));\n } else {\n warnings.push('No generator strategy found');\n return {\n pipeline: null,\n generatorStrategies: [],\n filterStrategies: [],\n warnings,\n };\n }\n }\n\n // Instantiate generators\n let generator: CardGenerator;\n\n if (generatorStrategies.length === 1) {\n // Single generator\n const nav = await ContentNavigator.create(user, course, generatorStrategies[0]);\n generator = nav as unknown as CardGenerator;\n logger.debug(`[PipelineAssembler] Using single generator: ${generatorStrategies[0].name}`);\n } else {\n // Multiple generators - wrap in CompositeGenerator\n logger.debug(\n `[PipelineAssembler] Using CompositeGenerator for ${generatorStrategies.length} generators: ${generatorStrategies.map((g) => g.name).join(', ')}`\n );\n generator = await CompositeGenerator.fromStrategies(user, course, generatorStrategies);\n }\n\n // Instantiate filters\n const filters: CardFilter[] = [];\n\n // Sort filters alphabetically for deterministic ordering\n const sortedFilterStrategies = [...filterStrategies].sort((a, b) =>\n a.name.localeCompare(b.name)\n );\n\n for (const filterStrategy of sortedFilterStrategies) {\n try {\n const nav = await ContentNavigator.create(user, course, filterStrategy);\n // The navigator implements CardFilter\n if ('transform' in nav && typeof nav.transform === 'function') {\n filters.push(nav as unknown as CardFilter);\n logger.debug(`[PipelineAssembler] Added filter: ${filterStrategy.name}`);\n } else {\n warnings.push(\n `Filter '${filterStrategy.name}' does not implement CardFilter.transform(), skipping`\n );\n }\n } catch (e) {\n warnings.push(`Failed to instantiate filter '${filterStrategy.name}': ${e}`);\n }\n }\n\n // Build pipeline\n const pipeline = new Pipeline(generator, filters, user, course);\n\n logger.debug(\n `[PipelineAssembler] Assembled pipeline with ${generatorStrategies.length} generator(s) and ${filters.length} filter(s)`\n );\n\n return {\n pipeline,\n generatorStrategies,\n filterStrategies: sortedFilterStrategies,\n warnings,\n };\n }\n\n /**\n * Creates a default ELO generator strategy.\n * Used when filters are configured but no generator is specified.\n */\n private makeDefaultEloStrategy(courseId: string): ContentNavigationStrategyData {\n return {\n _id: 'NAVIGATION_STRATEGY-ELO-default',\n course: courseId,\n docType: DocType.NAVIGATION_STRATEGY,\n name: 'ELO (default)',\n description: 'Default ELO-based generator',\n implementingClass: Navigators.ELO,\n serializedData: '',\n };\n }\n}\n","import type { CourseDBInterface } from '../../interfaces/courseDB';\nimport type { UserDBInterface } from '../../interfaces/userDB';\nimport { ContentNavigator } from '../index';\nimport type { WeightedCard } from '../index';\nimport { toCourseElo } from '@vue-skuilder/common';\nimport type { QualifiedCardID } from '../..';\nimport type { CardGenerator, GeneratorContext } from './types';\n\n// ============================================================================\n// ELO NAVIGATOR\n// ============================================================================\n//\n// A generator strategy that selects new cards based on ELO proximity.\n//\n// Cards closer to the user's skill level (ELO) receive higher scores.\n// This ensures learners see content matched to their current ability.\n//\n// NOTE: This generator only handles NEW cards. Reviews are handled by\n// SRSNavigator. Use CompositeGenerator to combine both.\n//\n// ============================================================================\n\n/**\n * A navigation strategy that scores new cards by ELO proximity.\n *\n * Implements CardGenerator for use in Pipeline architecture.\n * Also extends ContentNavigator for backward compatibility with legacy code.\n *\n * Higher scores indicate better ELO match:\n * - Cards at user's ELO level score highest\n * - Score decreases with ELO distance\n *\n * Only returns new cards - use SRSNavigator for reviews.\n */\nexport default class ELONavigator extends ContentNavigator implements CardGenerator {\n /** Human-readable name for CardGenerator interface */\n name: string;\n\n constructor(\n user: UserDBInterface,\n course: CourseDBInterface,\n strategyData?: { name: string; _id: string }\n // The ELO strategy is non-parameterized.\n //\n // It instead relies on existing meta data from the course and user with respect to\n // ELO scores - it uses those to select cards matched to user skill level.\n ) {\n super(user, course, strategyData as any);\n this.name = strategyData?.name || 'ELO';\n }\n\n /**\n * Get new cards with suitability scores based on ELO distance.\n *\n * Cards closer to user's ELO get higher scores.\n * Score formula: max(0, 1 - distance / 500)\n *\n * NOTE: This generator only handles NEW cards. Reviews are handled by\n * SRSNavigator. Use CompositeGenerator to combine both.\n *\n * This method supports both the legacy signature (limit only) and the\n * CardGenerator interface signature (limit, context).\n *\n * @param limit - Maximum number of cards to return\n * @param context - Optional GeneratorContext (used when called via Pipeline)\n */\n async getWeightedCards(limit: number, context?: GeneratorContext): Promise<WeightedCard[]> {\n // Determine user ELO - from context if available, otherwise fetch\n let userGlobalElo: number;\n if (context?.userElo !== undefined) {\n userGlobalElo = context.userElo;\n } else {\n const courseReg = await this.user.getCourseRegDoc(this.course.getCourseID());\n const userElo = toCourseElo(courseReg.elo);\n userGlobalElo = userElo.global.score;\n }\n\n const activeCards = await this.user.getActiveCards();\n const newCards = (\n await this.course.getCardsCenteredAtELO(\n { limit, elo: 'user' },\n (c: QualifiedCardID) => !activeCards.some((ac) => c.cardID === ac.cardID)\n )\n ).map((c) => ({ ...c, status: 'new' as const }));\n\n // Get ELO data for all cards in one batch\n const cardIds = newCards.map((c) => c.cardID);\n const cardEloData = await this.course.getCardEloData(cardIds);\n\n // Score new cards by ELO distance\n const scored: WeightedCard[] = newCards.map((c, i) => {\n const cardElo = cardEloData[i]?.global?.score ?? 1000;\n const distance = Math.abs(cardElo - userGlobalElo);\n const score = Math.max(0, 1 - distance / 500);\n\n return {\n cardId: c.cardID,\n courseId: c.courseID,\n score,\n provenance: [\n {\n strategy: 'elo',\n strategyName: this.strategyName || this.name,\n strategyId: this.strategyId || 'NAVIGATION_STRATEGY-ELO-default',\n action: 'generated',\n score,\n reason: `ELO distance ${Math.round(distance)} (card: ${Math.round(cardElo)}, user: ${Math.round(userGlobalElo)}), new card`,\n },\n ],\n };\n });\n\n // Sort by score descending\n scored.sort((a, b) => b.score - a.score);\n\n return scored.slice(0, limit);\n }\n}\n","import moment from 'moment';\nimport type { ScheduledCard } from '../../types/user';\nimport type { CourseDBInterface } from '../../interfaces/courseDB';\nimport type { UserDBInterface } from '../../interfaces/userDB';\nimport { ContentNavigator } from '../index';\nimport type { WeightedCard } from '../index';\nimport type { ContentNavigationStrategyData } from '../../types/contentNavigationStrategy';\nimport type { CardGenerator, GeneratorContext } from './types';\nimport { logger } from '@db/util/logger';\n\n// ============================================================================\n// SRS NAVIGATOR\n// ============================================================================\n//\n// A generator strategy that scores review cards by urgency.\n//\n// Urgency is determined by two factors:\n// 1. Overdueness - how far past the scheduled review time\n// 2. Interval recency - shorter scheduled intervals indicate \"novel content in progress\"\n//\n// A card with a 3-day interval that's 2 days overdue is more urgent than a card\n// with a 6-month interval that's 2 days overdue. The shorter interval represents\n// active learning at higher resolution.\n//\n// This navigator only handles reviews - it does not generate new cards.\n// For new cards, use ELONavigator or another generator via CompositeGenerator.\n//\n// ============================================================================\n\n/**\n * Configuration for the SRS strategy.\n * Currently minimal - the algorithm is not parameterized.\n */\n// eslint-disable-next-line @typescript-eslint/no-empty-object-type\nexport interface SRSConfig {\n // Future: configurable urgency curves, thresholds, etc.\n}\n\n/**\n * A navigation strategy that scores review cards by urgency.\n *\n * Implements CardGenerator for use in Pipeline architecture.\n * Also extends ContentNavigator for backward compatibility with legacy code.\n *\n * Higher scores indicate more urgent reviews:\n * - Cards that are more overdue (relative to their interval) score higher\n * - Cards with shorter intervals (recent learning) score higher\n *\n * Only returns cards that are actually due (reviewTime has passed).\n * Does not generate new cards - use with CompositeGenerator for mixed content.\n */\nexport default class SRSNavigator extends ContentNavigator implements CardGenerator {\n /** Human-readable name for CardGenerator interface */\n name: string;\n\n constructor(\n user: UserDBInterface,\n course: CourseDBInterface,\n strategyData?: ContentNavigationStrategyData\n ) {\n super(user, course, strategyData as ContentNavigationStrategyData);\n this.name = strategyData?.name || 'SRS';\n }\n\n /**\n * Get review cards scored by urgency.\n *\n * Score formula combines:\n * - Relative overdueness: hoursOverdue / intervalHours\n * - Interval recency: exponential decay favoring shorter intervals\n *\n * Cards not yet due are excluded (not scored as 0).\n *\n * This method supports both the legacy signature (limit only) and the\n * CardGenerator interface signature (limit, context).\n *\n * @param limit - Maximum number of cards to return\n * @param _context - Optional GeneratorContext (currently unused, but required for interface)\n */\n async getWeightedCards(limit: number, _context?: GeneratorContext): Promise<WeightedCard[]> {\n if (!this.user || !this.course) {\n throw new Error('SRSNavigator requires user and course to be set');\n }\n\n const reviews = await this.user.getPendingReviews(this.course.getCourseID());\n const now = moment.utc();\n\n // Filter to only cards that are actually due\n const dueReviews = reviews.filter((r) => now.isAfter(moment.utc(r.reviewTime)));\n\n const scored = dueReviews.map((review) => {\n const { score, reason } = this.computeUrgencyScore(review, now);\n\n return {\n cardId: review.cardId,\n courseId: review.courseId,\n score,\n reviewID: review._id,\n provenance: [\n {\n strategy: 'srs',\n strategyName: this.strategyName || this.name,\n strategyId: this.strategyId || 'NAVIGATION_STRATEGY-SRS-default',\n action: 'generated' as const,\n score,\n reason,\n },\n ],\n };\n });\n\n logger.debug(`[srsNav] got ${scored.length} weighted cards`);\n\n // Sort by score descending and limit\n return scored.sort((a, b) => b.score - a.score).slice(0, limit);\n }\n\n /**\n * Compute urgency score for a review card.\n *\n * Two factors:\n * 1. Relative overdueness = hoursOverdue / intervalHours\n * - 2 days overdue on 3-day interval = 0.67 (urgent)\n * - 2 days overdue on 180-day interval = 0.01 (not urgent)\n *\n * 2. Interval recency factor = 0.3 + 0.7 * exp(-intervalHours / 720)\n * - 24h interval → ~1.0 (very recent learning)\n * - 30 days (720h) → ~0.56\n * - 180 days → ~0.30\n *\n * Combined: base 0.5 + weighted average of factors * 0.45\n * Result range: approximately 0.5 to 0.95\n */\n private computeUrgencyScore(\n review: ScheduledCard,\n now: moment.Moment\n ): { score: number; reason: string } {\n const scheduledAt = moment.utc(review.scheduledAt);\n const due = moment.utc(review.reviewTime);\n\n // Interval = time between scheduling and due date (minimum 1 hour to avoid division issues)\n const intervalHours = Math.max(1, due.diff(scheduledAt, 'hours'));\n const hoursOverdue = now.diff(due, 'hours');\n\n // Relative overdueness: how late relative to the interval\n const relativeOverdue = hoursOverdue / intervalHours;\n\n // Interval recency factor: shorter intervals = more urgent\n // Exponential decay with 720h (30 days) as the characteristic time\n const recencyFactor = 0.3 + 0.7 * Math.exp(-intervalHours / 720);\n\n // Combined urgency: weighted average of relative overdue and recency\n // Clamp relative overdue contribution to [0, 1] to avoid runaway scores\n const overdueContribution = Math.min(1.0, Math.max(0, relativeOverdue));\n const urgency = overdueContribution * 0.5 + recencyFactor * 0.5;\n\n // Final score: base 0.5 + urgency contribution, capped at 0.95\n const score = Math.min(0.95, 0.5 + urgency * 0.45);\n\n const reason =\n `${Math.round(hoursOverdue)}h overdue (interval: ${Math.round(intervalHours)}h, ` +\n `relative: ${relativeOverdue.toFixed(2)}), recency: ${recencyFactor.toFixed(2)}, review`;\n\n return { score, reason };\n }\n}\n","import type { WeightedCard } from '../index';\nimport type { CardFilter, FilterContext } from './types';\n\n// ============================================================================\n// ELO DISTANCE FILTER\n// ============================================================================\n//\n// Penalizes cards that are far from the user's current ELO using a smooth curve.\n//\n// This filter addresses cross-strategy coordination:\n// - SRS generates reviews based on scheduling\n// - But some scheduled cards may be \"below\" the user's current level\n// - Or \"above\" (shouldn't happen often, but possible)\n//\n// By applying ELO distance penalties, we can:\n// - Deprioritize reviews the user has \"moved beyond\"\n// - Deprioritize cards that are too hard for current skill level\n//\n// The penalty curve is smooth (no discontinuities) using a Gaussian-like decay.\n//\n// ============================================================================\n\n/**\n * Configuration for the ELO distance filter.\n */\nexport interface EloDistanceConfig {\n /**\n * The ELO distance at which the multiplier is ~0.6 (one standard deviation).\n * Default: 200 ELO points.\n *\n * - At distance 0: multiplier ≈ 1.0\n * - At distance = halfLife: multiplier ≈ 0.6\n * - At distance = 2 * halfLife: multiplier ≈ 0.37\n * - At distance = 3 * halfLife: multiplier ≈ 0.22\n */\n halfLife?: number;\n\n /**\n * Minimum multiplier (floor) to prevent scores from going too low.\n * Default: 0.3\n */\n minMultiplier?: number;\n\n /**\n * Maximum multiplier (ceiling). Usually 1.0 (no boost for close cards).\n * Default: 1.0\n */\n maxMultiplier?: number;\n}\n\nconst DEFAULT_HALF_LIFE = 200;\nconst DEFAULT_MIN_MULTIPLIER = 0.3;\nconst DEFAULT_MAX_MULTIPLIER = 1.0;\n\n/**\n * Compute the multiplier for a given ELO distance using Gaussian decay.\n *\n * Formula: minMultiplier + (maxMultiplier - minMultiplier) * exp(-(distance/halfLife)^2)\n *\n * This produces a smooth bell curve centered at distance=0:\n * - At distance 0: multiplier = maxMultiplier (1.0)\n * - As distance increases: multiplier smoothly decays toward minMultiplier\n * - No discontinuities or sudden jumps\n */\nfunction computeMultiplier(\n distance: number,\n halfLife: number,\n minMultiplier: number,\n maxMultiplier: number\n): number {\n // Gaussian decay: exp(-(d/h)^2)\n const normalizedDistance = distance / halfLife;\n const decay = Math.exp(-(normalizedDistance * normalizedDistance));\n\n // Scale between min and max\n return minMultiplier + (maxMultiplier - minMultiplier) * decay;\n}\n\n/**\n * Create an ELO distance filter.\n *\n * Penalizes cards that are far from the user's current ELO level\n * using a smooth Gaussian decay curve. No discontinuities.\n *\n * @param config - Optional configuration for the decay curve\n * @returns A CardFilter that applies ELO distance penalties\n */\nexport function createEloDistanceFilter(config?: EloDistanceConfig): CardFilter {\n const halfLife = config?.halfLife ?? DEFAULT_HALF_LIFE;\n const minMultiplier = config?.minMultiplier ?? DEFAULT_MIN_MULTIPLIER;\n const maxMultiplier = config?.maxMultiplier ?? DEFAULT_MAX_MULTIPLIER;\n\n return {\n name: 'ELO Distance Filter',\n\n async transform(cards: WeightedCard[], context: FilterContext): Promise<WeightedCard[]> {\n const { course, userElo } = context;\n\n // Batch fetch ELO data for all cards\n const cardIds = cards.map((c) => c.cardId);\n const cardElos = await course.getCardEloData(cardIds);\n\n return cards.map((card, i) => {\n const cardElo = cardElos[i]?.global?.score ?? 1000;\n const distance = Math.abs(cardElo - userElo);\n const multiplier = computeMultiplier(distance, halfLife, minMultiplier, maxMultiplier);\n const newScore = card.score * multiplier;\n\n const action = multiplier < maxMultiplier - 0.01 ? 'penalized' : 'passed';\n\n return {\n ...card,\n score: newScore,\n provenance: [\n ...card.provenance,\n {\n strategy: 'eloDistance',\n strategyName: 'ELO Distance Filter',\n strategyId: 'ELO_DISTANCE_FILTER',\n action,\n score: newScore,\n reason: `ELO distance ${Math.round(distance)} (card: ${Math.round(cardElo)}, user: ${Math.round(userElo)}) → ${multiplier.toFixed(2)}x`,\n },\n ],\n };\n });\n },\n };\n}\n\n// Export defaults for testing\nexport { DEFAULT_HALF_LIFE, DEFAULT_MIN_MULTIPLIER, DEFAULT_MAX_MULTIPLIER };\n","import { Navigators } from './index';\nimport { Pipeline } from './Pipeline';\nimport CompositeGenerator from './generators/CompositeGenerator';\nimport ELONavigator from './generators/elo';\nimport SRSNavigator from './generators/srs';\nimport { createEloDistanceFilter } from './filters/eloDistance';\nimport type { ContentNavigationStrategyData } from '../types/contentNavigationStrategy';\nimport { DocType } from '../types/types-legacy';\nimport type { CourseDBInterface, UserDBInterface } from '../interfaces';\n\n/**\n * Default navigation pipeline configuration.\n *\n * This module provides factory functions for creating the canonical default\n * navigation pipeline used by both CouchDB and static course implementations.\n */\n\n/**\n * Create default ELO navigation strategy data.\n * Used when no custom strategies are configured.\n *\n * @param courseId - The course ID to associate with this strategy\n * @returns Strategy data for default ELO navigation\n */\nexport function createDefaultEloStrategy(courseId: string): ContentNavigationStrategyData {\n return {\n _id: 'NAVIGATION_STRATEGY-ELO-default',\n docType: DocType.NAVIGATION_STRATEGY,\n name: 'ELO (default)',\n description: 'Default ELO-based navigation strategy for new cards',\n implementingClass: Navigators.ELO,\n course: courseId,\n serializedData: '',\n };\n}\n\n/**\n * Create default SRS navigation strategy data.\n * Used when no custom strategies are configured.\n *\n * @param courseId - The course ID to associate with this strategy\n * @returns Strategy data for default SRS navigation\n */\nexport function createDefaultSrsStrategy(courseId: string): ContentNavigationStrategyData {\n return {\n _id: 'NAVIGATION_STRATEGY-SRS-default',\n docType: DocType.NAVIGATION_STRATEGY,\n name: 'SRS (default)',\n description: 'Default SRS-based navigation strategy for reviews',\n implementingClass: Navigators.SRS,\n course: courseId,\n serializedData: '',\n };\n}\n\n/**\n * Creates the default navigation pipeline for courses with no configured strategies.\n *\n * Default: Pipeline(Composite(ELO, SRS), [eloDistanceFilter])\n * - ELO generator: scores new cards by skill proximity\n * - SRS generator: scores reviews by overdueness and interval recency\n * - ELO distance filter: penalizes cards far from user's current level\n *\n * This is the canonical default configuration used when:\n * - No navigation strategy documents exist in the course\n * - PipelineAssembler fails to build from strategy documents\n *\n * @param user - User database interface for accessing user state\n * @param course - Course database interface for accessing course data\n * @returns Configured Pipeline ready for use\n */\nexport function createDefaultPipeline(\n user: UserDBInterface,\n course: CourseDBInterface\n): Pipeline {\n const courseId = course.getCourseID();\n const eloNavigator = new ELONavigator(user, course, createDefaultEloStrategy(courseId));\n const srsNavigator = new SRSNavigator(user, course, createDefaultSrsStrategy(courseId));\n\n const compositeGenerator = new CompositeGenerator([eloNavigator, srsNavigator]);\n const eloDistanceFilter = createEloDistanceFilter();\n\n return new Pipeline(compositeGenerator, [eloDistanceFilter], user, course);\n}\n","import { CourseDBInterface, CourseInfo, CoursesDBInterface, UserDBInterface } from '@db/core';\nimport {\n CourseConfig,\n CourseElo,\n DataShape,\n EloToNumber,\n Status,\n blankCourseElo,\n toCourseElo,\n} from '@vue-skuilder/common';\n\nimport { filterAllDocsByPrefix, getCourseDB, getCourseDoc, getCourseDocs } from '.';\nimport UpdateQueue from './updateQueue';\nimport { StudySessionItem } from '../../core/interfaces/contentSource';\nimport {\n CardData,\n DocType,\n QualifiedCardID,\n SkuilderCourseData,\n Tag,\n TagStub,\n DocTypePrefixes,\n} from '../../core/types/types-legacy';\nimport { logger } from '../../util/logger';\nimport { GET_CACHED } from './clientCache';\nimport { addNote55, addTagToCard, getCredentialledCourseConfig, getTagID } from './courseAPI';\nimport { DataLayerResult } from '@db/core/types/db';\nimport { PouchError } from './types';\nimport CourseLookup from './courseLookupDB';\nimport { ContentNavigationStrategyData } from '@db/core/types/contentNavigationStrategy';\nimport { ContentNavigator, Navigators, WeightedCard } from '@db/core/navigators';\nimport { PipelineAssembler } from '@db/core/navigators/PipelineAssembler';\nimport { createDefaultPipeline } from '@db/core/navigators/defaults';\n\nexport class CoursesDB implements CoursesDBInterface {\n _courseIDs: string[] | undefined;\n\n constructor(courseIDs?: string[]) {\n if (courseIDs && courseIDs.length > 0) {\n this._courseIDs = courseIDs;\n } else {\n this._courseIDs = undefined;\n }\n }\n\n public async getCourseList(): Promise<CourseConfig[]> {\n let crsList = await CourseLookup.allCourseWare();\n logger.debug(`AllCourses: ${crsList.map((c) => c.name + ', ' + c._id + '\\n\\t')}`);\n if (this._courseIDs) {\n crsList = crsList.filter((c) => this._courseIDs!.includes(c._id));\n }\n\n logger.debug(`AllCourses.filtered: ${crsList.map((c) => c.name + ', ' + c._id + '\\n\\t')}`);\n\n const cfgs = await Promise.all(\n crsList.map(async (c) => {\n try {\n const cfg = await getCredentialledCourseConfig(c._id);\n logger.debug(`Found cfg: ${JSON.stringify(cfg)}`);\n return cfg;\n } catch (e) {\n logger.warn(`Error fetching cfg for course ${c.name}, ${c._id}: ${e}`);\n return undefined;\n }\n })\n );\n return cfgs.filter((c) => !!c);\n }\n\n async getCourseConfig(courseId: string): Promise<CourseConfig> {\n if (this._courseIDs && this._courseIDs.length && !this._courseIDs.includes(courseId)) {\n throw new Error(`Course ${courseId} not in course list`);\n }\n\n const cfg = await getCredentialledCourseConfig(courseId);\n if (cfg === undefined) {\n throw new Error(`Error fetching cfg for course ${courseId}`);\n } else {\n return cfg;\n }\n }\n\n public async disambiguateCourse(courseId: string, disambiguator: string): Promise<void> {\n await CourseLookup.updateDisambiguator(courseId, disambiguator);\n }\n}\n\nfunction randIntWeightedTowardZero(n: number) {\n return Math.floor(Math.random() * Math.random() * Math.random() * n);\n}\n\nexport class CourseDB implements CourseDBInterface {\n // private log(msg: string): void {\n // log(`CourseLog: ${this.id}\\n ${msg}`);\n // }\n\n private db: PouchDB.Database;\n private id: string;\n private _getCurrentUser: () => Promise<UserDBInterface>;\n private updateQueue: UpdateQueue;\n\n constructor(id: string, userLookup: () => Promise<UserDBInterface>) {\n this.id = id;\n this.db = getCourseDB(this.id);\n this._getCurrentUser = userLookup;\n this.updateQueue = new UpdateQueue(this.db);\n }\n\n public getCourseID(): string {\n return this.id;\n }\n\n public async getCourseInfo(): Promise<CourseInfo> {\n const cardCount = (\n await this.db.find({\n selector: {\n docType: DocType.CARD,\n },\n limit: 1000,\n })\n ).docs.length;\n\n return {\n cardCount,\n registeredUsers: 0,\n };\n }\n\n public async getInexperiencedCards(limit: number = 2) {\n return (\n await this.db.query('cardsByInexperience', {\n limit,\n })\n ).rows.map((r) => {\n const ret = {\n courseId: this.id,\n cardId: r.id,\n count: r.key,\n elo: r.value,\n };\n return ret;\n });\n }\n\n public async getCardsByEloLimits(\n options: {\n low: number;\n high: number;\n limit: number;\n page: number;\n } = {\n low: 0,\n high: Number.MIN_SAFE_INTEGER,\n limit: 25,\n page: 0,\n }\n ) {\n return (\n await this.db.query('elo', {\n startkey: options.low,\n endkey: options.high,\n limit: options.limit,\n skip: options.limit * options.page,\n })\n ).rows.map((r) => {\n return `${this.id}-${r.id}-${r.key}`;\n });\n }\n public async getCardEloData(id: string[]): Promise<CourseElo[]> {\n const docs = await this.db.allDocs<CardData>({\n keys: id,\n include_docs: true,\n });\n const ret: CourseElo[] = [];\n docs.rows.forEach((r) => {\n // [ ] remove these ts-ignore directives.\n if (isSuccessRow(r)) {\n if (r.doc && r.doc.elo) {\n ret.push(toCourseElo(r.doc.elo));\n } else {\n logger.warn('no elo data for card: ' + r.id);\n ret.push(blankCourseElo());\n }\n } else {\n logger.warn('no elo data for card: ' + JSON.stringify(r));\n ret.push(blankCourseElo());\n }\n });\n return ret;\n }\n\n /**\n * Returns the lowest and highest `global` ELO ratings in the course\n */\n public async getELOBounds() {\n const [low, high] = await Promise.all([\n (\n await this.db.query('elo', {\n startkey: 0,\n limit: 1,\n include_docs: false,\n })\n ).rows[0].key,\n (\n await this.db.query('elo', {\n limit: 1,\n descending: true,\n startkey: 100_000,\n })\n ).rows[0].key,\n ]);\n\n return {\n low: low,\n high: high,\n };\n }\n\n public async removeCard(id: string) {\n const doc = await this.db.get<CardData>(id);\n if (!doc.docType || !(doc.docType === DocType.CARD)) {\n throw new Error(`failed to remove ${id} from course ${this.id}. id does not point to a card`);\n }\n\n // Remove card from all associated tags before deleting the card\n try {\n const appliedTags = await this.getAppliedTags(id);\n const results = await Promise.allSettled(\n appliedTags.rows.map(async (tagRow) => {\n const tagId = tagRow.id;\n await this.removeTagFromCard(id, tagId);\n })\n );\n\n // Log any individual tag cleanup failures\n results.forEach((result, index) => {\n if (result.status === 'rejected') {\n const tagId = appliedTags.rows[index].id;\n logger.error(`Failed to remove card ${id} from tag ${tagId}: ${result.reason}`);\n }\n });\n } catch (error) {\n logger.error(`Error removing card ${id} from tags: ${error}`);\n // Continue with card deletion even if tag cleanup fails\n }\n\n return this.db.remove(doc);\n }\n\n public async getCardDisplayableDataIDs(id: string[]) {\n logger.debug(id.join(', '));\n const cards = await this.db.allDocs<CardData>({\n keys: id,\n include_docs: true,\n });\n const ret: { [card: string]: string[] } = {};\n cards.rows.forEach((r) => {\n if (isSuccessRow(r)) {\n ret[r.id] = r.doc!.id_displayable_data;\n }\n });\n\n return ret;\n }\n\n async getCardsByELO(elo: number, cardLimit?: number) {\n elo = parseInt(elo as any);\n const limit = cardLimit ? cardLimit : 25;\n\n const below: PouchDB.Query.Response<object> = await this.db.query('elo', {\n limit: Math.ceil(limit / 2),\n startkey: elo,\n descending: true,\n });\n\n const aboveLimit = limit - below.rows.length;\n\n const above: PouchDB.Query.Response<object> = await this.db.query('elo', {\n limit: aboveLimit,\n startkey: elo + 1,\n });\n // logger.log(JSON.stringify(below));\n\n let cards = below.rows;\n cards = cards.concat(above.rows);\n\n const ret = cards\n .sort((a, b) => {\n const s = Math.abs(a.key - elo) - Math.abs(b.key - elo);\n if (s === 0) {\n return Math.random() - 0.5;\n } else {\n return s;\n }\n })\n .map((c) => {\n return {\n courseID: this.id,\n cardID: c.id,\n elo: c.key,\n };\n });\n\n const str = `below:\\n${below.rows.map((r) => `\\t${r.id}-${r.key}\\n`)}\n\nabove:\\n${above.rows.map((r) => `\\t${r.id}-${r.key}\\n`)}`;\n\n logger.debug(`Getting ${limit} cards centered around elo: ${elo}:\\n\\n` + str);\n\n return ret;\n }\n\n async getCourseConfig(): Promise<CourseConfig> {\n const ret = await getCredentialledCourseConfig(this.id);\n if (ret) {\n return ret;\n } else {\n throw new Error(`Course config not found for course ID: ${this.id}`);\n }\n }\n\n async updateCourseConfig(cfg: CourseConfig): Promise<PouchDB.Core.Response> {\n logger.debug(`Updating: ${JSON.stringify(cfg)}`);\n // write both to the course DB:\n try {\n return await updateCredentialledCourseConfig(this.id, cfg);\n } catch (error) {\n logger.error(`Error updating course config in course DB: ${error}`);\n throw error;\n }\n }\n\n async updateCardElo(cardId: string, elo: CourseElo): Promise<PouchDB.Core.Response> {\n if (!elo) {\n throw new Error(`Cannot update card elo with null or undefined value for card ID: ${cardId}`);\n }\n\n try {\n const result = await this.updateQueue.update<\n CardData & PouchDB.Core.GetMeta & PouchDB.Core.IdMeta\n >(cardId, (card) => {\n logger.debug(`Replacing ${JSON.stringify(card.elo)} with ${JSON.stringify(elo)}`);\n card.elo = elo;\n return card;\n });\n return { ok: true, id: cardId, rev: result._rev };\n } catch (error) {\n logger.error(`Failed to update card elo for card ID: ${cardId}`, error);\n throw new Error(`Failed to update card elo for card ID: ${cardId}`);\n }\n }\n\n async getAppliedTags(cardId: string): Promise<PouchDB.Query.Response<TagStub>> {\n const ret = await getAppliedTags(this.id, cardId);\n if (ret) {\n return ret;\n } else {\n throw new Error(`Failed to find tags for card ${this.id}-${cardId}`);\n }\n }\n\n async getAppliedTagsBatch(cardIds: string[]): Promise<Map<string, string[]>> {\n if (cardIds.length === 0) {\n return new Map();\n }\n\n const db = getCourseDB(this.id);\n const result = await db.query<TagStub>('getTags', {\n keys: cardIds,\n include_docs: false,\n });\n\n const tagsByCard = new Map<string, string[]>();\n\n // Initialize all requested cards with empty arrays\n for (const cardId of cardIds) {\n tagsByCard.set(cardId, []);\n }\n\n // Populate from query results\n for (const row of result.rows) {\n const cardId = row.key as string;\n const tagName = row.value?.name;\n if (tagName && tagsByCard.has(cardId)) {\n tagsByCard.get(cardId)!.push(tagName);\n }\n }\n\n return tagsByCard;\n }\n\n async addTagToCard(\n cardId: string,\n tagId: string,\n updateELO?: boolean\n ): Promise<PouchDB.Core.Response> {\n return await addTagToCard(\n this.id,\n cardId,\n tagId,\n (await this._getCurrentUser()).getUsername(),\n updateELO\n );\n }\n\n async removeTagFromCard(cardId: string, tagId: string): Promise<PouchDB.Core.Response> {\n return await removeTagFromCard(this.id, cardId, tagId);\n }\n\n async createTag(name: string, author: string): Promise<PouchDB.Core.Response> {\n return await createTag(this.id, name, author);\n }\n\n async getTag(tagId: string): Promise<PouchDB.Core.GetMeta & PouchDB.Core.Document<Tag>> {\n return await getTag(this.id, tagId);\n }\n\n async updateTag(tag: Tag): Promise<PouchDB.Core.Response> {\n if (tag.course !== this.id) {\n throw new Error(`Tag ${JSON.stringify(tag)} does not belong to course ${this.id}`);\n }\n\n return await updateTag(tag);\n }\n\n async getCourseTagStubs(): Promise<PouchDB.Core.AllDocsResponse<Tag>> {\n return getCourseTagStubs(this.id);\n }\n\n async addNote(\n codeCourse: string,\n shape: DataShape,\n data: unknown,\n author: string,\n tags: string[],\n uploads?: { [key: string]: PouchDB.Core.FullAttachment },\n elo: CourseElo = blankCourseElo()\n ): Promise<DataLayerResult> {\n try {\n const resp = await addNote55(this.id, codeCourse, shape, data, author, tags, uploads, elo);\n if (resp.ok) {\n // Check if card creation failed (property added by addNote55)\n if ((resp as any).cardCreationFailed) {\n logger.warn(\n `[courseDB.addNote] Note added but card creation failed: ${\n (resp as any).cardCreationError\n }`\n );\n return {\n status: Status.error,\n message: `Note was added but no cards were created: ${(resp as any).cardCreationError}`,\n id: resp.id,\n };\n }\n return {\n status: Status.ok,\n message: '',\n id: resp.id,\n };\n } else {\n return {\n status: Status.error,\n message: 'Unexpected error adding note',\n };\n }\n } catch (e) {\n const err = e as PouchDB.Core.Error;\n logger.error(\n `[addNote] error ${err.name}\\n\\treason: ${err.reason}\\n\\tmessage: ${err.message}`\n );\n return {\n status: Status.error,\n message: `Error adding note to course. ${(e as PouchError).reason || err.message}`,\n };\n }\n }\n\n async getCourseDoc<T extends SkuilderCourseData>(\n id: string,\n options?: PouchDB.Core.GetOptions\n ): Promise<PouchDB.Core.GetMeta & PouchDB.Core.Document<T>> {\n return await getCourseDoc(this.id, id, options);\n }\n\n async getCourseDocs<T extends SkuilderCourseData>(\n ids: string[],\n options: PouchDB.Core.AllDocsOptions = {}\n ): Promise<PouchDB.Core.AllDocsWithKeysResponse<{} & T>> {\n return await getCourseDocs(this.id, ids, options);\n }\n\n ////////////////////////////////////\n // NavigationStrategyManager implementation\n ////////////////////////////////////\n\n getNavigationStrategy(id: string): Promise<ContentNavigationStrategyData> {\n logger.debug(`[courseDB] Getting navigation strategy: ${id}`);\n\n if (id == '') {\n const strategy: ContentNavigationStrategyData = {\n _id: 'NAVIGATION_STRATEGY-ELO',\n docType: DocType.NAVIGATION_STRATEGY,\n name: 'ELO',\n description: 'ELO-based navigation strategy for ordering content by difficulty',\n implementingClass: Navigators.ELO,\n course: this.id,\n serializedData: '', // serde is a noop for ELO navigator.\n };\n return Promise.resolve(strategy);\n } else {\n return this.db.get(id);\n }\n }\n\n async getAllNavigationStrategies(): Promise<ContentNavigationStrategyData[]> {\n const prefix = DocTypePrefixes[DocType.NAVIGATION_STRATEGY];\n const result = await this.db.allDocs<ContentNavigationStrategyData>({\n startkey: prefix,\n endkey: `${prefix}\\ufff0`,\n include_docs: true,\n });\n return result.rows.map((row) => row.doc!);\n }\n\n async addNavigationStrategy(data: ContentNavigationStrategyData): Promise<void> {\n logger.debug(`[courseDB] Adding navigation strategy: ${data._id}`);\n // // For now, just log the data and return success\n // logger.debug(JSON.stringify(data));\n return this.db.put(data).then(() => {});\n }\n updateNavigationStrategy(id: string, data: ContentNavigationStrategyData): Promise<void> {\n logger.debug(`[courseDB] Updating navigation strategy: ${id}`);\n // For now, just log the data and return success\n logger.debug(JSON.stringify(data));\n return Promise.resolve();\n }\n\n /**\n * Creates an instantiated navigator for this course.\n *\n * Handles multiple generators by wrapping them in CompositeGenerator.\n * This is the preferred method for getting a ready-to-use navigator.\n *\n * @param user - User database interface\n * @returns Instantiated ContentNavigator ready for use\n */\n async createNavigator(user: UserDBInterface): Promise<ContentNavigator> {\n try {\n const allStrategies = await this.getAllNavigationStrategies();\n\n if (allStrategies.length === 0) {\n // No strategies configured: use default Pipeline(Composite(ELO, SRS), [eloDistanceFilter])\n logger.debug(\n '[courseDB] No strategy documents found, using default Pipeline(Composite(ELO, SRS), [eloDistanceFilter])'\n );\n return createDefaultPipeline(user, this);\n }\n\n // Use PipelineAssembler to build a Pipeline from strategy documents\n const assembler = new PipelineAssembler();\n const { pipeline, generatorStrategies, filterStrategies, warnings } =\n await assembler.assemble({\n strategies: allStrategies,\n user,\n course: this,\n });\n\n // Log any warnings from assembly\n for (const warning of warnings) {\n logger.warn(`[PipelineAssembler] ${warning}`);\n }\n\n if (!pipeline) {\n // Assembly failed - fall back to default\n logger.debug('[courseDB] Pipeline assembly failed, using default pipeline');\n return createDefaultPipeline(user, this);\n }\n\n logger.debug(\n `[courseDB] Using assembled pipeline with ${generatorStrategies.length} generator(s) and ${filterStrategies.length} filter(s)`\n );\n return pipeline;\n } catch (e) {\n logger.error(`[courseDB] Error creating navigator: ${e}`);\n throw e;\n }\n }\n\n ////////////////////////////////////\n // END NavigationStrategyManager implementation\n ////////////////////////////////////\n\n ////////////////////////////////////\n // StudyContentSource implementation\n ////////////////////////////////////\n\n /**\n * Get cards with suitability scores for presentation.\n *\n * This is the PRIMARY API for content sources going forward. Delegates to the\n * course's configured NavigationStrategy to get scored candidates.\n *\n * @param limit - Maximum number of cards to return\n * @returns Cards sorted by score descending\n */\n public async getWeightedCards(limit: number): Promise<WeightedCard[]> {\n const u = await this._getCurrentUser();\n\n try {\n const navigator = await this.createNavigator(u);\n return navigator.getWeightedCards(limit);\n } catch (e) {\n logger.error(`[courseDB] Error getting weighted cards: ${e}`);\n throw e;\n }\n }\n\n public async getCardsCenteredAtELO(\n options: {\n limit: number;\n elo: 'user' | 'random' | number;\n } = {\n limit: 99,\n elo: 'user',\n },\n filter?: (a: QualifiedCardID) => boolean\n ): Promise<StudySessionItem[]> {\n let targetElo: number;\n\n if (options.elo === 'user') {\n const u = await this._getCurrentUser();\n\n targetElo = -1;\n try {\n const courseDoc = (await u.getCourseRegistrationsDoc()).courses.find((c) => {\n return c.courseID === this.id;\n })!;\n targetElo = EloToNumber(courseDoc.elo);\n } catch {\n targetElo = 1000;\n }\n } else if (options.elo === 'random') {\n const bounds = await GET_CACHED(`elo-bounds-${this.id}`, () => this.getELOBounds());\n targetElo = Math.round(bounds.low + Math.random() * (bounds.high - bounds.low));\n // logger.log(`Picked ${targetElo} from [${bounds.low}, ${bounds.high}]`);\n } else {\n targetElo = options.elo;\n }\n\n let cards: (QualifiedCardID & { elo?: number })[] = [];\n let mult: number = 4;\n let previousCount: number = -1;\n let newCount: number = 0;\n\n while (cards.length < options.limit && newCount !== previousCount) {\n cards = await this.getCardsByELO(targetElo, mult * options.limit);\n previousCount = newCount;\n newCount = cards.length;\n\n logger.debug(`Found ${cards.length} elo neighbor cards...`);\n\n if (filter) {\n cards = cards.filter(filter);\n logger.debug(`Filtered to ${cards.length} cards...`);\n }\n\n mult *= 2;\n }\n\n const selectedCards: {\n courseID: string;\n cardID: string;\n elo?: number;\n }[] = [];\n\n while (selectedCards.length < options.limit && cards.length > 0) {\n const index = randIntWeightedTowardZero(cards.length);\n const card = cards.splice(index, 1)[0];\n selectedCards.push(card);\n }\n\n return selectedCards.map((c) => {\n return {\n courseID: this.id,\n cardID: c.cardID,\n contentSourceType: 'course',\n contentSourceID: this.id,\n elo: c.elo,\n status: 'new',\n };\n });\n }\n\n // Admin search methods\n public async searchCards(query: string): Promise<any[]> {\n logger.log(`[CourseDB ${this.id}] Searching for: \"${query}\"`);\n\n // Try multiple search approaches\n let displayableData;\n\n try {\n // Try regex search on the correct data structure: data[0].data\n displayableData = await this.db.find({\n selector: {\n docType: 'DISPLAYABLE_DATA',\n 'data.0.data': { $regex: `.*${query}.*` },\n },\n });\n logger.log(`[CourseDB ${this.id}] Regex search on data[0].data successful`);\n } catch (regexError) {\n logger.log(\n `[CourseDB ${this.id}] Regex search failed, falling back to manual search:`,\n regexError\n );\n\n // Fallback: get all displayable data and filter manually\n const allDisplayable = await this.db.find({\n selector: {\n docType: 'DISPLAYABLE_DATA',\n },\n });\n\n logger.log(\n `[CourseDB ${this.id}] Retrieved ${allDisplayable.docs.length} documents for manual filtering`\n );\n\n displayableData = {\n docs: allDisplayable.docs.filter((doc) => {\n // Search entire document as JSON string - inclusive approach for admin tool\n const docString = JSON.stringify(doc).toLowerCase();\n const match = docString.includes(query.toLowerCase());\n if (match) {\n logger.log(`[CourseDB ${this.id}] Manual match found in document: ${doc._id}`);\n }\n return match;\n }),\n };\n }\n\n logger.log(\n `[CourseDB ${this.id}] Found ${displayableData.docs.length} displayable data documents`\n );\n\n if (displayableData.docs.length === 0) {\n // Debug: Let's see what displayable data exists\n const allDisplayableData = await this.db.find({\n selector: {\n docType: 'DISPLAYABLE_DATA',\n },\n limit: 5, // Just sample a few\n });\n\n logger.log(\n `[CourseDB ${this.id}] Sample displayable data:`,\n allDisplayableData.docs.map((d) => ({\n id: d._id,\n docType: (d as any).docType,\n dataStructure: (d as any).data ? Object.keys((d as any).data) : 'no data field',\n dataContent: (d as any).data,\n fullDoc: d,\n }))\n );\n }\n\n const allResults: any[] = [];\n\n for (const dd of displayableData.docs) {\n const cards = await this.db.find({\n selector: {\n docType: 'CARD',\n id_displayable_data: { $in: [dd._id] },\n },\n });\n\n logger.log(\n `[CourseDB ${this.id}] Displayable data ${dd._id} linked to ${cards.docs.length} cards`\n );\n allResults.push(...cards.docs);\n }\n\n logger.log(`[CourseDB ${this.id}] Total cards found: ${allResults.length}`);\n return allResults;\n }\n\n public async find(\n request: PouchDB.Find.FindRequest<any>\n ): Promise<PouchDB.Find.FindResponse<any>> {\n return this.db.find(request);\n }\n}\n\n/**\n * Returns a list of registered datashapes for the specified\n * course.\n * @param courseID The ID of the course\n */\nexport async function getCourseDataShapes(courseID: string) {\n const cfg = await getCredentialledCourseConfig(courseID);\n return cfg!.dataShapes;\n}\n\nexport async function getCredentialledDataShapes(courseID: string) {\n const cfg = await getCredentialledCourseConfig(courseID);\n\n return cfg.dataShapes;\n}\n\nexport async function getCourseQuestionTypes(courseID: string) {\n const cfg = await getCredentialledCourseConfig(courseID);\n return cfg!.questionTypes;\n}\n\n// todo: this is actually returning full tag docs now.\n// - performance issue when tags have lots of\n// applied docs\n// - will require a computed couch DB view\nexport async function getCourseTagStubs(\n courseID: string\n): Promise<PouchDB.Core.AllDocsResponse<Tag>> {\n logger.debug(`Getting tag stubs for course: ${courseID}`);\n const stubs = await filterAllDocsByPrefix<Tag>(\n getCourseDB(courseID),\n DocType.TAG.valueOf() + '-'\n );\n\n stubs.rows.forEach((row) => {\n logger.debug(`\\tTag stub for doc: ${row.id}`);\n });\n\n return stubs;\n}\n\nexport async function deleteTag(courseID: string, tagName: string) {\n tagName = getTagID(tagName);\n const courseDB = getCourseDB(courseID);\n const doc = await courseDB.get<Tag>(DocType.TAG.valueOf() + '-' + tagName);\n const resp = await courseDB.remove(doc);\n return resp;\n}\n\nexport async function createTag(courseID: string, tagName: string, author: string) {\n logger.debug(`Creating tag: ${tagName}...`);\n const tagID = getTagID(tagName);\n const courseDB = getCourseDB(courseID);\n const resp = await courseDB.put<Tag>({\n course: courseID,\n docType: DocType.TAG,\n name: tagName,\n snippet: '',\n taggedCards: [],\n wiki: '',\n author,\n _id: tagID,\n });\n return resp;\n}\n\nexport async function updateTag(tag: Tag) {\n const prior = await getTag(tag.course, tag.name);\n return await getCourseDB(tag.course).put<Tag>({\n ...tag,\n _rev: prior._rev,\n });\n}\n\nexport async function getTag(courseID: string, tagName: string) {\n const tagID = getTagID(tagName);\n const courseDB = getCourseDB(courseID);\n return courseDB.get<Tag>(tagID);\n}\n\nexport async function removeTagFromCard(courseID: string, cardID: string, tagID: string) {\n // todo: possible future perf. hit if tags have large #s of taggedCards.\n // In this case, should be converted to a server-request\n tagID = getTagID(tagID);\n const courseDB = getCourseDB(courseID);\n const tag = await courseDB.get<Tag>(tagID);\n tag.taggedCards = tag.taggedCards.filter((taggedID) => {\n return cardID !== taggedID;\n });\n return courseDB.put<Tag>(tag);\n}\n\n/**\n * Returns an array of ancestor tag IDs, where:\n * return[0] = parent,\n * return[1] = grandparent,\n * return[2] = great grandparent,\n * etc.\n *\n * If ret is empty, the tag itself is a root\n */\nexport function getAncestorTagIDs(courseID: string, tagID: string): string[] {\n tagID = getTagID(tagID);\n const split = tagID.split('>');\n if (split.length === 1) {\n return [];\n } else {\n split.pop();\n const parent = split.join('>');\n return [parent].concat(getAncestorTagIDs(courseID, parent));\n }\n}\n\nexport async function getChildTagStubs(courseID: string, tagID: string) {\n return await filterAllDocsByPrefix(getCourseDB(courseID), tagID + '>');\n}\n\nexport async function getAppliedTags(id_course: string, id_card: string) {\n const db = getCourseDB(id_course);\n\n const result = await db.query<TagStub>('getTags', {\n startkey: id_card,\n endkey: id_card,\n // include_docs: true\n });\n\n // log(`getAppliedTags looked up: ${id_card}`);\n // log(`getAppliedTags returning: ${JSON.stringify(result)}`);\n\n return result;\n}\n\nexport async function updateCardElo(courseID: string, cardID: string, elo: CourseElo) {\n if (elo) {\n // checking against null, undefined, NaN\n const cDB = getCourseDB(courseID);\n const card = await cDB.get<CardData>(cardID);\n logger.debug(`Replacing ${JSON.stringify(card.elo)} with ${JSON.stringify(elo)}`);\n card.elo = elo;\n return cDB.put(card); // race conditions - is it important? probably not (net-zero effect)\n }\n}\n\nexport async function updateCredentialledCourseConfig(courseID: string, config: CourseConfig) {\n logger.debug(`Updating course config:\n\n${JSON.stringify(config)}\n`);\n\n const db = getCourseDB(courseID);\n const old = await getCredentialledCourseConfig(courseID);\n\n return await db.put<CourseConfig>({\n ...config,\n _rev: (old as any)._rev,\n });\n}\n\nfunction isSuccessRow<T>(\n row:\n | {\n key: PouchDB.Core.DocumentKey;\n error: 'not_found';\n }\n | {\n doc?: PouchDB.Core.ExistingDocument<PouchDB.Core.AllDocsMeta & T> | null | undefined;\n id: PouchDB.Core.DocumentId;\n key: PouchDB.Core.DocumentKey;\n value: {\n rev: PouchDB.Core.RevisionId;\n deleted?: boolean | undefined;\n };\n }\n): row is {\n doc?: PouchDB.Core.ExistingDocument<PouchDB.Core.AllDocsMeta & T> | null | undefined;\n id: PouchDB.Core.DocumentId;\n key: PouchDB.Core.DocumentKey;\n value: {\n rev: PouchDB.Core.RevisionId;\n deleted?: boolean | undefined;\n };\n} {\n return 'doc' in row && row.doc !== null && row.doc !== undefined;\n}\n","import { StudyContentSource } from '@db/core/interfaces/contentSource';\nimport { WeightedCard } from '@db/core/navigators';\nimport { ClassroomConfig } from '@vue-skuilder/common';\nimport { ENV } from '@db/factory';\nimport { logger } from '@db/util/logger';\nimport moment from 'moment';\nimport pouch from './pouchdb-setup';\nimport { getStartAndEndKeys, createPouchDBConfig, REVIEW_TIME_FORMAT } from '.';\nimport { CourseDB, getTag } from './courseDB';\n\nimport { UserDBInterface } from '@db/core';\nimport {\n AssignedContent,\n AssignedCourse,\n AssignedTag,\n StudentClassroomDBInterface,\n TeacherClassroomDBInterface,\n} from '@db/core/interfaces/classroomDB';\n\nconst classroomLookupDBTitle = 'classdb-lookup';\nexport const CLASSROOM_CONFIG = 'ClassroomConfig';\n\nexport type ClassroomMessage = object;\n\nabstract class ClassroomDBBase {\n public _id!: string;\n protected _db!: PouchDB.Database;\n protected _cfg!: ClassroomConfig;\n protected _initComplete: boolean = false;\n\n protected readonly _content_prefix: string = 'content';\n protected get _content_searchkeys() {\n return getStartAndEndKeys(this._content_prefix);\n }\n\n protected abstract init(): Promise<void>;\n\n public async getAssignedContent(): Promise<AssignedContent[]> {\n logger.info(`Getting assigned content...`);\n // see couchdb docs 6.2.2:\n // Guide to Views -> Views Collation -> String Ranges\n const docRows = await this._db.allDocs<AssignedContent>({\n startkey: this._content_prefix,\n endkey: this._content_prefix + `\\ufff0`,\n include_docs: true,\n });\n\n const ret = docRows.rows.map((row) => {\n return row.doc!;\n });\n // logger.info(`Assigned content: ${JSON.stringify(ret)}`);\n\n return ret;\n }\n\n protected getContentId(content: AssignedContent): string {\n if (content.type === 'tag') {\n return `${this._content_prefix}-${content.courseID}-${content.tagID}`;\n } else {\n return `${this._content_prefix}-${content.courseID}`;\n }\n }\n\n public get ready(): boolean {\n return this._initComplete;\n }\n public getConfig(): ClassroomConfig {\n return this._cfg;\n }\n}\n\nexport class StudentClassroomDB\n extends ClassroomDBBase\n implements StudyContentSource, StudentClassroomDBInterface\n{\n // private readonly _prefix: string = 'content';\n private userMessages!: PouchDB.Core.Changes<object>;\n private _user: UserDBInterface;\n\n private constructor(classID: string, user: UserDBInterface) {\n super();\n this._id = classID;\n this._user = user;\n // init() is called explicitly in factory method, not in constructor\n }\n\n async init(): Promise<void> {\n const dbName = `classdb-student-${this._id}`;\n this._db = new pouch(\n ENV.COUCHDB_SERVER_PROTOCOL + '://' + ENV.COUCHDB_SERVER_URL + dbName,\n createPouchDBConfig()\n );\n try {\n const cfg = await this._db.get<ClassroomConfig>(CLASSROOM_CONFIG);\n this._cfg = cfg;\n this.userMessages = this._db.changes({\n since: 'now',\n live: true,\n include_docs: true,\n });\n this._initComplete = true;\n return;\n } catch (e) {\n throw new Error(`Error in StudentClassroomDB constructor: ${JSON.stringify(e)}`);\n }\n }\n\n public static async factory(classID: string, user: UserDBInterface): Promise<StudentClassroomDB> {\n const ret = new StudentClassroomDB(classID, user);\n await ret.init();\n return ret;\n }\n\n public setChangeFcn(f: (value: unknown) => object): void {\n // todo: make this into a view request, w/ the user's name attached\n // todo: requires creating the view doc on classroom create in /express\n void this.userMessages.on('change', f);\n }\n\n /**\n * Get cards with suitability scores for presentation.\n *\n * Gathers new cards from assigned content (courses, tags, cards) and\n * pending reviews scheduled for this classroom. Assigns score=1.0 to all.\n *\n * @param limit - Maximum number of cards to return\n * @returns Cards sorted by score descending (all scores = 1.0)\n */\n public async getWeightedCards(limit: number): Promise<WeightedCard[]> {\n const weighted: WeightedCard[] = [];\n\n // Get pending reviews for this classroom\n const allUserReviews = await this._user.getPendingReviews();\n const classroomReviews = allUserReviews.filter(\n (r) => r.scheduledFor === 'classroom' && r.schedulingAgentId === this._id\n );\n\n for (const r of classroomReviews) {\n weighted.push({\n cardId: r.cardId,\n courseId: r.courseId,\n score: 1.0,\n reviewID: r._id,\n provenance: [\n {\n strategy: 'classroom',\n strategyName: 'Classroom',\n strategyId: 'CLASSROOM',\n action: 'generated' as const,\n score: 1.0,\n reason: 'Classroom scheduled review',\n },\n ],\n });\n }\n\n // Get new cards from assigned content\n const activeCards = await this._user.getActiveCards();\n const activeCardIds = new Set(activeCards.map((ac) => ac.cardID));\n const now = moment.utc();\n const assigned = await this.getAssignedContent();\n const due = assigned.filter((c) => now.isAfter(moment.utc(c.activeOn, REVIEW_TIME_FORMAT)));\n\n logger.info(`[StudentClassroomDB] Due content: ${JSON.stringify(due)}`);\n\n for (const content of due) {\n if (content.type === 'course') {\n // Get weighted cards from the course directly\n const db = new CourseDB(content.courseID, async () => this._user);\n const courseCards = await db.getWeightedCards(limit);\n for (const card of courseCards) {\n if (!activeCardIds.has(card.cardId)) {\n weighted.push({\n ...card,\n provenance: [\n ...card.provenance,\n {\n strategy: 'classroom',\n strategyName: 'Classroom',\n strategyId: 'CLASSROOM',\n action: 'passed' as const,\n score: card.score,\n reason: `Assigned via classroom from course ${content.courseID}`,\n },\n ],\n });\n }\n }\n } else if (content.type === 'tag') {\n const tagDoc = await getTag(content.courseID, content.tagID);\n\n for (const cardId of tagDoc.taggedCards) {\n if (!activeCardIds.has(cardId)) {\n weighted.push({\n cardId,\n courseId: content.courseID,\n score: 1.0,\n provenance: [\n {\n strategy: 'classroom',\n strategyName: 'Classroom',\n strategyId: 'CLASSROOM',\n action: 'generated' as const,\n score: 1.0,\n reason: `Classroom assigned tag: ${content.tagID}, new card`,\n },\n ],\n });\n }\n }\n } else if (content.type === 'card') {\n if (!activeCardIds.has(content.cardID)) {\n weighted.push({\n cardId: content.cardID,\n courseId: content.courseID,\n score: 1.0,\n provenance: [\n {\n strategy: 'classroom',\n strategyName: 'Classroom',\n strategyId: 'CLASSROOM',\n action: 'generated' as const,\n score: 1.0,\n reason: 'Classroom assigned card, new card',\n },\n ],\n });\n }\n }\n }\n\n logger.info(\n `[StudentClassroomDB] New cards from classroom ${this._cfg.name}: ` +\n `${weighted.length} total (reviews + new)`\n );\n\n // Sort by score descending and limit\n return weighted.sort((a, b) => b.score - a.score).slice(0, limit);\n }\n}\n\n/**\n * Interface for managing a classroom.\n */\nexport class TeacherClassroomDB extends ClassroomDBBase implements TeacherClassroomDBInterface {\n private _stuDb!: PouchDB.Database;\n\n private constructor(classID: string) {\n super();\n this._id = classID;\n }\n\n async init(): Promise<void> {\n const dbName = `classdb-teacher-${this._id}`;\n const stuDbName = `classdb-student-${this._id}`;\n this._db = new pouch(\n ENV.COUCHDB_SERVER_PROTOCOL + '://' + ENV.COUCHDB_SERVER_URL + dbName,\n createPouchDBConfig()\n );\n this._stuDb = new pouch(\n ENV.COUCHDB_SERVER_PROTOCOL + '://' + ENV.COUCHDB_SERVER_URL + stuDbName,\n createPouchDBConfig()\n );\n try {\n return this._db\n .get<ClassroomConfig>(CLASSROOM_CONFIG)\n .then((cfg) => {\n this._cfg = cfg;\n this._initComplete = true;\n })\n .then(() => {\n return;\n });\n } catch (e) {\n throw new Error(`Error in TeacherClassroomDB constructor: ${JSON.stringify(e)}`);\n }\n }\n\n public static async factory(classID: string): Promise<TeacherClassroomDB> {\n const ret = new TeacherClassroomDB(classID);\n await ret.init();\n return ret;\n }\n\n public async removeContent(content: AssignedContent): Promise<void> {\n const contentID = this.getContentId(content);\n\n try {\n const doc = await this._db.get(contentID);\n await this._db.remove(doc);\n void this._db.replicate.to(this._stuDb, {\n doc_ids: [contentID],\n });\n } catch (error) {\n logger.error('Failed to remove content:', contentID, error);\n }\n }\n\n public async assignContent(content: AssignedContent): Promise<boolean> {\n let put: PouchDB.Core.Response;\n const id: string = this.getContentId(content);\n\n if (content.type === 'tag') {\n put = await this._db.put<AssignedTag>({\n courseID: content.courseID,\n tagID: content.tagID,\n type: 'tag',\n _id: id,\n assignedBy: content.assignedBy,\n assignedOn: moment.utc(),\n activeOn: content.activeOn || moment.utc(),\n });\n } else {\n put = await this._db.put<AssignedCourse>({\n courseID: content.courseID,\n type: 'course',\n _id: id,\n assignedBy: content.assignedBy,\n assignedOn: moment.utc(),\n activeOn: content.activeOn || moment.utc(),\n });\n }\n\n if (put.ok) {\n void this._db.replicate.to(this._stuDb, {\n doc_ids: [id],\n });\n return true;\n } else {\n return false;\n }\n }\n}\n\nexport const ClassroomLookupDB: () => PouchDB.Database = () =>\n new pouch(ENV.COUCHDB_SERVER_PROTOCOL + '://' + ENV.COUCHDB_SERVER_URL + classroomLookupDBTitle, {\n skip_setup: true,\n });\n\nexport function getClassroomDB(classID: string, version: 'student' | 'teacher'): PouchDB.Database {\n const dbName = `classdb-${version}-${classID}`;\n logger.info(`Retrieving classroom db: ${dbName}`);\n\n return new pouch(\n ENV.COUCHDB_SERVER_PROTOCOL + '://' + ENV.COUCHDB_SERVER_URL + dbName,\n createPouchDBConfig()\n );\n}\n\nexport async function getClassroomConfig(classID: string): Promise<ClassroomConfig> {\n return await getClassroomDB(classID, 'student').get<ClassroomConfig>(CLASSROOM_CONFIG);\n}\n","import pouch from './pouchdb-setup';\nimport { ENV } from '@db/factory';\nimport {\n createPouchDBConfig,\n getStartAndEndKeys,\n getCredentialledCourseConfig,\n updateCredentialledCourseConfig,\n} from '.';\nimport { TeacherClassroomDB, ClassroomLookupDB } from './classroomDB';\nimport { PouchError } from './types';\n\nimport { AdminDBInterface } from '@db/core';\nimport CourseLookup from './courseLookupDB';\nimport { logger } from '@db/util/logger';\n\nexport class AdminDB implements AdminDBInterface {\n private usersDB!: PouchDB.Database;\n\n constructor() {\n // [ ] execute a check here against credentials, and throw an error\n // if the user is not an admin\n this.usersDB = new pouch(\n ENV.COUCHDB_SERVER_PROTOCOL + '://' + ENV.COUCHDB_SERVER_URL + '_users',\n createPouchDBConfig()\n );\n }\n\n public async getUsers() {\n return (\n await this.usersDB.allDocs({\n include_docs: true,\n ...getStartAndEndKeys('org.couchdb.user:'),\n })\n ).rows.map((r) => r.doc!);\n }\n\n public async getCourses() {\n const list = await CourseLookup.allCourseWare();\n return await Promise.all(\n list.map((c) => {\n return getCredentialledCourseConfig(c._id);\n })\n );\n }\n public async removeCourse(id: string) {\n // remove the indexer\n const delResp = await CourseLookup.delete(id);\n\n // set the 'CourseConfig' to 'deleted'\n const cfg = await getCredentialledCourseConfig(id);\n cfg.deleted = true;\n const isDeletedResp = await updateCredentialledCourseConfig(id, cfg);\n\n return {\n ok: delResp.ok && isDeletedResp.ok,\n id: delResp.id,\n rev: delResp.rev,\n };\n }\n\n public async getClassrooms() {\n // const joincodes =\n const uuids = (\n await ClassroomLookupDB().allDocs<{ uuid: string }>({\n include_docs: true,\n })\n ).rows.map((r) => r.doc!.uuid);\n logger.debug(uuids.join(', '));\n\n const promisedCRDbs: TeacherClassroomDB[] = [];\n for (let i = 0; i < uuids.length; i++) {\n try {\n const db = await TeacherClassroomDB.factory(uuids[i]);\n promisedCRDbs.push(db);\n } catch (e) {\n const err = e as PouchError;\n if (err.error && err.error === 'not_found') {\n logger.warn(`db ${uuids[i]} not found`);\n }\n }\n }\n\n return promisedCRDbs.map((db) => {\n return {\n ...db.getConfig(),\n _id: db._id,\n };\n });\n }\n}\n","import { ENV, NOT_SET } from '@db/factory';\nimport { logger } from '@db/util/logger';\nimport fetch from 'cross-fetch';\n\ninterface SessionResponse {\n info: unknown;\n ok: boolean;\n userCtx: {\n name: string;\n roles: string[];\n };\n}\n\nexport async function getCurrentSession(): Promise<SessionResponse> {\n // Legacy XMLHttpRequest implementation\n // return new Promise((resolve, reject) => {\n // const authXML = new XMLHttpRequest();\n // authXML.withCredentials = true;\n //\n // authXML.onerror = (e): void => {\n // reject(new Error('Session check failed:', e));\n // };\n //\n // authXML.addEventListener('load', () => {\n // try {\n // const resp: SessionResponse = JSON.parse(authXML.responseText);\n // resolve(resp);\n // } catch (e) {\n // reject(e);\n // }\n // });\n //\n // const url = `${ENV.COUCHDB_SERVER_PROTOCOL}://${ENV.COUCHDB_SERVER_URL}_session`;\n // authXML.open('GET', url);\n // authXML.send();\n // });\n \n try {\n // Handle case where ENV variables might not be properly set\n if (ENV.COUCHDB_SERVER_URL === NOT_SET || ENV.COUCHDB_SERVER_PROTOCOL === NOT_SET) {\n throw new Error(`CouchDB server configuration not properly initialized. Protocol: \"${ENV.COUCHDB_SERVER_PROTOCOL}\", URL: \"${ENV.COUCHDB_SERVER_URL}\"`);\n }\n \n // Ensure URL has proper slash before _session endpoint\n const baseUrl = ENV.COUCHDB_SERVER_URL.endsWith('/') \n ? ENV.COUCHDB_SERVER_URL.slice(0, -1) \n : ENV.COUCHDB_SERVER_URL;\n const url = `${ENV.COUCHDB_SERVER_PROTOCOL}://${baseUrl}/_session`;\n logger.debug(`Attempting session check at: ${url}`);\n \n const response = await fetch(url, {\n method: 'GET',\n credentials: 'include',\n });\n \n if (!response.ok) {\n throw new Error(`Session check failed: ${response.status}`);\n }\n \n const resp: SessionResponse = await response.json();\n return resp;\n } catch (error) {\n // Use same URL construction logic for error reporting\n const baseUrl = ENV.COUCHDB_SERVER_URL.endsWith('/') \n ? ENV.COUCHDB_SERVER_URL.slice(0, -1) \n : ENV.COUCHDB_SERVER_URL;\n const url = `${ENV.COUCHDB_SERVER_PROTOCOL}://${baseUrl}/_session`;\n logger.error(`Session check error attempting to connect to: ${url} - ${error}`);\n throw new Error(`Session check failed connecting to ${url}: ${error}`);\n }\n}\n\nexport async function getLoggedInUsername(): Promise<string> {\n const session = await getCurrentSession();\n if (session.userCtx.name && session.userCtx.name !== '') {\n return session.userCtx.name;\n }\n // Not logged in - throw so caller can handle guest account\n throw new Error('No logged in user');\n}\n","// packages/db/src/impl/couch/CouchDBSyncStrategy.ts\n\nimport { ENV } from '@db/factory';\nimport { GuestUsername } from '../../core/types/types-legacy';\nimport { logger } from '../../util/logger';\nimport { Status } from '@vue-skuilder/common';\nimport type { SyncStrategy } from '../common/SyncStrategy';\nimport type { AccountCreationResult, AuthenticationResult } from '../common/types';\nimport { getLocalUserDB, hexEncode, updateGuestAccountExpirationDate, accomodateGuest } from '../common';\nimport pouch from './pouchdb-setup';\nimport { createPouchDBConfig } from './index';\nimport { getLoggedInUsername } from './auth';\n\nconst log = (s: any) => {\n logger.info(s);\n};\n\n/**\n * Sync strategy that implements full CouchDB remote synchronization\n * Handles account creation, authentication, and live sync with remote CouchDB server\n */\nexport class CouchDBSyncStrategy implements SyncStrategy {\n private syncHandle?: any; // Handle to cancel sync if needed\n\n setupRemoteDB(username: string): PouchDB.Database {\n if (username === GuestUsername || username.startsWith(GuestUsername)) {\n // For guest users, remote is same as local (no remote sync)\n return getLocalUserDB(username);\n } else {\n // For real users, connect to remote CouchDB\n return this.getUserDB(username);\n }\n }\n\n getWriteDB(username: string): PouchDB.Database {\n if (username === GuestUsername || username.startsWith(GuestUsername)) {\n // Guest users write to local database\n return getLocalUserDB(username);\n } else {\n // Authenticated users write to remote (which will sync to local)\n return this.getUserDB(username);\n }\n }\n\n startSync(localDB: PouchDB.Database, remoteDB: PouchDB.Database): void {\n // Only sync if local and remote are different instances\n if (localDB !== remoteDB) {\n this.syncHandle = pouch.sync(localDB, remoteDB, {\n live: true,\n retry: true,\n });\n }\n // If they're the same (guest mode), no sync needed\n }\n\n stopSync?(): void {\n if (this.syncHandle) {\n this.syncHandle.cancel();\n this.syncHandle = undefined;\n }\n }\n\n canCreateAccount(): boolean {\n return true;\n }\n\n canAuthenticate(): boolean {\n return true;\n }\n\n async createAccount(username: string, password: string): Promise<AccountCreationResult> {\n // IMPORTANT: Capture funnel username BEFORE any operations that might change session state\n const funnelUsername = await this.getCurrentUsername();\n const isFunnelAccount = funnelUsername.startsWith(GuestUsername);\n\n if (isFunnelAccount) {\n logger.info(`Creating account for funnel user ${funnelUsername} -> ${username}`);\n }\n\n try {\n const signupRequest = await this.getRemoteCouchRootDB().signUp(username, password);\n\n if (signupRequest.ok) {\n log(`CREATEACCOUNT: Successfully created account for ${username}`);\n\n // Log out any existing session\n try {\n const logoutResult = await this.getRemoteCouchRootDB().logOut();\n log(`CREATEACCOUNT: logged out: ${logoutResult.ok}`);\n } catch {\n // Ignore logout errors - might not be logged in\n }\n\n // Log in as the new user\n const loginResult = await this.getRemoteCouchRootDB().logIn(username, password);\n log(`CREATEACCOUNT: logged in as new user: ${loginResult.ok}`);\n\n if (loginResult.ok) {\n // Migrate funnel account data if applicable\n if (isFunnelAccount) {\n logger.info(`Migrating data from funnel account ${funnelUsername} to ${username}`);\n const migrationResult = await this.migrateFunnelData(funnelUsername, username);\n if (!migrationResult.success) {\n logger.warn(`Migration failed: ${migrationResult.error}`);\n // Continue anyway - don't block account creation\n }\n }\n\n return {\n status: Status.ok,\n error: undefined,\n };\n } else {\n return {\n status: Status.error,\n error: 'Failed to log in after account creation',\n };\n }\n } else {\n logger.warn(`Signup not OK: ${JSON.stringify(signupRequest)}`);\n return {\n status: Status.error,\n error: 'Account creation failed',\n };\n }\n } catch (e: any) {\n if (e.reason === 'Document update conflict.') {\n return {\n status: Status.error,\n error: 'This username is taken!',\n };\n }\n logger.error(`Error on signup: ${JSON.stringify(e)}`);\n return {\n status: Status.error,\n error: e.message || 'Unknown error during account creation',\n };\n }\n }\n\n async authenticate(username: string, password: string): Promise<AuthenticationResult> {\n try {\n const loginResult = await this.getRemoteCouchRootDB().logIn(username, password);\n\n if (loginResult.ok) {\n log(`Successfully logged in as ${username}`);\n return {\n ok: true,\n };\n } else {\n log(`Login failed for ${username}`);\n return {\n ok: false,\n error: 'Invalid username or password',\n };\n }\n } catch (error: any) {\n logger.error(`Authentication error for ${username}:`, error);\n return {\n ok: false,\n error: error.message || 'Authentication failed',\n };\n }\n }\n\n async logout(): Promise<AuthenticationResult> {\n try {\n const result = await this.getRemoteCouchRootDB().logOut();\n return {\n ok: result.ok,\n error: result.ok ? undefined : 'Logout failed',\n };\n } catch (error: any) {\n logger.error('Logout error:', error);\n return {\n ok: false,\n error: error.message || 'Logout failed',\n };\n }\n }\n\n async getCurrentUsername(): Promise<string> {\n logger.log('[funnel] CouchDBSyncStrategy.getCurrentUsername() called');\n try {\n const loggedInUsername = await getLoggedInUsername();\n logger.log('[funnel] getLoggedInUsername() returned:', loggedInUsername);\n return loggedInUsername;\n } catch (e) {\n // Not logged in - return unique guest account\n logger.log('[funnel] getLoggedInUsername() failed, calling accomodateGuest()');\n logger.log('[funnel] Error was:', e);\n const guestInfo = accomodateGuest();\n logger.log('[funnel] accomodateGuest() returned:', guestInfo);\n return guestInfo.username;\n }\n }\n\n /**\n * Migrate data from funnel account to real account\n */\n private async migrateFunnelData(\n oldUsername: string,\n newUsername: string\n ): Promise<{ success: boolean; error?: string }> {\n try {\n logger.info(`Starting data migration from ${oldUsername} to ${newUsername}`);\n\n const oldLocalDB = getLocalUserDB(oldUsername);\n const newLocalDB = getLocalUserDB(newUsername);\n\n // Get all docs from funnel account\n const allDocs = await oldLocalDB.allDocs({ include_docs: true });\n\n logger.info(`Found ${allDocs.rows.length} documents in funnel account`);\n\n // Filter out design docs and prepare for migration\n const docsToMigrate = allDocs.rows\n .filter(row => !row.id.startsWith('_design/'))\n .map(row => ({\n ...row.doc,\n _rev: undefined, // Remove rev to insert as new\n }));\n\n if (docsToMigrate.length > 0) {\n await newLocalDB.bulkDocs(docsToMigrate);\n logger.info(`Successfully migrated ${docsToMigrate.length} documents from ${oldUsername} to ${newUsername}`);\n } else {\n logger.info('No documents to migrate from funnel account');\n }\n\n return { success: true };\n } catch (error) {\n logger.error('Migration failed:', error);\n return {\n success: false,\n error: error instanceof Error ? error.message : 'Unknown error'\n };\n }\n }\n\n /**\n * Get remote CouchDB root database for authentication operations\n */\n private getRemoteCouchRootDB(): PouchDB.Database {\n const remoteStr: string =\n ENV.COUCHDB_SERVER_PROTOCOL + '://' + ENV.COUCHDB_SERVER_URL + 'skuilder';\n\n try {\n return new pouch(remoteStr, {\n skip_setup: true,\n });\n } catch (error) {\n logger.error('Failed to initialize remote CouchDB connection:', error);\n throw new Error(`Failed to initialize CouchDB: ${JSON.stringify(error)}`);\n }\n }\n\n /**\n * Get remote user database for a specific user\n */\n private getUserDB(username: string): PouchDB.Database {\n const guestAccount: boolean = false;\n\n const hexName = hexEncode(username);\n const dbName = `userdb-${hexName}`;\n log(`Fetching user database: ${dbName} (${username})`);\n\n // Odd construction here the result of a bug in the\n // interaction between pouch, pouch-auth.\n // see: https://github.com/pouchdb-community/pouchdb-authentication/issues/239\n const ret = new pouch(\n ENV.COUCHDB_SERVER_PROTOCOL + '://' + ENV.COUCHDB_SERVER_URL + dbName,\n createPouchDBConfig()\n );\n\n if (guestAccount) {\n updateGuestAccountExpirationDate(ret);\n }\n\n return ret;\n }\n}\n","import { ENV } from '@db/factory';\nimport {\n DocType,\n DocTypePrefixes,\n GuestUsername,\n log,\n SkuilderCourseData,\n} from '../../core/types/types-legacy';\nimport fetch from 'cross-fetch';\n// import { getCurrentUser } from '../../stores/useAuthStore';\nimport moment, { Moment } from 'moment';\nimport { logger } from '@db/util/logger';\n\nimport pouch from './pouchdb-setup';\n\nimport { ScheduledCard } from '@db/core/types/user';\nimport process from 'process';\n\nconst isBrowser = typeof window !== 'undefined';\n\nif (isBrowser) {\n (window as any).process = process; // required as a fix for pouchdb - see #18\n}\n\nconst expiryDocID: string = 'GuestAccountExpirationDate';\n\nconst GUEST_LOCAL_DB = `userdb-${GuestUsername}`;\nexport const localUserDB: PouchDB.Database = new pouch(GUEST_LOCAL_DB);\n\nexport function hexEncode(str: string): string {\n let hex: string;\n let returnStr: string = '';\n\n for (let i = 0; i < str.length; i++) {\n hex = str.charCodeAt(i).toString(16);\n returnStr += ('000' + hex).slice(3);\n }\n\n return returnStr;\n}\nconst pouchDBincludeCredentialsConfig: PouchDB.Configuration.RemoteDatabaseConfiguration = {\n fetch(url: string | Request, opts: RequestInit): Promise<Response> {\n opts.credentials = 'include';\n\n return (pouch as any).fetch(url, opts);\n },\n} as PouchDB.Configuration.RemoteDatabaseConfiguration;\n\n/**\n * Creates PouchDB configuration with appropriate authentication method\n * - Uses HTTP Basic Auth when credentials are available (Node.js/MCP)\n * - Falls back to cookie auth for browser environments\n */\nexport function createPouchDBConfig(): PouchDB.Configuration.RemoteDatabaseConfiguration {\n // Check if running in Node.js with explicit credentials\n const hasExplicitCredentials = ENV.COUCHDB_USERNAME && ENV.COUCHDB_PASSWORD;\n const isNodeEnvironment = typeof window === 'undefined';\n \n if (hasExplicitCredentials && isNodeEnvironment) {\n // Use HTTP Basic Auth for Node.js environments (MCP server)\n return {\n fetch(url: string | Request, opts: RequestInit = {}): Promise<Response> {\n const basicAuth = btoa(`${ENV.COUCHDB_USERNAME}:${ENV.COUCHDB_PASSWORD}`);\n const headers = new Headers(opts.headers || {});\n headers.set('Authorization', `Basic ${basicAuth}`);\n \n const newOpts = {\n ...opts,\n headers: headers\n };\n \n return (pouch as any).fetch(url, newOpts);\n }\n } as PouchDB.Configuration.RemoteDatabaseConfiguration;\n }\n \n // Use cookie-based auth for browser environments or when no explicit credentials\n return pouchDBincludeCredentialsConfig;\n}\n\nfunction getCouchDB(dbName: string): PouchDB.Database {\n return new pouch(\n ENV.COUCHDB_SERVER_PROTOCOL + '://' + ENV.COUCHDB_SERVER_URL + dbName,\n createPouchDBConfig()\n );\n}\n\nexport function getCourseDB(courseID: string): PouchDB.Database {\n // todo: keep a cache of opened courseDBs? need to benchmark this somehow\n return new pouch(\n ENV.COUCHDB_SERVER_PROTOCOL + '://' + ENV.COUCHDB_SERVER_URL + 'coursedb-' + courseID,\n createPouchDBConfig()\n );\n}\n\nexport async function getLatestVersion() {\n try {\n const docs = await getCouchDB('version').allDocs({\n descending: true,\n limit: 1,\n });\n if (docs && docs.rows && docs.rows[0]) {\n return docs.rows[0].id;\n } else {\n return '0.0.0';\n }\n } catch {\n return '-1';\n }\n}\n\n/**\n * Checks the remote couchdb to see if a given username is available\n * @param username The username to be checked\n */\nexport async function usernameIsAvailable(username: string): Promise<boolean> {\n log(`Checking availability of ${username}`);\n \n // Legacy XMLHttpRequest implementation (browser sync)\n // const req = new XMLHttpRequest();\n // const url = ENV.COUCHDB_SERVER_URL + 'userdb-' + hexEncode(username);\n // req.open('HEAD', url, false);\n // req.send();\n // return req.status === 404;\n \n try {\n const url = ENV.COUCHDB_SERVER_URL + 'userdb-' + hexEncode(username);\n const response = await fetch(url, { method: 'HEAD' });\n return response.status === 404;\n } catch (error) {\n log(`Error checking username availability: ${error}`);\n return false;\n }\n}\n\nexport function updateGuestAccountExpirationDate(guestDB: PouchDB.Database<object>) {\n const currentTime = moment.utc();\n const expirationDate: string = currentTime.add(2, 'months').toISOString();\n\n void guestDB\n .get(expiryDocID)\n .then((doc) => {\n return guestDB.put({\n _id: expiryDocID,\n _rev: doc._rev,\n date: expirationDate,\n });\n })\n .catch(() => {\n return guestDB.put({\n _id: expiryDocID,\n date: expirationDate,\n });\n });\n}\n\nexport function getCourseDocs<T extends SkuilderCourseData>(\n courseID: string,\n docIDs: string[],\n options: PouchDB.Core.AllDocsOptions = {}\n) {\n return getCourseDB(courseID).allDocs<T>({\n ...options,\n keys: docIDs,\n });\n}\n\nexport function getCourseDoc<T extends SkuilderCourseData>(\n courseID: string,\n docID: PouchDB.Core.DocumentId,\n options: PouchDB.Core.GetOptions = {}\n): Promise<T> {\n return getCourseDB(courseID).get<T>(docID, options);\n}\n\n/**\n * Returns *all* cards from the parameter courses, in\n * 'qualified' card format (\"courseid-cardid\")\n *\n * @param courseIDs A list of all course_ids to get cards from\n */\nexport async function getRandomCards(courseIDs: string[]) {\n if (courseIDs.length === 0) {\n throw new Error(`getRandomCards:\\n\\tAttempted to get all cards from no courses!`);\n } else {\n const courseResults = await Promise.all(\n courseIDs.map((course) => {\n return getCourseDB(course).find({\n selector: {\n docType: DocType.CARD,\n },\n limit: 1000,\n });\n })\n );\n\n const ret: string[] = [];\n courseResults.forEach((courseCards, index) => {\n courseCards.docs.forEach((doc) => {\n ret.push(`${courseIDs[index]}-${doc._id}`);\n });\n });\n\n return ret;\n }\n}\n\nexport const REVIEW_TIME_FORMAT: string = 'YYYY-MM-DD--kk:mm:ss-SSS';\n\nexport function getCouchUserDB(username: string): PouchDB.Database {\n const guestAccount: boolean = false;\n // console.log(`Getting user db: ${username}`);\n\n const hexName = hexEncode(username);\n const dbName = `userdb-${hexName}`;\n log(`Fetching user database: ${dbName} (${username})`);\n\n // odd construction here the result of a bug in the\n // interaction between pouch, pouch-auth.\n // see: https://github.com/pouchdb-community/pouchdb-authentication/issues/239\n const ret = new pouch(\n ENV.COUCHDB_SERVER_PROTOCOL + '://' + ENV.COUCHDB_SERVER_URL + dbName,\n createPouchDBConfig()\n );\n if (guestAccount) {\n updateGuestAccountExpirationDate(ret);\n }\n\n return ret;\n}\n\nexport function scheduleCardReview(review: {\n user: string;\n course_id: string;\n card_id: PouchDB.Core.DocumentId;\n time: Moment;\n scheduledFor: ScheduledCard['scheduledFor'];\n schedulingAgentId: ScheduledCard['schedulingAgentId'];\n}) {\n const now = moment.utc();\n logger.info(`Scheduling for review in: ${review.time.diff(now, 'h') / 24} days`);\n void getCouchUserDB(review.user).put<ScheduledCard>({\n _id: DocTypePrefixes[DocType.SCHEDULED_CARD] + review.time.format(REVIEW_TIME_FORMAT),\n cardId: review.card_id,\n reviewTime: review.time.toISOString(),\n courseId: review.course_id,\n scheduledAt: now.toISOString(),\n scheduledFor: review.scheduledFor,\n schedulingAgentId: review.schedulingAgentId,\n });\n}\n\nexport function filterAllDocsByPrefix<T>(\n db: PouchDB.Database,\n prefix: string,\n opts?: PouchDB.Core.AllDocsOptions\n) {\n // see couchdb docs 6.2.2:\n // Guide to Views -> Views Collation -> String Ranges\n const options: PouchDB.Core.AllDocsWithinRangeOptions = {\n startkey: prefix,\n endkey: prefix + '\\ufff0',\n include_docs: true,\n };\n\n if (opts) {\n Object.assign(options, opts);\n }\n return db.allDocs<T>(options);\n}\n\nexport function getStartAndEndKeys(key: string): {\n startkey: string;\n endkey: string;\n} {\n return {\n startkey: key,\n endkey: key + '\\ufff0',\n };\n}\n\n//////////////////////\n// Package exports\n//////////////////////\n\nexport * from '../../core/interfaces/contentSource';\nexport * from './adminDB';\nexport * from './classroomDB';\nexport * from './courseAPI';\nexport * from './courseDB';\nexport * from './CouchDBSyncStrategy';\n","import { DocType, DocTypePrefixes, StrategyStateDoc, buildStrategyStateId } from '@db/core';\nimport { getCardHistoryID } from '@db/core/util';\nimport { CourseElo, Status } from '@vue-skuilder/common';\nimport moment, { Moment } from 'moment';\nimport { GuestUsername } from '../../core/types/types-legacy';\nimport { logger } from '../../util/logger';\n\nimport {\n ClassroomRegistrationDoc,\n UserCourseSetting,\n UserDBInterface,\n UsrCrsDataInterface,\n} from '@db/core';\nimport {\n ActivityRecord,\n CourseRegistration,\n CourseRegistrationDoc,\n ScheduledCard,\n UserConfig,\n} from '@db/core/types/user';\nimport { DocumentUpdater } from '@db/study';\nimport { CardHistory, CardRecord } from '../../core/types/types-legacy';\nimport type { SyncStrategy } from './SyncStrategy';\nimport {\n filterAllDocsByPrefix,\n getStartAndEndKeys,\n REVIEW_TIME_FORMAT,\n getLocalUserDB,\n scheduleCardReviewLocal,\n removeScheduledCardReviewLocal,\n} from './userDBHelpers';\nimport { PouchError } from '../couch/types';\nimport UpdateQueue, { Update } from '../couch/updateQueue';\nimport { UsrCrsData } from '../couch/user-course-relDB';\nimport { getCredentialledCourseConfig } from '../couch/index';\n\nconst log = (s: any) => {\n logger.info(s);\n};\n\n// logger.log(`Connecting to remote: ${remoteStr}`);\n\ninterface DesignDoc {\n _id: string;\n views: {\n [viewName: string]: {\n map: string; // String representation of the map function\n };\n };\n}\n\n/**\n * Base user database implementation that uses a pluggable sync strategy.\n * Handles local storage operations and delegates sync/remote operations to the strategy.\n */\nexport class BaseUser implements UserDBInterface, DocumentUpdater {\n private static _instance: BaseUser;\n private static _initialized: boolean = false;\n\n public static Dummy(syncStrategy: SyncStrategy): BaseUser {\n return new BaseUser('Me', syncStrategy);\n }\n\n static readonly DOC_IDS = {\n CONFIG: 'CONFIG',\n COURSE_REGISTRATIONS: 'CourseRegistrations',\n CLASSROOM_REGISTRATIONS: 'ClassroomRegistrations',\n };\n\n // private email: string;\n private _username: string;\n private syncStrategy: SyncStrategy;\n\n public getUsername(): string {\n return this._username;\n }\n\n public isLoggedIn(): boolean {\n return !this._username.startsWith(GuestUsername);\n }\n\n public remote(): PouchDB.Database {\n return this.remoteDB;\n }\n\n private localDB!: PouchDB.Database;\n private remoteDB!: PouchDB.Database;\n private writeDB!: PouchDB.Database; // Database to use for write operations (local-first approach)\n\n private updateQueue!: UpdateQueue;\n\n public async createAccount(\n username: string,\n password: string\n ): Promise<{\n status: Status;\n error: string;\n }> {\n if (!this.syncStrategy.canCreateAccount()) {\n throw new Error('Account creation not supported by current sync strategy');\n }\n\n if (!this._username.startsWith(GuestUsername)) {\n throw new Error(\n `Cannot create a new account while logged in:\nCurrently logged-in as ${this._username}.`\n );\n }\n\n const result = await this.syncStrategy.createAccount!(username, password);\n\n // If account creation was successful, update the username and reinitialize\n if (result.status === Status.ok) {\n log(`Account created successfully, updating username to ${username}`);\n this._username = username;\n try {\n localStorage.removeItem('sk-guest-uuid');\n } catch (e) {\n logger.warn('localStorage not available (Node.js environment):', e);\n }\n await this.init();\n }\n\n return {\n status: result.status,\n error: result.error || '',\n };\n }\n public async login(username: string, password: string) {\n if (!this.syncStrategy.canAuthenticate()) {\n throw new Error('Authentication not supported by current sync strategy');\n }\n\n if (!this._username.startsWith(GuestUsername) && this._username != username) {\n if (this._username != username) {\n throw new Error(`Cannot change accounts while logged in.\n Log out of account ${this.getUsername()} before logging in as ${username}.`);\n }\n logger.warn(`User ${this._username} is already logged in, but executing login again.`);\n }\n\n const loginResult = await this.syncStrategy.authenticate!(username, password);\n if (loginResult.ok) {\n log(`Logged in as ${username}`);\n this._username = username;\n try {\n localStorage.removeItem('sk-guest-uuid');\n } catch (e) {\n logger.warn('localStorage not available (Node.js environment):', e);\n }\n await this.init();\n }\n return loginResult;\n }\n\n public async resetUserData(): Promise<{ status: Status; error?: string }> {\n // Only allow reset for local-only sync strategies\n if (this.syncStrategy.canAuthenticate()) {\n return {\n status: Status.error,\n error:\n 'Reset user data is only available for local-only mode. Use logout instead for remote sync.',\n };\n }\n\n try {\n const localDB = getLocalUserDB(this._username);\n\n // Get all documents to identify user data to clear\n const allDocs = await localDB.allDocs({ include_docs: false });\n\n // Identify documents to delete (preserve authentication and user identity)\n const docsToDelete = allDocs.rows\n .filter((row) => {\n const id = row.id;\n // Delete user progress data but preserve core user documents\n return (\n id.startsWith(DocTypePrefixes[DocType.CARDRECORD]) || // Card interaction history\n id.startsWith(DocTypePrefixes[DocType.SCHEDULED_CARD]) || // Scheduled reviews\n id === BaseUser.DOC_IDS.COURSE_REGISTRATIONS || // Course registrations\n id === BaseUser.DOC_IDS.CLASSROOM_REGISTRATIONS || // Classroom registrations\n id === BaseUser.DOC_IDS.CONFIG // User config\n );\n })\n .map((row) => ({ _id: row.id, _rev: row.value.rev, _deleted: true }));\n\n if (docsToDelete.length > 0) {\n await localDB.bulkDocs(docsToDelete);\n }\n\n // Reinitialize to create fresh default documents\n await this.init();\n\n return { status: Status.ok };\n } catch (error) {\n logger.error('Failed to reset user data:', error);\n return {\n status: Status.error,\n error: error instanceof Error ? error.message : 'Unknown error during reset',\n };\n }\n }\n\n public async logout() {\n if (!this.syncStrategy.canAuthenticate()) {\n // For strategies that don't support authentication, just switch to guest\n this._username = await this.syncStrategy.getCurrentUsername();\n await this.init();\n return { ok: true };\n }\n\n const ret = await this.syncStrategy.logout!();\n // return to 'guest' mode\n this._username = await this.syncStrategy.getCurrentUsername();\n await this.init();\n\n return ret;\n }\n\n public async get<T>(id: string): Promise<T & PouchDB.Core.RevisionIdMeta> {\n return this.localDB.get<T>(id);\n }\n\n public update<T extends PouchDB.Core.Document<object>>(id: string, update: Update<T>) {\n return this.updateQueue.update(id, update);\n }\n\n public async getCourseRegistrationsDoc(): Promise<\n CourseRegistrationDoc & PouchDB.Core.IdMeta & PouchDB.Core.GetMeta\n > {\n logger.debug(`Fetching courseRegistrations for ${this.getUsername()}`);\n\n let ret;\n\n try {\n const regDoc = await this.localDB.get<CourseRegistrationDoc>(\n BaseUser.DOC_IDS.COURSE_REGISTRATIONS\n );\n return regDoc;\n } catch (e) {\n const err = e as PouchError;\n if (err.status === 404) {\n await this.localDB.put<CourseRegistrationDoc>({\n _id: BaseUser.DOC_IDS.COURSE_REGISTRATIONS,\n courses: [],\n studyWeight: {},\n });\n ret = await this.getCourseRegistrationsDoc();\n } else {\n throw new Error(\n `Unexpected error ${JSON.stringify(e)} in getOrCreateCourseRegistrationDoc...`\n );\n }\n }\n\n return ret;\n }\n\n public async getActiveCourses() {\n const reg = await this.getCourseRegistrationsDoc();\n return reg.courses.filter((c) => {\n return c.status === undefined || c.status === 'active';\n });\n }\n\n /**\n * Returns a promise of the card IDs that the user has\n * a scheduled review for.\n *\n */\n public async getActiveCards() {\n const keys = getStartAndEndKeys(DocTypePrefixes[DocType.SCHEDULED_CARD]);\n\n const reviews = await this.remoteDB.allDocs<ScheduledCard>({\n startkey: keys.startkey,\n endkey: keys.endkey,\n include_docs: true,\n });\n\n return reviews.rows.map((r) => {\n return {\n courseID: r.doc!.courseId,\n cardID: r.doc!.cardId,\n };\n });\n }\n\n public async getActivityRecords(): Promise<ActivityRecord[]> {\n try {\n const hist = await this.getHistory();\n\n const allRecords: ActivityRecord[] = [];\n if (!Array.isArray(hist)) {\n logger.error('getHistory did not return an array:', hist);\n return allRecords;\n }\n\n // Sample the first few records to understand structure\n let sampleCount = 0;\n\n for (let i = 0; i < hist.length; i++) {\n try {\n if (hist[i] && Array.isArray(hist[i]!.records)) {\n hist[i]!.records.forEach((record: CardRecord) => {\n try {\n // Skip this record if timeStamp is missing\n if (!record.timeStamp) {\n return;\n }\n\n let timeStamp;\n\n // Handle different timestamp formats\n if (typeof record.timeStamp === 'object') {\n // It's likely a Moment object\n if (typeof record.timeStamp.toDate === 'function') {\n // It's definitely a Moment object\n timeStamp = record.timeStamp.toISOString();\n } else if (record.timeStamp instanceof Date) {\n // It's a Date object\n timeStamp = record.timeStamp.toISOString();\n } else {\n // Log a sample of unknown object types, but don't flood logger\n if (sampleCount < 3) {\n logger.warn('Unknown timestamp object type:', record.timeStamp);\n sampleCount++;\n }\n return;\n }\n } else if (typeof record.timeStamp === 'string') {\n // It's already a string, but make sure it's a valid date\n const date = new Date(record.timeStamp);\n if (isNaN(date.getTime())) {\n return; // Invalid date string\n }\n timeStamp = record.timeStamp;\n } else if (typeof record.timeStamp === 'number') {\n // Assume it's a Unix timestamp (milliseconds since epoch)\n timeStamp = new Date(record.timeStamp).toISOString();\n } else {\n // Unknown type, skip\n return;\n }\n\n allRecords.push({\n timeStamp,\n courseID: record.courseID || 'unknown',\n cardID: record.cardID || 'unknown',\n timeSpent: record.timeSpent || 0,\n type: 'card_view',\n });\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n } catch (err) {\n // Silently skip problematic records to avoid flooding logs\n }\n });\n }\n } catch (err) {\n logger.error('Error processing history item:', err);\n }\n }\n\n logger.debug(`Found ${allRecords.length} activity records`);\n return allRecords;\n } catch (err) {\n logger.error('Error in getActivityRecords:', err);\n return [];\n }\n }\n\n private async getReviewstoDate(targetDate: Moment, course_id?: string) {\n const keys = getStartAndEndKeys(DocTypePrefixes[DocType.SCHEDULED_CARD]);\n\n const reviews = await this.remoteDB.allDocs<ScheduledCard>({\n startkey: keys.startkey,\n endkey: keys.endkey,\n include_docs: true,\n });\n\n log(\n `Fetching ${this._username}'s scheduled reviews${\n course_id ? ` for course ${course_id}` : ''\n }.`\n );\n return reviews.rows\n .filter((r) => {\n if (r.id.startsWith(DocTypePrefixes[DocType.SCHEDULED_CARD])) {\n const date = moment.utc(\n r.id.substr(DocTypePrefixes[DocType.SCHEDULED_CARD].length),\n REVIEW_TIME_FORMAT\n );\n if (targetDate.isAfter(date)) {\n if (course_id === undefined || r.doc!.courseId === course_id) {\n return true;\n }\n }\n }\n })\n .map((r) => r.doc!);\n }\n\n public async getReviewsForcast(daysCount: number) {\n const time = moment.utc().add(daysCount, 'days');\n return this.getReviewstoDate(time);\n }\n\n public async getPendingReviews(course_id?: string) {\n const now = moment.utc();\n return this.getReviewstoDate(now, course_id);\n }\n\n public async getScheduledReviewCount(course_id: string): Promise<number> {\n return (await this.getPendingReviews(course_id)).length;\n }\n\n public async getRegisteredCourses() {\n const regDoc = await this.getCourseRegistrationsDoc();\n return regDoc.courses.filter((c) => {\n return !c.status || c.status === 'active' || c.status === 'maintenance-mode';\n });\n }\n\n public async getCourseRegDoc(courseID: string) {\n const regDocs = await this.getCourseRegistrationsDoc();\n const ret = regDocs.courses.find((c) => c.courseID === courseID);\n if (ret) {\n return ret;\n } else {\n throw new Error(`Course registration not found for course ID: ${courseID}`);\n }\n }\n\n public async registerForCourse(course_id: string, previewMode: boolean = false) {\n return this.getCourseRegistrationsDoc()\n .then((doc: CourseRegistrationDoc) => {\n const status = previewMode ? 'preview' : 'active';\n logger.debug(`Registering for ${course_id} with status: ${status}`);\n\n const regItem: CourseRegistration = {\n status: status,\n courseID: course_id,\n user: true,\n admin: false,\n moderator: false,\n elo: {\n global: {\n score: 1000,\n count: 0,\n },\n tags: {},\n misc: {},\n },\n };\n\n if (\n doc.courses.filter((course) => {\n return course.courseID === regItem.courseID;\n }).length === 0\n ) {\n log(`It's a new course registration!`);\n doc.courses.push(regItem);\n doc.studyWeight[course_id] = 1;\n } else {\n doc.courses.forEach((c) => {\n log(`Found the previously registered course!`);\n if (c.courseID === course_id) {\n c.status = status;\n }\n });\n }\n\n return this.localDB.put<CourseRegistrationDoc>(doc);\n })\n .catch((e) => {\n log(`Registration failed because of: ${JSON.stringify(e)}`);\n throw e;\n });\n }\n public async dropCourse(course_id: string, dropStatus: CourseRegistration['status'] = 'dropped') {\n return this.getCourseRegistrationsDoc().then((doc) => {\n let index: number = -1;\n for (let i = 0; i < doc.courses.length; i++) {\n if (doc.courses[i].courseID === course_id) {\n index = i;\n }\n }\n\n if (index !== -1) {\n // remove from the relative-weighting of course study\n delete doc.studyWeight[course_id];\n // set drop status\n doc.courses[index].status = dropStatus;\n } else {\n throw new Error(\n `User ${this.getUsername()} is not currently registered for course ${course_id}`\n );\n }\n\n return this.localDB.put<CourseRegistrationDoc>(doc);\n });\n }\n\n public async getCourseInterface(courseId: string): Promise<UsrCrsDataInterface> {\n return new UsrCrsData(this, courseId);\n }\n\n public async getUserEditableCourses() {\n let courseIDs: string[] = [];\n\n const registeredCourses = await this.getCourseRegistrationsDoc();\n\n courseIDs = courseIDs.concat(\n registeredCourses.courses.map((course) => {\n return course.courseID;\n })\n );\n\n const cfgs = await Promise.all(\n courseIDs.map(async (id) => {\n return await getCredentialledCourseConfig(id);\n })\n );\n return cfgs;\n }\n\n public async getConfig(): Promise<UserConfig & PouchDB.Core.IdMeta & PouchDB.Core.GetMeta> {\n const defaultConfig: PouchDB.Core.Document<UserConfig> = {\n _id: BaseUser.DOC_IDS.CONFIG,\n darkMode: false,\n likesConfetti: false,\n sessionTimeLimit: 5,\n };\n\n try {\n const cfg = await this.localDB.get<UserConfig>(BaseUser.DOC_IDS.CONFIG);\n logger.debug('Raw config from DB:', cfg);\n\n return cfg;\n } catch (e) {\n const err = e as PouchError;\n if (err.name && err.name === 'not_found') {\n await this.localDB.put<UserConfig>(defaultConfig);\n return this.getConfig();\n } else {\n logger.error(`Error setting user default config:`, e);\n throw new Error(`Error returning the user's configuration: ${JSON.stringify(e)}`);\n }\n }\n }\n\n public async setConfig(items: Partial<UserConfig>) {\n logger.debug(`Setting Config items ${JSON.stringify(items)}`);\n\n const c = await this.getConfig();\n const put = await this.localDB.put<UserConfig>({\n ...c,\n ...items,\n });\n\n if (put.ok) {\n logger.debug(`Config items set: ${JSON.stringify(items)}`);\n } else {\n logger.error(`Error setting config items: ${JSON.stringify(put)}`);\n }\n }\n\n /**\n *\n * This function should be called *only* by the pouchdb datalayer provider\n * auth store.\n *\n *\n * Anyone else seeking the current user should use the auth store's\n * exported `getCurrentUser` method.\n *\n */\n public static async instance(syncStrategy: SyncStrategy, username?: string): Promise<BaseUser> {\n if (username) {\n BaseUser._instance = new BaseUser(username, syncStrategy);\n await BaseUser._instance.init();\n return BaseUser._instance;\n } else if (BaseUser._instance && BaseUser._initialized) {\n // log(`USER.instance() returning user ${BaseUser._instance._username}`);\n return BaseUser._instance;\n } else if (BaseUser._instance) {\n return new Promise((resolve) => {\n (function waitForUser() {\n if (BaseUser._initialized) {\n return resolve(BaseUser._instance);\n } else {\n setTimeout(waitForUser, 50);\n }\n })();\n });\n } else {\n const guestUsername = await syncStrategy.getCurrentUsername();\n BaseUser._instance = new BaseUser(guestUsername, syncStrategy);\n await BaseUser._instance.init();\n return BaseUser._instance;\n }\n }\n\n private constructor(username: string, syncStrategy: SyncStrategy) {\n BaseUser._initialized = false;\n this._username = username;\n this.syncStrategy = syncStrategy;\n this.setDBandQ();\n }\n\n private setDBandQ() {\n this.localDB = getLocalUserDB(this._username);\n this.remoteDB = this.syncStrategy.setupRemoteDB(this._username);\n // writeDB follows local-first pattern: static mode writes to local, CouchDB writes to remote/local as appropriate\n this.writeDB = this.syncStrategy.getWriteDB\n ? this.syncStrategy.getWriteDB(this._username)\n : this.localDB;\n this.updateQueue = new UpdateQueue(this.localDB, this.writeDB);\n }\n\n private async init() {\n BaseUser._initialized = false;\n\n // Skip admin user\n if (this._username === 'admin') {\n BaseUser._initialized = true;\n return;\n }\n\n this.setDBandQ();\n\n this.syncStrategy.startSync(this.localDB, this.remoteDB);\n this.applyDesignDocs().catch((error) => {\n log(`Error in applyDesignDocs background task: ${error}`);\n if (error && typeof error === 'object') {\n log(`Full error details in applyDesignDocs: ${JSON.stringify(error)}`);\n }\n });\n this.deduplicateReviews().catch((error) => {\n log(`Error in deduplicateReviews background task: ${error}`);\n if (error && typeof error === 'object') {\n log(`Full error details in background task: ${JSON.stringify(error)}`);\n }\n });\n BaseUser._initialized = true;\n }\n\n private static designDocs: DesignDoc[] = [\n {\n _id: '_design/reviewCards',\n views: {\n reviewCards: {\n map: `function (doc) {\n if (doc._id && doc._id.indexOf('card_review') === 0 && doc.courseId && doc.cardId) {\n emit(doc._id, doc.courseId + '-' + doc.cardId);\n }\n }`,\n },\n },\n },\n ];\n\n private async applyDesignDocs() {\n log(`Starting applyDesignDocs for user: ${this._username}`);\n log(`Remote DB name: ${this.remoteDB.name || 'unknown'}`);\n\n if (this._username === 'admin') {\n // Skip admin user\n log('Skipping design docs for admin user');\n return;\n }\n\n log(`Applying ${BaseUser.designDocs.length} design docs`);\n for (const doc of BaseUser.designDocs) {\n log(`Applying design doc: ${doc._id}`);\n try {\n // Try to get existing doc\n try {\n const existingDoc = await this.remoteDB.get(doc._id);\n // Update existing doc\n await this.remoteDB.put({\n ...doc,\n _rev: existingDoc._rev,\n });\n } catch (e: unknown) {\n if (e instanceof Error && e.name === 'not_found') {\n // Create new doc\n await this.remoteDB.put(doc);\n } else {\n throw e; // Re-throw unexpected errors\n }\n }\n } catch (error: unknown) {\n if ((error as any).name && (error as any).name === 'conflict') {\n logger.warn(`Design doc ${doc._id} update conflict - will retry`);\n // Wait a bit and try again\n await new Promise((resolve) => setTimeout(resolve, 1000));\n await this.applyDesignDoc(doc); // Recursive retry\n } else {\n logger.error(`Failed to apply design doc ${doc._id}:`, error);\n throw error;\n }\n }\n }\n }\n\n // Helper method for single doc update with retry\n private async applyDesignDoc(doc: DesignDoc, retries = 3): Promise<void> {\n try {\n const existingDoc = await this.remoteDB.get(doc._id);\n await this.remoteDB.put({\n ...doc,\n _rev: existingDoc._rev,\n });\n } catch (e: unknown) {\n if (e instanceof Error && e.name === 'conflict' && retries > 0) {\n await new Promise((resolve) => setTimeout(resolve, 1000));\n return this.applyDesignDoc(doc, retries - 1);\n }\n throw e;\n }\n }\n\n /**\n * Logs a record of the user's interaction with the card and returns the card's\n * up-to-date history.\n *\n * **Automatic Initialization:**\n * If this is the user's first interaction with the card (CardHistory doesn't exist),\n * this method automatically creates the CardHistory document with initial values\n * (lapses: 0, streak: 0, bestInterval: 0).\n *\n * **Error Handling:**\n * - Handles 404 errors by creating initial CardHistory document\n * - Re-throws all other errors from UpdateQueue\n *\n * // [ ] #db-refactor extract to a smaller scope - eg, UserStudySession\n *\n * @param record - The recent recorded interaction between user and card\n * @returns The updated state of the card's CardHistory data\n * @throws Error if document creation fails or non-404 database error occurs\n */\n\n public async putCardRecord<T extends CardRecord>(\n record: T\n ): Promise<CardHistory<CardRecord> & PouchDB.Core.RevisionIdMeta> {\n const cardHistoryID = getCardHistoryID(record.courseID, record.cardID);\n // stringify the current record to make it writable to couchdb\n record.timeStamp = moment.utc(record.timeStamp).toString() as unknown as Moment;\n\n try {\n const cardHistory = await this.update<CardHistory<T>>(\n cardHistoryID,\n function (h: CardHistory<T>) {\n h.records.push(record);\n h.bestInterval = h.bestInterval || 0;\n h.lapses = h.lapses || 0;\n h.streak = h.streak || 0;\n return h;\n }\n );\n\n // Convert timestamps to moment objects\n cardHistory.records = cardHistory.records.map<T>((record) => {\n const ret: T = {\n ...(record as object),\n } as T;\n ret.timeStamp = moment.utc(record.timeStamp);\n return ret;\n });\n return cardHistory;\n } catch (e) {\n const reason = e as PouchError;\n if (reason.status === 404) {\n try {\n const initCardHistory: CardHistory<T> = {\n _id: cardHistoryID,\n cardID: record.cardID,\n courseID: record.courseID,\n records: [record],\n lapses: 0,\n streak: 0,\n bestInterval: 0,\n };\n const putResult = await this.writeDB.put<CardHistory<T>>(initCardHistory);\n return { ...initCardHistory, _rev: putResult.rev };\n } catch (creationError) {\n throw new Error(\n `Failed to create CardHistory for ${cardHistoryID}. Reason: ${creationError}`\n );\n }\n } else {\n throw new Error(`putCardRecord failed because of:\n name:${reason.name}\n error: ${reason.error}\n message: ${reason.message}`);\n }\n }\n }\n\n private async deduplicateReviews() {\n try {\n log('Starting deduplication of scheduled reviews...');\n log(`Remote DB name: ${this.remoteDB.name || 'unknown'}`);\n log(`Write DB name: ${this.writeDB.name || 'unknown'}`);\n /**\n * Maps the qualified-id of a scheduled review card to\n * the docId of the same scheduled review.\n *\n * EG: {\n * courseId-cardId: 'card_review_2021-06--17:12:165'\n * }\n */\n const reviewsMap: { [index: string]: string } = {};\n const duplicateDocIds: string[] = [];\n\n log(\n `Attempting to query remoteDB for reviewCards/reviewCards. Database: ${this.remoteDB.name || 'unknown'}`\n );\n const scheduledReviews = await this.remoteDB.query<{\n id: string;\n value: string;\n }>('reviewCards/reviewCards');\n\n log(`Found ${scheduledReviews.rows.length} scheduled reviews to process`);\n\n // First pass: identify duplicates\n scheduledReviews.rows.forEach((r) => {\n const qualifiedCardId = r.value; // courseId-cardId\n const docId = r.key; // card_review_2021-06--17:12:165\n\n if (reviewsMap[qualifiedCardId]) {\n // this card is scheduled more than once! mark the earlier one for deletion\n log(`Found duplicate scheduled review for card: ${qualifiedCardId}`);\n log(\n `Marking earlier review ${reviewsMap[qualifiedCardId]} for deletion, keeping ${docId}`\n );\n duplicateDocIds.push(reviewsMap[qualifiedCardId]);\n // replace with the later-dated scheduled review\n reviewsMap[qualifiedCardId] = docId;\n } else {\n // note that this card is scheduled for review\n reviewsMap[qualifiedCardId] = docId;\n }\n });\n\n // Second pass: remove duplicates\n if (duplicateDocIds.length > 0) {\n log(`Removing ${duplicateDocIds.length} duplicate reviews...`);\n const deletePromises = duplicateDocIds.map(async (docId) => {\n try {\n const doc = await this.remoteDB.get(docId);\n await this.writeDB.remove(doc);\n log(`Successfully removed duplicate review: ${docId}`);\n } catch (error) {\n log(`Failed to remove duplicate review ${docId}: ${error}`);\n }\n });\n\n await Promise.all(deletePromises);\n log(`Deduplication complete. Processed ${duplicateDocIds.length} duplicates`);\n } else {\n log('No duplicate reviews found');\n }\n } catch (error) {\n log(`Error during review deduplication: ${error}`);\n if (error && typeof error === 'object' && 'status' in error && error.status === 404) {\n log(\n `Database not found (404) during review deduplication. Database: ${this.remoteDB.name || 'unknown'}`\n );\n log(\n `This might indicate the user database doesn't exist or the reviewCards view isn't available`\n );\n }\n // Log full error details for debugging\n if (error && typeof error === 'object') {\n log(`Full error details: ${JSON.stringify(error)}`);\n }\n }\n }\n\n /**\n * Returns a promise of the card IDs that the user has\n * encountered in the past.\n *\n * @param course_id optional specification of individual course\n */\n async getSeenCards(course_id?: string) {\n let prefix = DocTypePrefixes[DocType.CARDRECORD];\n if (course_id) {\n prefix += course_id;\n }\n const docs = await filterAllDocsByPrefix(this.localDB, prefix, {\n include_docs: false,\n });\n // const docs = await this.localDB.allDocs({});\n const ret: PouchDB.Core.DocumentId[] = [];\n docs.rows.forEach((row) => {\n if (row.id.startsWith(DocTypePrefixes[DocType.CARDRECORD])) {\n ret.push(row.id.substr(DocTypePrefixes[DocType.CARDRECORD].length));\n }\n });\n return ret;\n }\n\n /**\n *\n * @returns A promise of the cards that the user has seen in the past.\n */\n async getHistory() {\n const cards = await filterAllDocsByPrefix<CardHistory<CardRecord>>(\n this.remoteDB,\n DocTypePrefixes[DocType.CARDRECORD],\n {\n include_docs: true,\n attachments: false,\n }\n );\n return cards.rows.map((r) => r.doc);\n }\n\n async updateCourseSettings(course_id: string, settings: UserCourseSetting[]) {\n void this.getCourseRegistrationsDoc().then((doc) => {\n const crs = doc.courses.find((c) => c.courseID === course_id);\n if (crs) {\n if (crs.settings === null || crs.settings === undefined) {\n crs.settings = {};\n }\n settings.forEach((setting) => {\n crs!.settings![setting.key] = setting.value;\n });\n }\n\n return this.localDB.put(doc);\n });\n }\n async getCourseSettings(course_id: string) {\n const regDoc = await this.getCourseRegistrationsDoc();\n const crsDoc = regDoc.courses.find((c) => c.courseID === course_id);\n\n if (crsDoc) {\n return crsDoc.settings;\n } else {\n throw new Error(`getCourseSettings Failed:\n User is not registered for course ${course_id}`);\n }\n }\n\n private async getOrCreateClassroomRegistrationsDoc(): Promise<\n ClassroomRegistrationDoc & PouchDB.Core.IdMeta & PouchDB.Core.GetMeta\n > {\n let ret;\n\n try {\n ret = await this.remoteDB.get<ClassroomRegistrationDoc>(\n BaseUser.DOC_IDS.CLASSROOM_REGISTRATIONS\n );\n } catch (e) {\n const err = e as PouchError;\n\n if (err.status === 404) {\n // doc does not exist. Create it and then run this fcn again.\n await this.writeDB.put<ClassroomRegistrationDoc>({\n _id: BaseUser.DOC_IDS.CLASSROOM_REGISTRATIONS,\n registrations: [],\n });\n ret = await this.getOrCreateClassroomRegistrationsDoc();\n } else {\n // Properly serialize error information\n const errorDetails = {\n name: err.name,\n status: err.status,\n message: err.message,\n reason: err.reason,\n error: err.error,\n };\n\n logger.error(\n 'Database error in getOrCreateClassroomRegistrationsDoc (private method):',\n errorDetails\n );\n\n throw new Error(\n `Database error accessing classroom registrations: ${err.message || err.name || 'Unknown error'} (status: ${err.status})`\n );\n }\n }\n\n logger.debug(`Returning classroom registrations doc: ${JSON.stringify(ret)}`);\n return ret;\n }\n\n /**\n * Retrieves the list of active classroom IDs where the user is registered as a student.\n *\n * @returns Promise<string[]> - Array of classroom IDs, or empty array if classroom\n * registration document is unavailable due to database errors\n *\n * @description This method gracefully handles database connectivity issues by returning\n * an empty array when the classroom registrations document cannot be accessed.\n * This ensures that users can still access other application features even\n * when classroom functionality is temporarily unavailable.\n */\n public async getActiveClasses(): Promise<string[]> {\n try {\n return (await this.getOrCreateClassroomRegistrationsDoc()).registrations\n .filter((c) => c.registeredAs === 'student')\n .map((c) => c.classID);\n } catch (error) {\n logger.warn(\n 'Failed to load classroom registrations, continuing without classroom data:',\n error\n );\n // Return empty array so user can still access other features\n return [];\n }\n }\n\n public async scheduleCardReview(review: {\n user: string;\n course_id: string;\n card_id: PouchDB.Core.DocumentId;\n time: Moment;\n scheduledFor: ScheduledCard['scheduledFor'];\n schedulingAgentId: ScheduledCard['schedulingAgentId'];\n }) {\n return scheduleCardReviewLocal(this.writeDB, review);\n }\n public async removeScheduledCardReview(reviewId: string): Promise<void> {\n return removeScheduledCardReviewLocal(this.writeDB, reviewId);\n }\n\n public async registerForClassroom(\n _classId: string,\n _registerAs: 'student' | 'teacher' | 'aide' | 'admin'\n ): Promise<PouchDB.Core.Response> {\n return registerUserForClassroom(this._username, _classId, _registerAs);\n }\n\n public async dropFromClassroom(classId: string): Promise<PouchDB.Core.Response> {\n return dropUserFromClassroom(this._username, classId);\n }\n public async getUserClassrooms(): Promise<ClassroomRegistrationDoc> {\n return getUserClassrooms(this._username);\n }\n\n public async updateUserElo(courseId: string, elo: CourseElo): Promise<PouchDB.Core.Response> {\n return updateUserElo(this._username, courseId, elo);\n }\n\n public async getStrategyState<T>(courseId: string, strategyKey: string): Promise<T | null> {\n const docId = buildStrategyStateId(courseId, strategyKey);\n try {\n const doc = await this.localDB.get<StrategyStateDoc<T>>(docId);\n return doc.data;\n } catch (e) {\n const err = e as PouchError;\n if (err.status === 404) {\n return null;\n }\n throw e;\n }\n }\n\n public async putStrategyState<T>(courseId: string, strategyKey: string, data: T): Promise<void> {\n const docId = buildStrategyStateId(courseId, strategyKey);\n let existingRev: string | undefined;\n\n try {\n const existing = await this.localDB.get<StrategyStateDoc<T>>(docId);\n existingRev = existing._rev;\n } catch (e) {\n const err = e as PouchError;\n if (err.status !== 404) {\n throw e;\n }\n }\n\n const doc: StrategyStateDoc<T> = {\n _id: docId,\n _rev: existingRev,\n docType: DocType.STRATEGY_STATE,\n courseId,\n strategyKey,\n data,\n updatedAt: new Date().toISOString(),\n };\n\n await this.localDB.put(doc);\n }\n\n public async deleteStrategyState(courseId: string, strategyKey: string): Promise<void> {\n const docId = buildStrategyStateId(courseId, strategyKey);\n try {\n const doc = await this.localDB.get(docId);\n await this.localDB.remove(doc);\n } catch (e) {\n const err = e as PouchError;\n if (err.status === 404) {\n return;\n }\n throw e;\n }\n }\n}\n\nexport function accomodateGuest(): {\n username: string;\n firstVisit: boolean;\n} {\n logger.log('[funnel] accomodateGuest() called');\n\n // Check if localStorage is available (browser environment)\n if (typeof localStorage === 'undefined') {\n logger.log(\n '[funnel] localStorage not available (Node.js environment), returning default guest'\n );\n return {\n username: GuestUsername + 'nodejs-test',\n firstVisit: true,\n };\n }\n\n const dbUUID = 'sk-guest-uuid';\n let firstVisit: boolean;\n\n const existingUUID = localStorage.getItem(dbUUID);\n logger.log('[funnel] Checking localStorage for key:', dbUUID);\n logger.log('[funnel] Existing UUID value:', existingUUID);\n logger.log('[funnel] existingUUID !== null:', existingUUID !== null);\n\n if (existingUUID !== null) {\n firstVisit = false;\n logger.log(`[funnel] Returning guest ${existingUUID} \"logging in\".`);\n } else {\n firstVisit = true;\n logger.log('[funnel] No existing UUID, generating new one...');\n const uuid = generateUUID();\n logger.log('[funnel] Generated UUID:', uuid);\n logger.log('[funnel] UUID length:', uuid.length);\n\n try {\n localStorage.setItem(dbUUID, uuid);\n logger.log('[funnel] Successfully stored UUID in localStorage');\n const verification = localStorage.getItem(dbUUID);\n logger.log('[funnel] Verification read from localStorage:', verification);\n } catch (e) {\n logger.error('[funnel] ERROR storing UUID:', e);\n }\n\n logger.log(`[funnel] Accommodating a new guest with account: ${uuid}`);\n }\n\n const finalUUID = localStorage.getItem(dbUUID);\n const finalUsername = GuestUsername + finalUUID;\n logger.log('[funnel] Final UUID from localStorage:', finalUUID);\n logger.log('[funnel] GuestUsername constant:', GuestUsername);\n logger.log('[funnel] Final username to return:', finalUsername);\n\n return {\n username: finalUsername,\n firstVisit: firstVisit,\n };\n\n // Use cryptographically secure UUID generation\n function generateUUID() {\n logger.log('[funnel] Inside generateUUID()');\n\n // Use crypto.randomUUID() if available (Node 14.17+ / modern browsers)\n if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {\n const uuid = crypto.randomUUID();\n logger.log('[funnel] Generated UUID using crypto.randomUUID():', uuid);\n return uuid;\n }\n\n // Fallback for older environments: use crypto.getRandomValues()\n if (typeof crypto !== 'undefined' && typeof crypto.getRandomValues === 'function') {\n const bytes = new Uint8Array(16);\n crypto.getRandomValues(bytes);\n\n // Set version (4) and variant bits according to RFC 4122\n bytes[6] = (bytes[6] & 0x0f) | 0x40; // Version 4\n bytes[8] = (bytes[8] & 0x3f) | 0x80; // Variant 10\n\n const uuid = [\n Array.from(bytes.slice(0, 4))\n .map((b) => b.toString(16).padStart(2, '0'))\n .join(''),\n Array.from(bytes.slice(4, 6))\n .map((b) => b.toString(16).padStart(2, '0'))\n .join(''),\n Array.from(bytes.slice(6, 8))\n .map((b) => b.toString(16).padStart(2, '0'))\n .join(''),\n Array.from(bytes.slice(8, 10))\n .map((b) => b.toString(16).padStart(2, '0'))\n .join(''),\n Array.from(bytes.slice(10, 16))\n .map((b) => b.toString(16).padStart(2, '0'))\n .join(''),\n ].join('-');\n\n logger.log('[funnel] Generated UUID using crypto.getRandomValues():', uuid);\n return uuid;\n }\n\n // Last resort fallback (should never happen in modern environments)\n logger.warn('[funnel] crypto API not available, using timestamp-based UUID (NOT SECURE)');\n let d = new Date().getTime();\n if (typeof performance !== 'undefined' && typeof performance.now === 'function') {\n d += performance.now();\n }\n const uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {\n const r = (d + Math.random() * 16) % 16 | 0;\n d = Math.floor(d / 16);\n return (c === 'x' ? r : (r & 0x3) | 0x8).toString(16);\n });\n logger.log('[funnel] Generated UUID (fallback):', uuid);\n return uuid;\n }\n}\n\nconst userCoursesDoc = 'CourseRegistrations';\nconst userClassroomsDoc = 'ClassroomRegistrations';\n\nasync function getOrCreateClassroomRegistrationsDoc(\n user: string\n): Promise<ClassroomRegistrationDoc & PouchDB.Core.IdMeta & PouchDB.Core.GetMeta> {\n let ret;\n\n try {\n ret = await getLocalUserDB(user).get<ClassroomRegistrationDoc>(userClassroomsDoc);\n } catch (e) {\n const err = e as PouchError;\n\n if (err.status === 404) {\n // doc does not exist. Create it and then run this fcn again.\n await getLocalUserDB(user).put<ClassroomRegistrationDoc>({\n _id: userClassroomsDoc,\n registrations: [],\n });\n ret = await getOrCreateClassroomRegistrationsDoc(user);\n } else {\n // Properly serialize error information\n const errorDetails = {\n name: err.name,\n status: err.status,\n message: err.message,\n reason: err.reason,\n error: err.error,\n };\n\n logger.error(\n 'Database error in getOrCreateClassroomRegistrationsDoc (standalone function):',\n errorDetails\n );\n\n throw new Error(\n `Database error accessing classroom registrations: ${err.message || err.name || 'Unknown error'} (status: ${err.status})`\n );\n }\n }\n\n return ret;\n}\n\nasync function getOrCreateCourseRegistrationsDoc(\n user: string\n): Promise<CourseRegistrationDoc & PouchDB.Core.IdMeta & PouchDB.Core.GetMeta> {\n let ret;\n\n try {\n ret = await getLocalUserDB(user).get<CourseRegistrationDoc>(userCoursesDoc);\n } catch (e) {\n const err = e as PouchError;\n if (err.status === 404) {\n // doc does not exist. Create it and then run this fcn again.\n await getLocalUserDB(user).put<CourseRegistrationDoc>({\n _id: userCoursesDoc,\n courses: [],\n studyWeight: {},\n });\n ret = await getOrCreateCourseRegistrationsDoc(user);\n } else {\n throw new Error(\n `Unexpected error ${JSON.stringify(e)} in getOrCreateCourseRegistrationDoc...`\n );\n }\n }\n\n return ret;\n}\n\nexport async function updateUserElo(user: string, course_id: string, elo: CourseElo) {\n const regDoc = await getOrCreateCourseRegistrationsDoc(user);\n const course = regDoc.courses.find((c) => c.courseID === course_id)!;\n course.elo = elo;\n return getLocalUserDB(user).put(regDoc);\n}\n\nexport async function registerUserForClassroom(\n user: string,\n classID: string,\n registerAs: 'student' | 'teacher' | 'aide' | 'admin'\n) {\n log(`Registering user: ${user} in course: ${classID}`);\n return getOrCreateClassroomRegistrationsDoc(user).then((doc) => {\n const regItem = {\n classID: classID,\n registeredAs: registerAs,\n };\n\n if (\n doc.registrations.filter((reg) => {\n return reg.classID === regItem.classID && reg.registeredAs === regItem.registeredAs;\n }).length === 0\n ) {\n doc.registrations.push(regItem);\n } else {\n log(`User ${user} is already registered for class ${classID}`);\n }\n\n return getLocalUserDB(user).put(doc);\n });\n}\n\nexport async function dropUserFromClassroom(user: string, classID: string) {\n return getOrCreateClassroomRegistrationsDoc(user).then((doc) => {\n let index: number = -1;\n\n for (let i = 0; i < doc.registrations.length; i++) {\n if (doc.registrations[i].classID === classID) {\n index = i;\n }\n }\n\n if (index !== -1) {\n doc.registrations.splice(index, 1);\n }\n return getLocalUserDB(user).put(doc);\n });\n}\n\nexport async function getUserClassrooms(user: string) {\n return getOrCreateClassroomRegistrationsDoc(user);\n}\n","// packages/db/src/impl/common/index.ts\n\nexport type { SyncStrategy } from './SyncStrategy';\nexport { BaseSyncStrategy } from './SyncStrategy';\nexport type {\n AccountCreationResult,\n AuthenticationResult,\n UserSession,\n SyncConfig,\n SyncStatus,\n} from './types';\nexport { BaseUser, accomodateGuest } from './BaseUserDB';\nexport {\n REVIEW_TIME_FORMAT,\n hexEncode,\n filterAllDocsByPrefix,\n getStartAndEndKeys,\n updateGuestAccountExpirationDate,\n getLocalUserDB,\n scheduleCardReviewLocal,\n removeScheduledCardReviewLocal,\n} from './userDBHelpers';\n","// db/src/impl/couch/PouchDataLayerProvider.ts\n\nimport {\n AdminDBInterface,\n ClassroomDBInterface,\n CoursesDBInterface,\n CourseDBInterface,\n DataLayerProvider,\n UserDBInterface,\n UserDBReader,\n} from '../../core/interfaces';\nimport { logger } from '../../util/logger';\nimport { initializeDataDirectory } from '../../util/dataDirectory';\n\nimport { getLoggedInUsername } from './auth';\n\nimport { AdminDB } from './adminDB';\nimport { StudentClassroomDB, TeacherClassroomDB } from './classroomDB';\nimport { CourseDB, CoursesDB } from './courseDB';\n\nimport { BaseUser } from '../common';\nimport { CouchDBSyncStrategy } from './CouchDBSyncStrategy';\n\nexport class CouchDataLayerProvider implements DataLayerProvider {\n private initialized: boolean = false;\n private userDB!: UserDBInterface;\n private currentUsername: string = '';\n\n // the scoped list of courseIDs for a UI focused on a specific course\n // or group of courses\n private _courseIDs: string[] = [];\n\n constructor(coursIDs?: string[]) {\n if (coursIDs) {\n this._courseIDs = coursIDs;\n }\n }\n\n async initialize(): Promise<void> {\n if (this.initialized) return;\n\n // Check if we are in a Node.js environment\n const isNodeEnvironment =\n typeof process !== 'undefined' && process.versions != null && process.versions.node != null;\n\n if (isNodeEnvironment) {\n logger.info(\n 'CouchDataLayerProvider: Running in Node.js environment, creating guest UserDB for testing.'\n );\n await initializeDataDirectory();\n // In Node.js (testing) environment, create a guest user instance\n const syncStrategy = new CouchDBSyncStrategy();\n this.userDB = await BaseUser.instance(syncStrategy);\n } else {\n // Assume browser-like environment, proceed with user session logic\n // Let CouchDBSyncStrategy.getCurrentUsername() handle both logged-in and guest users\n const syncStrategy = new CouchDBSyncStrategy();\n this.userDB = await BaseUser.instance(syncStrategy);\n this.currentUsername = this.userDB.getUsername();\n logger.debug(`Current username: ${this.currentUsername}`);\n }\n\n this.initialized = true;\n }\n\n async teardown(): Promise<void> {\n // Close connections, etc.\n this.initialized = false;\n }\n\n getUserDB(): UserDBInterface {\n return this.userDB;\n }\n\n getCourseDB(courseId: string): CourseDBInterface {\n return new CourseDB(courseId, async () => this.getUserDB());\n }\n\n getCoursesDB(): CoursesDBInterface {\n return new CoursesDB(this._courseIDs);\n }\n\n async getClassroomDB(\n classId: string,\n type: 'student' | 'teacher'\n ): Promise<ClassroomDBInterface> {\n if (type === 'student') {\n return await StudentClassroomDB.factory(classId, this.getUserDB());\n } else {\n return await TeacherClassroomDB.factory(classId);\n }\n }\n\n getAdminDB(): AdminDBInterface {\n return new AdminDB();\n }\n\n async createUserReaderForUser(targetUsername: string): Promise<UserDBReader> {\n // Security check: only admin can access other users' data\n const requestingUsername = await getLoggedInUsername();\n if (requestingUsername !== 'admin') {\n throw new Error('Unauthorized: Only admin users can access other users\\' data');\n }\n\n logger.info(`Admin user '${requestingUsername}' requesting UserDBReader for '${targetUsername}'`);\n\n // Create a new sync strategy for the target user\n const syncStrategy = new CouchDBSyncStrategy();\n \n // Create a BaseUser instance for the target user\n // Note: This creates a read-capable user instance without affecting the current session\n const targetUserDB = await BaseUser.instance(syncStrategy, targetUsername);\n \n // Return as UserDBReader (which BaseUser implements since UserDBInterface extends UserDBReader)\n return targetUserDB as UserDBReader;\n }\n\n isReadOnly(): boolean {\n return false;\n }\n}\n","// packages/db/src/impl/static/StaticDataUnpacker.ts\n\nimport { StaticCourseManifest, ChunkMetadata } from '../../util/packer/types';\nimport { logger } from '../../util/logger';\nimport { DocType, DocTypePrefixes } from '@db/core';\n\n// Browser-compatible path utilities\nconst pathUtils = {\n isAbsolute: (path: string): boolean => {\n // Check for Windows absolute paths (C:\\ or \\\\server\\)\n if (/^[a-zA-Z]:[\\\\/]/.test(path) || /^\\\\\\\\/.test(path)) {\n return true;\n }\n // Check for Unix absolute paths (/)\n if (path.startsWith('/')) {\n return true;\n }\n return false;\n },\n};\n\n// Check if we're in Node.js environment and fs is available\nlet nodeFS: any = null;\ntry {\n // Use eval to prevent bundlers from including fs in browser builds\n if (typeof window === 'undefined' && typeof process !== 'undefined' && process.versions?.node) {\n nodeFS = eval('require')('fs');\n }\n} catch {\n // fs not available, will use fetch\n}\n\ninterface EloIndexEntry {\n elo: number;\n cardId: string;\n}\n\ninterface EloIndex {\n sorted: EloIndexEntry[];\n buckets: Record<string, EloIndexEntry[]>;\n stats: {\n min: number;\n max: number;\n count: number;\n };\n}\n\ninterface TagsIndex {\n byTag: Record<string, string[]>; // tagName -> cardIds\n byCard: Record<string, string[]>; // cardId -> tagNames\n}\n\nexport class StaticDataUnpacker {\n private manifest: StaticCourseManifest;\n private basePath: string;\n private documentCache: Map<string, any> = new Map();\n private chunkCache: Map<string, any[]> = new Map();\n private indexCache: Map<string, any> = new Map();\n\n constructor(manifest: StaticCourseManifest, basePath: string) {\n this.manifest = manifest;\n this.basePath = basePath;\n }\n\n /**\n * Get a document by ID, loading from appropriate chunk if needed\n */\n async getDocument<T = any>(id: string): Promise<T> {\n // Check document cache first\n if (this.documentCache.has(id)) {\n const doc = this.documentCache.get(id);\n return await this.hydrateAttachments(doc);\n }\n\n // Find which chunk contains this document\n const chunk = await this.findChunkForDocument(id);\n if (!chunk) {\n logger.error(\n `Document ${id} not found in any chunk. Available chunks:`,\n this.manifest.chunks.map((c) => `${c.id} (${c.docType}): ${c.startKey} - ${c.endKey}`)\n );\n throw new Error(`Document ${id} not found in any chunk`);\n }\n\n // Load the chunk if not cached\n await this.loadChunk(chunk.id);\n\n // Try to get the document from cache again\n if (this.documentCache.has(id)) {\n const doc = this.documentCache.get(id);\n return await this.hydrateAttachments(doc);\n }\n\n logger.error(`Document ${id} not found in chunk ${chunk.id}`);\n throw new Error(`Document ${id} not found in chunk ${chunk.id}`);\n }\n\n /**\n * Get all documents with IDs starting with a specific prefix.\n *\n * This method loads the relevant chunk(s) and returns all matching documents.\n * Useful for querying documents by type (e.g., all NAVIGATION_STRATEGY documents).\n *\n * @param prefix - Document ID prefix to match (e.g., \"NAVIGATION_STRATEGY\")\n * @returns Array of all documents with IDs starting with the prefix\n */\n async getAllDocumentsByPrefix(prefix: string): Promise<any[]> {\n // Find all chunks that could contain documents with this prefix\n // A chunk contains documents if the prefix falls within its startKey/endKey range\n const relevantChunks = this.manifest.chunks.filter((chunk) => {\n // Check if prefix could be in this chunk's range\n // Prefix matches if it's >= startKey and <= endKey (for lexicographic ordering)\n const prefixEnd = prefix + '\\ufff0'; // High unicode character for range end\n return chunk.startKey <= prefixEnd && chunk.endKey >= prefix;\n });\n\n if (relevantChunks.length === 0) {\n logger.debug(`[StaticDataUnpacker] No chunks found for prefix: ${prefix}`);\n return [];\n }\n\n // Load all relevant chunks\n await Promise.all(relevantChunks.map((chunk) => this.loadChunk(chunk.id)));\n\n // Filter documents from cache that match the prefix\n const matchingDocs: any[] = [];\n for (const [docId, doc] of this.documentCache.entries()) {\n if (docId.startsWith(prefix)) {\n matchingDocs.push(await this.hydrateAttachments(doc));\n }\n }\n\n logger.debug(\n `[StaticDataUnpacker] Found ${matchingDocs.length} documents with prefix: ${prefix}`\n );\n return matchingDocs;\n }\n\n /**\n * Query cards by ELO score, returning card IDs sorted by ELO\n */\n async queryByElo(targetElo: number, limit: number = 25): Promise<string[]> {\n const eloIndex = (await this.loadIndex('elo')) as EloIndex;\n\n if (!eloIndex || !eloIndex.sorted) {\n logger.warn('ELO index not found or malformed, returning empty results');\n return [];\n }\n\n // Find cards near the target ELO\n const sorted = eloIndex.sorted;\n let startIndex = 0;\n\n // Binary search to find insertion point for target ELO\n let left = 0;\n let right = sorted.length - 1;\n while (left <= right) {\n const mid = Math.floor((left + right) / 2);\n if (sorted[mid].elo < targetElo) {\n left = mid + 1;\n } else {\n right = mid - 1;\n }\n }\n startIndex = left;\n\n // Collect cards around the target ELO\n const result: string[] = [];\n const halfLimit = Math.floor(limit / 2);\n\n // Get cards below target ELO\n for (\n let i = Math.max(0, startIndex - halfLimit);\n i < startIndex && result.length < limit;\n i++\n ) {\n result.push(sorted[i].cardId);\n }\n\n // Get cards at or above target ELO\n for (let i = startIndex; i < sorted.length && result.length < limit; i++) {\n result.push(sorted[i].cardId);\n }\n\n return result;\n }\n\n /**\n * Get all tag names mapped to their card arrays\n */\n async getTagsIndex(): Promise<TagsIndex> {\n try {\n return (await this.loadIndex('tags')) as TagsIndex;\n } catch {\n // If no tags index exists, presume \"no tagged cards\"\n return {\n byCard: {},\n byTag: {},\n }\n }\n }\n\n private getDocTypeFromId(id: string): DocType | undefined {\n for (const docTypeKey in DocTypePrefixes) {\n const prefix = DocTypePrefixes[docTypeKey as DocType];\n if (id.startsWith(`${prefix}-`)) {\n return docTypeKey as DocType;\n }\n }\n return undefined;\n }\n\n /**\n * Find which chunk contains a specific document ID\n */\n private async findChunkForDocument(docId: string): Promise<ChunkMetadata | undefined> {\n const expectedDocType = this.getDocTypeFromId(docId);\n\n if (expectedDocType) {\n const typeChunks = this.manifest.chunks.filter((c) => c.docType === expectedDocType);\n\n for (const chunk of typeChunks) {\n if (docId >= chunk.startKey && docId <= chunk.endKey) {\n const exists = await this.verifyDocumentInChunk(docId, chunk);\n if (exists) {\n return chunk;\n }\n }\n }\n } else {\n // Fallback for documents without recognized prefixes (e.g., CourseConfig, or old documents)\n // This part remains for backward compatibility and non-prefixed documents.\n // It's less efficient but necessary if not all document types are prefixed.\n for (const chunk of this.manifest.chunks) {\n if (docId >= chunk.startKey && docId <= chunk.endKey) {\n // Verify document actually exists in chunk\n const exists = await this.verifyDocumentInChunk(docId, chunk);\n if (exists) {\n return chunk;\n }\n }\n }\n\n // Finally try any other chunk types\n const otherChunks = this.manifest.chunks.filter(\n (c) => c.docType !== 'CARD' && c.docType !== 'DISPLAYABLE_DATA' && c.docType !== 'TAG'\n );\n for (const chunk of otherChunks) {\n if (docId >= chunk.startKey && docId <= chunk.endKey) {\n // Verify document actually exists in chunk\n const exists = await this.verifyDocumentInChunk(docId, chunk);\n if (exists) {\n return chunk;\n }\n }\n }\n\n return undefined;\n }\n return undefined;\n }\n\n /**\n * Verify that a document actually exists in a specific chunk by loading and checking it\n */\n private async verifyDocumentInChunk(docId: string, chunk: ChunkMetadata): Promise<boolean> {\n try {\n // Load the chunk if not already cached\n await this.loadChunk(chunk.id);\n\n // Check if the document is now in our document cache\n return this.documentCache.has(docId);\n } catch {\n return false;\n }\n }\n\n /**\n * Load a chunk file and cache its documents\n */\n private async loadChunk(chunkId: string): Promise<void> {\n if (this.chunkCache.has(chunkId)) {\n return; // Already loaded\n }\n\n const chunk = this.manifest.chunks.find((c) => c.id === chunkId);\n if (!chunk) {\n throw new Error(`Chunk ${chunkId} not found in manifest`);\n }\n\n try {\n const chunkPath = `${this.basePath}/${chunk.path}`;\n logger.debug(`Loading chunk from ${chunkPath}`);\n\n let documents: any[];\n\n // Check if we're in a Node.js environment with local files\n if (this.isLocalPath(chunkPath) && nodeFS) {\n // Use fs for local file access (e.g., in tests)\n const fileContent = await nodeFS.promises.readFile(chunkPath, 'utf8');\n documents = JSON.parse(fileContent);\n } else {\n // Use fetch for URL-based access (e.g., in browser)\n const response = await fetch(chunkPath);\n if (!response.ok) {\n throw new Error(\n `Failed to fetch chunk ${chunkId}: ${response.status} ${response.statusText}`\n );\n }\n documents = await response.json();\n }\n\n this.chunkCache.set(chunkId, documents);\n\n // Cache individual documents for quick lookup\n for (const doc of documents) {\n if (doc._id) {\n this.documentCache.set(doc._id, doc);\n }\n }\n\n logger.debug(`Loaded ${documents.length} documents from chunk ${chunkId}`);\n } catch (error) {\n logger.error(`Failed to load chunk ${chunkId}:`, error);\n throw error;\n }\n }\n\n /**\n * Load an index file and cache it\n */\n private async loadIndex(indexName: string): Promise<any> {\n if (this.indexCache.has(indexName)) {\n return this.indexCache.get(indexName);\n }\n\n const indexMeta = this.manifest.indices.find((idx) => idx.name === indexName);\n if (!indexMeta) {\n throw new Error(`Index ${indexName} not found in manifest`);\n }\n\n try {\n const indexPath = `${this.basePath}/${indexMeta.path}`;\n logger.debug(`Loading index from ${indexPath}`);\n\n let indexData: any;\n\n // Check if we're in a Node.js environment with local files\n if (this.isLocalPath(indexPath) && nodeFS) {\n // Use fs for local file access (e.g., in tests)\n const fileContent = await nodeFS.promises.readFile(indexPath, 'utf8');\n indexData = JSON.parse(fileContent);\n } else {\n // Use fetch for URL-based access (e.g., in browser)\n const response = await fetch(indexPath);\n if (!response.ok) {\n throw new Error(\n `Failed to fetch index ${indexName}: ${response.status} ${response.statusText}`\n );\n }\n indexData = await response.json();\n }\n\n this.indexCache.set(indexName, indexData);\n\n logger.debug(`Loaded index ${indexName}`);\n return indexData;\n } catch (error) {\n logger.error(`Failed to load index ${indexName}:`, error);\n throw error;\n }\n }\n\n /**\n * Get a document by ID without hydration (raw document access)\n * Used internally to avoid recursion during attachment hydration\n */\n private async getRawDocument<T = any>(id: string): Promise<T> {\n // Check document cache first\n if (this.documentCache.has(id)) {\n return this.documentCache.get(id);\n }\n\n // Find which chunk contains this document\n const chunk = await this.findChunkForDocument(id);\n if (!chunk) {\n logger.error(\n `Document ${id} not found in any chunk. Available chunks:`,\n this.manifest.chunks.map((c) => `${c.id} (${c.docType}): ${c.startKey} - ${c.endKey}`)\n );\n throw new Error(`Document ${id} not found in any chunk`);\n }\n\n // Load the chunk if not cached\n await this.loadChunk(chunk.id);\n\n // Try to get the document from cache again\n if (this.documentCache.has(id)) {\n return this.documentCache.get(id);\n }\n\n logger.error(`Document ${id} not found in chunk ${chunk.id}`);\n throw new Error(`Document ${id} not found in chunk ${chunk.id}`);\n }\n\n /**\n * Get attachment URL for a given document and attachment name\n */\n getAttachmentUrl(docId: string, attachmentName: string): string {\n // Construct attachment path: attachments/{docId}/{attachmentName}.{ext}\n // The exact filename will be resolved from the document's _attachments metadata\n return `${this.basePath}/attachments/${docId}/${attachmentName}`;\n }\n\n /**\n * Load attachment data from the document and get the correct file path\n * Uses raw document access to avoid recursion with hydration\n */\n async getAttachmentPath(docId: string, attachmentName: string): Promise<string | null> {\n try {\n const doc = await this.getRawDocument(docId);\n if (doc._attachments && doc._attachments[attachmentName]) {\n const attachment = doc._attachments[attachmentName];\n if (attachment.path) {\n // Return the full path as stored in the document\n return `${this.basePath}/${attachment.path}`;\n }\n }\n return null;\n } catch {\n return null;\n }\n }\n\n /**\n * Load attachment as a blob (for browser) or buffer (for Node.js)\n */\n async getAttachmentBlob(docId: string, attachmentName: string): Promise<Blob | Buffer | null> {\n const attachmentPath = await this.getAttachmentPath(docId, attachmentName);\n if (!attachmentPath) {\n return null;\n }\n\n try {\n // Check if we're in a Node.js environment with local files\n if (this.isLocalPath(attachmentPath) && nodeFS) {\n // Use fs for local file access (e.g., in tests)\n const buffer = await nodeFS.promises.readFile(attachmentPath);\n return buffer;\n } else {\n // Use fetch for URL-based access (e.g., in browser)\n const response = await fetch(attachmentPath);\n if (!response.ok) {\n throw new Error(\n `Failed to fetch attachment ${docId}/${attachmentName}: ${response.status} ${response.statusText}`\n );\n }\n return await response.blob();\n }\n } catch (error) {\n logger.error(`Failed to load attachment ${docId}/${attachmentName}:`, error);\n return null;\n }\n }\n\n /**\n * Hydrate document attachments by converting file paths to blob URLs\n */\n private async hydrateAttachments<T = any>(doc: T): Promise<T> {\n // logger.debug(`[hydrateAttachments] Starting hydration for doc: ${JSON.stringify(doc)}`);\n const typedDoc = doc as any;\n\n // If no attachments, return document as-is\n if (!typedDoc._attachments) {\n return doc;\n }\n\n // Clone the document to avoid mutating the cached version\n const hydratedDoc = JSON.parse(JSON.stringify(doc));\n\n // Process each attachment\n for (const [attachmentName, attachment] of Object.entries(typedDoc._attachments)) {\n // logger.debug(\n // `[hydrateAttachments] Processing attachment: ${attachmentName} for doc ${typedDoc?._id}`\n // );\n const attachmentData = attachment as any;\n\n // If attachment has a path, convert it to a blob URL\n if (attachmentData.path) {\n // logger.debug(\n // `[hydrateAttachments] Attachment ${attachmentName} has path: ${attachmentData.path}. Attempting to get blob.`\n // );\n try {\n const blob = await this.getAttachmentBlob(typedDoc._id, attachmentName);\n if (blob) {\n // logger.debug(\n // `[hydrateAttachments] Successfully retrieved blob for ${typedDoc._id}/${attachmentName}. Size: ${blob instanceof Blob ? blob.size : (blob as Buffer).length}`\n // );\n // Create blob URL for browser rendering\n if (typeof window !== 'undefined' && window.URL) {\n // Store attachment data in PouchDB-compatible format\n hydratedDoc._attachments[attachmentName] = {\n ...attachmentData,\n data: blob,\n stub: false, // Indicates this contains actual data, not just metadata\n };\n // logger.debug(\n // `[hydrateAttachments] Added blobUrl and blob to attachment ${attachmentName} for doc ${typedDoc._id}.`\n // );\n } else {\n // In Node.js environment, just attach the buffer\n hydratedDoc._attachments[attachmentName] = {\n ...attachmentData,\n buffer: blob, // Attach buffer for Node.js use\n };\n // logger.debug(\n // `[hydrateAttachments] Added buffer to attachment ${attachmentName} for doc ${typedDoc._id}.`\n // );\n }\n } else {\n logger.warn(\n `[hydrateAttachments] getAttachmentBlob returned null for ${typedDoc._id}/${attachmentName}. Skipping hydration for this attachment.`\n );\n }\n } catch (error) {\n logger.warn(\n `[hydrateAttachments] Failed to hydrate attachment ${typedDoc._id}/${attachmentName}:`,\n error\n );\n // Keep original attachment data if hydration fails\n }\n } else {\n logger.debug(\n `[hydrateAttachments] Attachment ${attachmentName} for doc ${typedDoc._id} has no path. Skipping blob conversion.`\n );\n }\n }\n\n // logger.debug(\n // `[hydrateAttachments] Finished hydration for doc ${typedDoc?._id}. Returning hydrated doc: ${JSON.stringify(hydratedDoc)}`\n // );\n return hydratedDoc;\n }\n\n /**\n * Clear all caches (useful for testing or memory management)\n */\n clearCaches(): void {\n this.documentCache.clear();\n this.chunkCache.clear();\n this.indexCache.clear();\n }\n\n /**\n * Get cache statistics for debugging\n */\n getCacheStats(): {\n documents: number;\n chunks: number;\n indices: number;\n } {\n return {\n documents: this.documentCache.size,\n chunks: this.chunkCache.size,\n indices: this.indexCache.size,\n };\n }\n\n /**\n * Check if a path is a local file path (vs URL)\n */\n private isLocalPath(filePath: string): boolean {\n // Check if it's an absolute path or doesn't start with http/https\n return (\n !filePath.startsWith('http://') &&\n !filePath.startsWith('https://') &&\n (pathUtils.isAbsolute(filePath) || filePath.startsWith('./') || filePath.startsWith('../'))\n );\n }\n}\n","// packages/db/src/impl/static/courseDB.ts\n\nimport {\n CourseDBInterface,\n UserDBInterface,\n CourseInfo,\n StudySessionItem,\n} from '../../core/interfaces';\nimport { StaticDataUnpacker } from './StaticDataUnpacker';\nimport { StaticCourseManifest } from '../../util/packer/types';\nimport { CourseConfig, CourseElo, DataShape, Status } from '@vue-skuilder/common';\nimport {\n Tag,\n TagStub,\n DocType,\n SkuilderCourseData,\n QualifiedCardID,\n DocTypePrefixes,\n} from '../../core/types/types-legacy';\nimport { DataLayerResult } from '../../core/types/db';\nimport { ContentNavigationStrategyData } from '../../core/types/contentNavigationStrategy';\n\nimport { ContentNavigator, WeightedCard } from '../../core/navigators';\nimport { logger } from '../../util/logger';\nimport { createDefaultPipeline } from '@db/core/navigators/defaults';\nimport { PipelineAssembler } from '@db/core/navigators/PipelineAssembler';\n\nexport class StaticCourseDB implements CourseDBInterface {\n constructor(\n private courseId: string,\n private unpacker: StaticDataUnpacker,\n private userDB: UserDBInterface,\n private manifest: StaticCourseManifest\n ) {}\n\n getCourseID(): string {\n return this.courseId;\n }\n\n async getCourseConfig(): Promise<CourseConfig> {\n if (this.manifest.courseConfig != null) {\n return this.manifest.courseConfig;\n } else {\n throw new Error(`Course config not found for course ${this.courseId}`);\n }\n }\n\n async updateCourseConfig(_cfg: CourseConfig): Promise<PouchDB.Core.Response> {\n throw new Error('Cannot update course config in static mode');\n }\n\n async getCourseInfo(): Promise<CourseInfo> {\n // Count only cards, not all documents\n // Use chunks metadata to count card documents specifically\n const cardCount = this.manifest.chunks\n .filter((chunk) => chunk.docType === DocType.CARD)\n .reduce((total, chunk) => total + chunk.documentCount, 0);\n\n return {\n cardCount,\n registeredUsers: 0, // Always 0 in static mode\n };\n }\n\n async getCourseDoc<T extends SkuilderCourseData>(\n id: string,\n _options?: PouchDB.Core.GetOptions\n ): Promise<T> {\n return this.unpacker.getDocument(id);\n }\n\n async getCourseDocs<T extends SkuilderCourseData>(\n ids: string[],\n _options?: PouchDB.Core.AllDocsOptions\n ): Promise<PouchDB.Core.AllDocsWithKeysResponse<{} & T>> {\n const rows = await Promise.all(\n ids.map(async (id) => {\n try {\n const doc = await this.unpacker.getDocument(id);\n return {\n id,\n key: id,\n value: { rev: '1-static' },\n doc,\n };\n } catch {\n return {\n key: id,\n error: 'not_found' as const,\n };\n }\n })\n );\n\n return {\n total_rows: ids.length,\n offset: 0,\n rows,\n };\n }\n\n async getCardsByELO(\n elo: number,\n limit?: number\n ): Promise<\n {\n courseID: string;\n cardID: string;\n elo?: number;\n }[]\n > {\n return (await this.unpacker.queryByElo(elo, limit || 25)).map((card) => {\n const [courseID, cardID, elo] = card.split('-');\n return { courseID, cardID, elo: elo ? parseInt(elo) : undefined };\n });\n }\n\n async getCardEloData(cardIds: string[]): Promise<CourseElo[]> {\n const results = await Promise.all(\n cardIds.map(async (id) => {\n try {\n const card = await this.unpacker.getDocument(id);\n return card.elo || { global: { score: 1000, count: 0 }, tags: {}, misc: {} };\n } catch {\n return { global: { score: 1000, count: 0 }, tags: {}, misc: {} };\n }\n })\n );\n return results;\n }\n\n async updateCardElo(cardId: string, _elo: CourseElo): Promise<PouchDB.Core.Response> {\n // No updates to card data in static mode - this is a noop\n return { ok: true, id: cardId, rev: '1-static' };\n }\n\n async getCardsCenteredAtELO(\n options: { limit: number; elo: 'user' | 'random' | number },\n filter?: (id: QualifiedCardID) => boolean\n ): Promise<StudySessionItem[]> {\n let targetElo = typeof options.elo === 'number' ? options.elo : 1000;\n\n if (options.elo === 'user') {\n // Get user's ELO for this course\n try {\n const regDoc = await this.userDB.getCourseRegistrationsDoc();\n const courseReg = regDoc.courses.find((c) => c.courseID === this.courseId);\n if (courseReg && typeof courseReg.elo === 'object') {\n targetElo = courseReg.elo.global.score;\n }\n } catch {\n targetElo = 1000;\n }\n } else if (options.elo === 'random') {\n targetElo = 800 + Math.random() * 400; // Random between 800-1200\n }\n\n let cardIds = (await this.unpacker.queryByElo(targetElo, options.limit * 2)).map((c) => {\n return {\n cardID: c,\n courseID: this.courseId,\n };\n });\n\n if (filter) {\n cardIds = cardIds.filter(filter);\n }\n\n return cardIds.slice(0, options.limit).map((card) => ({\n status: 'new' as const,\n // qualifiedID: `${this.courseId}-${cardId}`,\n cardID: card.cardID,\n contentSourceType: 'course' as const,\n contentSourceID: this.courseId,\n courseID: this.courseId,\n }));\n }\n\n async getAppliedTags(cardId: string): Promise<PouchDB.Query.Response<TagStub>> {\n try {\n const tagsIndex = await this.unpacker.getTagsIndex();\n const cardTags = tagsIndex.byCard[cardId] || [];\n\n const rows = await Promise.all(\n cardTags.map(async (tagName) => {\n const tagId = `${DocType.TAG}-${tagName}`;\n\n try {\n // Try to get the full tag document\n const tagDoc = await this.unpacker.getDocument(tagId);\n return {\n id: tagId,\n key: cardId,\n value: {\n name: tagDoc.name,\n snippet: tagDoc.snippet,\n count: tagDoc.taggedCards?.length || 0,\n },\n };\n } catch (error) {\n if (error && (error as PouchDB.Core.Error).status === 404) {\n logger.warn(`Tag document not found for ${tagName}, creating stub`);\n } else {\n logger.error(`Error getting tag document for ${tagName}:`, error);\n throw error;\n }\n // If tag document not found, create a minimal stub\n return {\n id: tagId,\n key: cardId,\n value: {\n name: tagName,\n snippet: `Tag: ${tagName}`,\n count: tagsIndex.byTag[tagName]?.length || 0,\n },\n };\n }\n })\n );\n\n return {\n total_rows: rows.length,\n offset: 0,\n rows,\n };\n } catch (error) {\n logger.error(`Error getting applied tags for card ${cardId}:`, error);\n return {\n total_rows: 0,\n offset: 0,\n rows: [],\n };\n }\n }\n\n async getAppliedTagsBatch(cardIds: string[]): Promise<Map<string, string[]>> {\n const tagsIndex = await this.unpacker.getTagsIndex();\n const tagsByCard = new Map<string, string[]>();\n\n for (const cardId of cardIds) {\n tagsByCard.set(cardId, tagsIndex.byCard[cardId] || []);\n }\n\n return tagsByCard;\n }\n\n async addTagToCard(_cardId: string, _tagId: string): Promise<PouchDB.Core.Response> {\n throw new Error('Cannot modify tags in static mode');\n }\n\n async removeTagFromCard(_cardId: string, _tagId: string): Promise<PouchDB.Core.Response> {\n throw new Error('Cannot modify tags in static mode');\n }\n\n async createTag(_tagName: string): Promise<PouchDB.Core.Response> {\n throw new Error('Cannot create tags in static mode');\n }\n\n async getTag(tagName: string): Promise<Tag> {\n return this.unpacker.getDocument(`${DocType.TAG}-${tagName}`);\n }\n\n async updateTag(_tag: Tag): Promise<PouchDB.Core.Response> {\n throw new Error('Cannot update tags in static mode');\n }\n\n async getCourseTagStubs(): Promise<PouchDB.Core.AllDocsResponse<Tag>> {\n try {\n const tagsIndex = await this.unpacker.getTagsIndex();\n\n if (!tagsIndex || !tagsIndex.byTag) {\n logger.warn('Tags index not found or empty');\n return {\n total_rows: 0,\n offset: 0,\n rows: [],\n };\n }\n\n // Create tag stubs from the index\n const tagNames = Object.keys(tagsIndex.byTag);\n const rows = await Promise.all(\n tagNames.map(async (tagName) => {\n const cardIds = tagsIndex.byTag[tagName] || [];\n const tagId = `${DocType.TAG}-${tagName}`;\n\n try {\n // Try to get the full tag document\n const tagDoc = await this.unpacker.getDocument(tagId);\n return {\n id: tagId,\n key: tagId,\n value: { rev: '1-static' },\n doc: tagDoc,\n };\n } catch (error) {\n // If tag document not found, create a minimal stub\n if (error && (error as PouchDB.Core.Error).status === 404) {\n logger.warn(`Tag document not found for ${tagName}, creating stub`);\n const stubDoc = {\n _id: tagId,\n _rev: '1-static',\n course: this.courseId,\n docType: DocType.TAG,\n name: tagName,\n snippet: `Tag: ${tagName}`,\n wiki: '',\n taggedCards: cardIds,\n author: 'system',\n };\n return {\n id: tagId,\n key: tagId,\n value: { rev: '1-static' },\n doc: stubDoc,\n };\n } else {\n logger.error(`Error getting tag document for ${tagName}:`, error);\n throw error;\n }\n }\n })\n );\n\n return {\n total_rows: rows.length,\n offset: 0,\n rows,\n };\n } catch (error) {\n logger.error('Failed to get course tag stubs:', error);\n return {\n total_rows: 0,\n offset: 0,\n rows: [],\n };\n }\n }\n\n async addNote(\n _codeCourse: string,\n _shape: DataShape,\n _data: unknown,\n _author: string,\n _tags: string[],\n _uploads?: { [key: string]: PouchDB.Core.FullAttachment },\n _elo?: CourseElo\n ): Promise<DataLayerResult> {\n return {\n status: Status.error,\n message: 'Cannot add notes in static mode',\n };\n }\n\n async removeCard(_cardId: string): Promise<PouchDB.Core.Response> {\n throw new Error('Cannot remove cards in static mode');\n }\n\n async getInexperiencedCards(): Promise<any[]> {\n // Would need to be pre-computed in indices\n return [];\n }\n\n // Navigation Strategy Manager implementation\n async getNavigationStrategy(id: string): Promise<ContentNavigationStrategyData> {\n try {\n return await this.unpacker.getDocument(id);\n } catch (error) {\n logger.error(`[static/courseDB] Strategy ${id} not found: ${error}`);\n throw error;\n }\n }\n\n async getAllNavigationStrategies(): Promise<ContentNavigationStrategyData[]> {\n const prefix = DocTypePrefixes[DocType.NAVIGATION_STRATEGY];\n try {\n const docs = await this.unpacker.getAllDocumentsByPrefix(prefix);\n return docs as ContentNavigationStrategyData[];\n } catch (error) {\n logger.warn(`[static/courseDB] Error loading navigation strategies: ${error}`);\n return []; // Fall back to default pipeline\n }\n }\n\n async addNavigationStrategy(_data: ContentNavigationStrategyData): Promise<void> {\n throw new Error('Cannot add navigation strategies in static mode');\n }\n\n async updateNavigationStrategy(_id: string, _data: ContentNavigationStrategyData): Promise<void> {\n throw new Error('Cannot update navigation strategies in static mode');\n }\n\n /**\n * Create a ContentNavigator for this course.\n *\n * Loads navigation strategy documents from static data and uses PipelineAssembler\n * to build a Pipeline. Falls back to default pipeline if no strategies found.\n */\n async createNavigator(user: UserDBInterface): Promise<ContentNavigator> {\n try {\n const allStrategies = await this.getAllNavigationStrategies();\n\n if (allStrategies.length === 0) {\n logger.debug(\n '[static/courseDB] No strategy documents found, using default Pipeline(Composite(ELO, SRS), [eloDistanceFilter])'\n );\n return createDefaultPipeline(user, this);\n }\n\n // Use PipelineAssembler to build Pipeline from strategy documents\n const assembler = new PipelineAssembler();\n const { pipeline, generatorStrategies, filterStrategies, warnings } =\n await assembler.assemble({\n strategies: allStrategies,\n user,\n course: this,\n });\n\n // Log warnings\n for (const warning of warnings) {\n logger.warn(`[PipelineAssembler] ${warning}`);\n }\n\n if (!pipeline) {\n logger.debug('[static/courseDB] Pipeline assembly failed, using default pipeline');\n return createDefaultPipeline(user, this);\n }\n\n logger.debug(\n `[static/courseDB] Using assembled pipeline with ${generatorStrategies.length} generator(s) and ${filterStrategies.length} filter(s)`\n );\n return pipeline;\n } catch (e) {\n logger.error(`[static/courseDB] Error creating navigator: ${e}`);\n throw e;\n }\n }\n\n // Study Content Source implementation\n async getWeightedCards(limit: number): Promise<WeightedCard[]> {\n try {\n const navigator = await this.createNavigator(this.userDB);\n return navigator.getWeightedCards(limit);\n } catch (e) {\n logger.error(`[static/courseDB] Error getting weighted cards: ${e}`);\n throw e;\n }\n }\n\n // Attachment helper methods (internal use, not part of interface)\n\n /**\n * Get attachment URL for a document and attachment name\n * Internal helper method for static attachment serving\n */\n getAttachmentUrl(docId: string, attachmentName: string): string {\n return this.unpacker.getAttachmentUrl(docId, attachmentName);\n }\n\n /**\n * Load attachment as blob/buffer\n * Internal helper method for static attachment serving\n */\n async getAttachmentBlob(docId: string, attachmentName: string): Promise<Blob | Buffer | null> {\n return this.unpacker.getAttachmentBlob(docId, attachmentName);\n }\n\n // Admin search methods\n async searchCards(_query: string): Promise<any[]> {\n // In static mode, return empty results for now\n // Could be implemented with local search if needed\n return [];\n }\n\n async find(_request: PouchDB.Find.FindRequest<any>): Promise<PouchDB.Find.FindResponse<any>> {\n // In static mode, return empty results for now\n // Could be implemented with local search if needed\n return {\n docs: [],\n warning: 'Find operations not supported in static mode',\n } as any;\n }\n}\n","// packages/db/src/impl/static/coursesDB.ts\n\nimport { CoursesDBInterface } from '../../core/interfaces';\nimport { CourseConfig } from '@vue-skuilder/common';\nimport { StaticCourseManifest } from '../../util/packer/types';\nimport { logger } from '../../util/logger';\n\nexport class StaticCoursesDB implements CoursesDBInterface {\n constructor(\n private manifests: Record<string, StaticCourseManifest>,\n private dependencyNameToCourseId?: Map<string, string>\n ) {}\n\n async getCourseConfig(courseId: string): Promise<CourseConfig> {\n // Try direct lookup by courseId first\n let manifest = this.manifests[courseId];\n\n // If not found, try lookup by dependency name (backwards compatibility)\n if (!manifest && this.dependencyNameToCourseId) {\n const mappedCourseId = this.dependencyNameToCourseId.get(courseId);\n if (mappedCourseId) {\n manifest = this.manifests[mappedCourseId];\n }\n }\n\n if (!manifest) {\n logger.warn(`Course manifest for ${courseId} not found`);\n throw new Error(`Course ${courseId} not found`);\n }\n\n if (manifest.courseConfig) {\n return manifest.courseConfig;\n } else {\n logger.warn(`Course config not found in manifest for course ${courseId}`);\n throw new Error(`Course config not found for course ${courseId}`);\n }\n }\n\n async getCourseList(): Promise<CourseConfig[]> {\n // Return configs for all available courses\n return Object.keys(this.manifests).map(\n (courseId) =>\n ({\n courseID: courseId,\n name: this.manifests[courseId].courseName,\n // ... other config fields\n }) as CourseConfig\n );\n }\n\n async disambiguateCourse(_courseId: string, _disambiguator: string): Promise<void> {\n logger.warn('Cannot disambiguate courses in static mode');\n }\n}\n","// packages/db/src/impl/static/NoOpSyncStrategy.ts\nimport type { SyncStrategy } from '../common/SyncStrategy';\nimport type { AccountCreationResult, AuthenticationResult } from '../common/types';\nimport { getLocalUserDB, accomodateGuest } from '../common';\n\n/**\n * Sync strategy for static deployments that provides no-op implementations\n * for remote operations. Uses only local storage with no remote synchronization.\n */\nexport class NoOpSyncStrategy implements SyncStrategy {\n private currentUsername: string = accomodateGuest().username;\n\n setupRemoteDB(username: string): PouchDB.Database {\n // Return the same database instance as local - sync with self is a no-op\n return getLocalUserDB(username);\n }\n\n getWriteDB(username: string): PouchDB.Database {\n // In static mode, always write to local database\n return getLocalUserDB(username);\n }\n\n startSync(_localDB: PouchDB.Database, _remoteDB: PouchDB.Database): void {\n // No-op - in static mode, local and remote are the same database instance\n // PouchDB sync with itself is harmless and efficient\n }\n\n stopSync?(): void {\n // No-op - no sync to stop in static mode\n }\n\n canCreateAccount(): boolean {\n return false; // Account creation not supported in static mode\n }\n\n canAuthenticate(): boolean {\n return false; // Remote authentication not supported in static mode\n }\n\n async createAccount(_username: string, _password: string): Promise<AccountCreationResult> {\n throw new Error(\n 'Account creation not supported in static mode. Use local account switching instead.'\n );\n }\n\n async authenticate(_username: string, _password: string): Promise<AuthenticationResult> {\n throw new Error(\n 'Remote authentication not supported in static mode. Use local account switching instead.'\n );\n }\n\n async logout(): Promise<AuthenticationResult> {\n // In static mode, \"logout\" means switch back to guest user\n this.currentUsername = accomodateGuest().username;\n return {\n ok: true,\n };\n }\n\n async getCurrentUsername(): Promise<string> {\n // In static mode, always return guest username\n // TODO: This will be enhanced with local account switching to support multiple local users\n return this.currentUsername;\n }\n\n /**\n * Set the current username (for local account switching)\n * This method is specific to NoOpSyncStrategy and not part of the base interface\n */\n setCurrentUsername(username: string): void {\n this.currentUsername = username;\n }\n}\n","\n// packages/db/src/impl/static/StaticDataLayerProvider.ts\n\nimport {\n AdminDBInterface,\n ClassroomDBInterface,\n CoursesDBInterface,\n CourseDBInterface,\n DataLayerProvider,\n UserDBInterface,\n UserDBReader,\n} from '../../core/interfaces';\nimport { logger } from '../../util/logger';\nimport { StaticCourseManifest } from '../../util/packer/types';\nimport { StaticDataUnpacker } from './StaticDataUnpacker';\nimport { StaticCourseDB } from './courseDB';\nimport { StaticCoursesDB } from './coursesDB';\nimport { BaseUser } from '../common';\nimport { NoOpSyncStrategy } from './NoOpSyncStrategy';\n\n\ninterface SkuilderManifest {\n name?: string;\n version?: string;\n description?: string;\n dependencies?: Record<string, string>;\n}\n\ninterface StaticDataLayerConfig {\n localStoragePrefix?: string;\n rootManifest: SkuilderManifest; // The parsed root skuilder.json object\n rootManifestUrl: string; // The absolute URL where the root manifest was found\n}\n\nexport class StaticDataLayerProvider implements DataLayerProvider {\n private config: StaticDataLayerConfig;\n private initialized: boolean = false;\n private courseUnpackers: Map<string, StaticDataUnpacker> = new Map();\n private manifests: Record<string, StaticCourseManifest> = {};\n // Mapping from dependency name to actual courseId for backwards compatibility\n private dependencyNameToCourseId: Map<string, string> = new Map();\n\n constructor(config: Partial<StaticDataLayerConfig>) {\n this.config = {\n localStoragePrefix: config.localStoragePrefix || 'skuilder-static',\n rootManifest: config.rootManifest || { dependencies: {} },\n rootManifestUrl: config.rootManifestUrl || '/',\n };\n }\n\n private async resolveCourseDependencies(): Promise<void> {\n logger.info('[StaticDataLayerProvider] Starting course dependency resolution...');\n const rootManifest = this.config.rootManifest;\n\n for (const [courseName, courseUrl] of Object.entries(rootManifest.dependencies || {})) {\n try {\n logger.debug(`[StaticDataLayerProvider] Resolving dependency: ${courseName} from ${courseUrl}`);\n \n const courseManifestUrl = new URL(courseUrl as string, this.config.rootManifestUrl).href;\n const courseJsonResponse = await fetch(courseManifestUrl);\n if (!courseJsonResponse.ok) {\n throw new Error(`Failed to fetch course manifest for ${courseName}`);\n }\n const courseJson = await courseJsonResponse.json();\n\n if (courseJson.content && courseJson.content.manifest) {\n const baseUrl = new URL('.', courseManifestUrl).href;\n const finalManifestUrl = new URL(courseJson.content.manifest, courseManifestUrl).href;\n\n const finalManifestResponse = await fetch(finalManifestUrl);\n if (!finalManifestResponse.ok) {\n throw new Error(`Failed to fetch final content manifest for ${courseName} at ${finalManifestUrl}`);\n }\n const finalManifest = await finalManifestResponse.json();\n\n // Extract courseId from the manifest to use as the lookup key\n const courseId = finalManifest.courseId || finalManifest.courseConfig?.courseID;\n if (!courseId) {\n throw new Error(`Course manifest for ${courseName} missing courseId`);\n }\n\n this.manifests[courseId] = finalManifest;\n const unpacker = new StaticDataUnpacker(finalManifest, baseUrl);\n this.courseUnpackers.set(courseId, unpacker);\n\n // Also store mapping from dependency name to courseId for backwards compatibility\n // This allows lookup by either dependency name or courseId\n this.dependencyNameToCourseId.set(courseName, courseId);\n\n logger.info(`[StaticDataLayerProvider] Successfully resolved and prepared course: ${courseName} (courseId: ${courseId})`);\n }\n } catch (e) {\n logger.error(`[StaticDataLayerProvider] Failed to resolve dependency ${courseName}:`, e);\n // Continue to next dependency\n }\n }\n logger.info('[StaticDataLayerProvider] Course dependency resolution complete.');\n }\n\n async initialize(): Promise<void> {\n if (this.initialized) return;\n\n logger.info('Initializing static data layer provider');\n await this.resolveCourseDependencies();\n this.initialized = true;\n }\n\n async teardown(): Promise<void> {\n this.courseUnpackers.clear();\n this.initialized = false;\n }\n\n getUserDB(): UserDBInterface {\n const syncStrategy = new NoOpSyncStrategy();\n // For now, use guest user - local account switching will be added later\n return BaseUser.Dummy(syncStrategy);\n }\n\n getCourseDB(courseId: string): CourseDBInterface {\n // Try direct lookup by courseId first\n let unpacker = this.courseUnpackers.get(courseId);\n let actualCourseId = courseId;\n\n // If not found, try lookup by dependency name (backwards compatibility)\n if (!unpacker) {\n const mappedCourseId = this.dependencyNameToCourseId.get(courseId);\n if (mappedCourseId) {\n unpacker = this.courseUnpackers.get(mappedCourseId);\n actualCourseId = mappedCourseId;\n }\n }\n\n if (!unpacker) {\n throw new Error(`Course ${courseId} not found or failed to initialize in static data layer.`);\n }\n const manifest = this.manifests[actualCourseId];\n return new StaticCourseDB(actualCourseId, unpacker, this.getUserDB(), manifest);\n }\n\n getCoursesDB(): CoursesDBInterface {\n return new StaticCoursesDB(this.manifests, this.dependencyNameToCourseId);\n }\n\n async getClassroomDB(\n _classId: string,\n _type: 'student' | 'teacher'\n ): Promise<ClassroomDBInterface> {\n throw new Error('Classrooms not supported in static mode');\n }\n\n getAdminDB(): AdminDBInterface {\n throw new Error('Admin functions not supported in static mode');\n }\n\n async createUserReaderForUser(targetUsername: string): Promise<UserDBReader> {\n logger.warn(`StaticDataLayerProvider: Multi-user access not supported in static mode`);\n logger.warn(`Request: trying to access data for ${targetUsername}`);\n logger.warn(`Returning current user's data instead`);\n \n // In static mode, just return the current user's DB as a reader\n // This is safe since static mode is typically for development/testing\n return this.getUserDB() as UserDBReader;\n }\n\n isReadOnly(): boolean {\n return true;\n }\n}\n","// db/src/factory.ts\n\nimport { DataLayerProvider } from './core/interfaces';\nimport { BaseUser } from './impl/common';\nimport { logger } from './util/logger';\nimport { StaticCourseManifest } from './util/packer/types';\n\nconst NOT_SET = 'NOT_SET' as const;\n\ninterface DBEnv {\n COUCHDB_SERVER_URL: string; // URL of CouchDB server\n COUCHDB_SERVER_PROTOCOL: string; // Protocol of CouchDB server (http or https)\n COUCHDB_USERNAME?: string;\n COUCHDB_PASSWORD?: string;\n LOCAL_STORAGE_PREFIX: string; // Prefix for IndexedDB storage names\n}\n\nexport const ENV: DBEnv = {\n COUCHDB_SERVER_PROTOCOL: NOT_SET,\n COUCHDB_SERVER_URL: NOT_SET,\n LOCAL_STORAGE_PREFIX: '',\n};\n\nexport { NOT_SET };\n\n// Configuration type for data layer initialization\nexport interface DataLayerConfig {\n type: 'couch' | 'static';\n options: {\n staticContentPath?: string; // Path to static content JSON files\n localStoragePrefix?: string; // Prefix for IndexedDB storage names\n manifests?: Record<string, StaticCourseManifest>; // Course manifests for static mode\n COUCHDB_SERVER_URL?: string;\n COUCHDB_SERVER_PROTOCOL?: string;\n COUCHDB_USERNAME?: string;\n COUCHDB_PASSWORD?: string;\n\n COURSE_IDS?: string[];\n };\n}\n\n// Singleton instance\nlet dataLayerInstance: DataLayerProvider | null = null;\n\n/**\n * Initialize the data layer with the specified configuration\n */\nexport async function initializeDataLayer(config: DataLayerConfig): Promise<DataLayerProvider> {\n if (dataLayerInstance) {\n logger.warn('Data layer already initialized. Returning existing instance.');\n return dataLayerInstance;\n }\n if (config.options.localStoragePrefix) {\n ENV.LOCAL_STORAGE_PREFIX = config.options.localStoragePrefix;\n }\n\n if (config.type === 'couch') {\n if (!config.options.COUCHDB_SERVER_URL || !config.options.COUCHDB_SERVER_PROTOCOL) {\n throw new Error('Missing CouchDB server URL or protocol');\n }\n ENV.COUCHDB_SERVER_PROTOCOL = config.options.COUCHDB_SERVER_PROTOCOL;\n ENV.COUCHDB_SERVER_URL = config.options.COUCHDB_SERVER_URL;\n ENV.COUCHDB_USERNAME = config.options.COUCHDB_USERNAME;\n ENV.COUCHDB_PASSWORD = config.options.COUCHDB_PASSWORD;\n\n if (\n config.options.COUCHDB_PASSWORD &&\n config.options.COUCHDB_USERNAME &&\n typeof window !== 'undefined'\n ) {\n // Dynamic import to avoid loading both implementations when only one is needed\n const { CouchDBSyncStrategy } = await import('./impl/couch/CouchDBSyncStrategy');\n\n // Create a sync strategy instance and authenticate\n const syncStrategy = new CouchDBSyncStrategy();\n\n const user = await BaseUser.instance(syncStrategy, config.options.COUCHDB_USERNAME);\n const authResult = await user.login(\n config.options.COUCHDB_USERNAME,\n config.options.COUCHDB_PASSWORD\n );\n\n if (authResult.ok) {\n logger.info(`Successfully authenticated as ${config.options.COUCHDB_USERNAME}`);\n } else {\n logger.warn(`Authentication failed: ${authResult.error}`);\n }\n }\n\n // Dynamic import to avoid loading both implementations when only one is needed\n const { CouchDataLayerProvider } = await import('./impl/couch/PouchDataLayerProvider');\n dataLayerInstance = new CouchDataLayerProvider(config.options.COURSE_IDS);\n } else if (config.type === 'static') {\n const { StaticDataLayerProvider } = await import('./impl/static/StaticDataLayerProvider');\n dataLayerInstance = new StaticDataLayerProvider(config.options);\n } else {\n throw new Error(`Unknown data layer type: ${config.type}`);\n }\n\n await dataLayerInstance.initialize();\n return dataLayerInstance;\n}\n\n/**\n * Get the initialized data layer instance\n * @throws Error if not initialized\n */\nexport function getDataLayer(): DataLayerProvider {\n if (!dataLayerInstance) {\n throw new Error('Data layer not initialized. Call initializeDataLayer first.');\n }\n return dataLayerInstance;\n}\n\n/**\n * Reset the data layer (primarily for testing)\n */\nexport async function _resetDataLayer(): Promise<void> {\n if (dataLayerInstance) {\n await dataLayerInstance.teardown();\n }\n dataLayerInstance = null;\n}\n","import { StudyContentSource } from '@db/core/interfaces/contentSource';\nimport { WeightedCard } from '@db/core/navigators';\nimport { UserDBInterface } from '@db/core';\nimport { TagFilter, hasActiveFilter } from '@vue-skuilder/common';\nimport { getTag } from '../impl/couch/courseDB';\nimport { logger } from '@db/util/logger';\n\n/**\n * A StudyContentSource that filters cards based on tag inclusion/exclusion.\n *\n * This enables ephemeral, tag-scoped study sessions where users can focus\n * on specific topics within a course without permanent configuration.\n *\n * Filter logic:\n * - If `include` is non-empty: card must have at least one of the included tags\n * - If `exclude` is non-empty: card must not have any of the excluded tags\n * - Both filters are applied (include first, then exclude)\n */\nexport class TagFilteredContentSource implements StudyContentSource {\n private courseId: string;\n private filter: TagFilter;\n private user: UserDBInterface;\n\n // Cache resolved card IDs to avoid repeated lookups within a session\n private resolvedCardIds: Set<string> | null = null;\n\n constructor(courseId: string, filter: TagFilter, user: UserDBInterface) {\n this.courseId = courseId;\n this.filter = filter;\n this.user = user;\n\n logger.info(\n `[TagFilteredContentSource] Created for course \"${courseId}\" with filter:`,\n JSON.stringify(filter)\n );\n }\n\n /**\n * Resolves the TagFilter to a set of eligible card IDs.\n *\n * - Cards in `include` tags are OR'd together (card needs at least one)\n * - Cards in `exclude` tags are removed from the result\n */\n private async resolveFilteredCardIds(): Promise<Set<string>> {\n // Return cached result if available\n if (this.resolvedCardIds !== null) {\n return this.resolvedCardIds;\n }\n\n const includedCardIds = new Set<string>();\n\n // Build inclusion set (OR of all include tags)\n if (this.filter.include.length > 0) {\n for (const tagName of this.filter.include) {\n try {\n const tagDoc = await getTag(this.courseId, tagName);\n tagDoc.taggedCards.forEach((cardId) => includedCardIds.add(cardId));\n } catch (error) {\n logger.warn(\n `[TagFilteredContentSource] Could not resolve tag \"${tagName}\" for inclusion:`,\n error\n );\n }\n }\n }\n\n // If no include tags specified or none resolved, return empty set\n // (requiring explicit inclusion prevents \"study everything\" on empty filter)\n if (includedCardIds.size === 0 && this.filter.include.length > 0) {\n logger.warn(\n `[TagFilteredContentSource] No cards found for include tags: ${this.filter.include.join(', ')}`\n );\n this.resolvedCardIds = new Set();\n return this.resolvedCardIds;\n }\n\n // Build exclusion set\n const excludedCardIds = new Set<string>();\n if (this.filter.exclude.length > 0) {\n for (const tagName of this.filter.exclude) {\n try {\n const tagDoc = await getTag(this.courseId, tagName);\n tagDoc.taggedCards.forEach((cardId) => excludedCardIds.add(cardId));\n } catch (error) {\n logger.warn(\n `[TagFilteredContentSource] Could not resolve tag \"${tagName}\" for exclusion:`,\n error\n );\n }\n }\n }\n\n // Apply exclusion filter\n const finalCardIds = new Set<string>();\n for (const cardId of includedCardIds) {\n if (!excludedCardIds.has(cardId)) {\n finalCardIds.add(cardId);\n }\n }\n\n logger.info(\n `[TagFilteredContentSource] Resolved ${finalCardIds.size} cards ` +\n `(included: ${includedCardIds.size}, excluded: ${excludedCardIds.size})`\n );\n\n this.resolvedCardIds = finalCardIds;\n return finalCardIds;\n }\n\n /**\n * Get cards with suitability scores for presentation.\n *\n * Filters cards by tag inclusion/exclusion and assigns score=1.0 to all.\n * TagFilteredContentSource does not currently support pluggable navigation\n * strategies - it returns flat-scored candidates.\n *\n * @param limit - Maximum number of cards to return\n * @returns Cards sorted by score descending (all scores = 1.0)\n */\n public async getWeightedCards(limit: number): Promise<WeightedCard[]> {\n if (!hasActiveFilter(this.filter)) {\n logger.warn('[TagFilteredContentSource] getWeightedCards called with no active filter');\n return [];\n }\n\n const eligibleCardIds = await this.resolveFilteredCardIds();\n\n // Get new cards: eligible cards that are not already active\n const activeCards = await this.user.getActiveCards();\n const activeCardIds = new Set(activeCards.map((c) => c.cardID));\n\n const newCardWeighted: WeightedCard[] = [];\n for (const cardId of eligibleCardIds) {\n if (!activeCardIds.has(cardId)) {\n newCardWeighted.push({\n cardId,\n courseId: this.courseId,\n score: 1.0,\n provenance: [\n {\n strategy: 'tagFilter',\n strategyName: 'Tag Filter',\n strategyId: 'TAG_FILTER',\n action: 'generated' as const,\n score: 1.0,\n reason: `Tag-filtered new card (tags: ${this.filter.include.join(', ')})`,\n },\n ],\n });\n }\n\n if (newCardWeighted.length >= limit) {\n break;\n }\n }\n\n logger.info(\n `[TagFilteredContentSource] Found ${newCardWeighted.length} new cards matching filter`\n );\n\n // Get pending reviews: reviews for cards in the eligible set\n const allReviews = await this.user.getPendingReviews(this.courseId);\n const filteredReviews = allReviews.filter((review) => eligibleCardIds.has(review.cardId));\n\n logger.info(\n `[TagFilteredContentSource] Found ${filteredReviews.length} pending reviews matching filter ` +\n `(of ${allReviews.length} total)`\n );\n\n const reviewWeighted: WeightedCard[] = filteredReviews.map((r) => ({\n cardId: r.cardId,\n courseId: r.courseId,\n score: 1.0,\n reviewID: r._id,\n provenance: [\n {\n strategy: 'tagFilter',\n strategyName: 'Tag Filter',\n strategyId: 'TAG_FILTER',\n action: 'generated' as const,\n score: 1.0,\n reason: `Tag-filtered review (tags: ${this.filter.include.join(', ')})`,\n },\n ],\n }));\n\n // Reviews first, then new cards; respect limit\n return [...reviewWeighted, ...newCardWeighted].slice(0, limit);\n }\n\n /**\n * Clears the cached resolved card IDs.\n * Call this if the underlying tag data may have changed during a session.\n */\n public clearCache(): void {\n this.resolvedCardIds = null;\n }\n\n /**\n * Returns the course ID this source is filtering.\n */\n public getCourseId(): string {\n return this.courseId;\n }\n\n /**\n * Returns the active tag filter.\n */\n public getFilter(): TagFilter {\n return this.filter;\n }\n}\n","import { getDataLayer } from '@db/factory';\nimport { UserDBInterface } from '..';\nimport { StudentClassroomDB } from '../../impl/couch/classroomDB';\nimport { WeightedCard } from '../navigators';\nimport { TagFilter, hasActiveFilter } from '@vue-skuilder/common';\nimport { TagFilteredContentSource } from '../../study/TagFilteredContentSource';\n\nexport type StudySessionFailedItem = StudySessionFailedNewItem | StudySessionFailedReviewItem;\n\nexport interface StudySessionFailedNewItem extends StudySessionItem {\n status: 'failed-new';\n}\nexport interface StudySessionFailedReviewItem extends StudySessionReviewItem {\n status: 'failed-review';\n}\n\nexport interface StudySessionNewItem extends StudySessionItem {\n status: 'new';\n}\n\nexport interface StudySessionReviewItem extends StudySessionItem {\n reviewID: string;\n status: 'review' | 'failed-review';\n}\nexport function isReview(item: StudySessionItem): item is StudySessionReviewItem {\n const ret = item.status === 'review' || item.status === 'failed-review' || 'reviewID' in item;\n\n // console.log(`itemIsReview: ${ret}\n // \\t${JSON.stringify(item)}`);\n\n return ret;\n}\n\nexport interface StudySessionItem {\n status: 'new' | 'review' | 'failed-new' | 'failed-review';\n contentSourceType: 'course' | 'classroom';\n contentSourceID: string;\n // qualifiedID: `${string}-${string}` | `${string}-${string}-${number}`;\n cardID: string;\n courseID: string;\n elo?: number;\n // reviewID?: string;\n}\n\nexport interface ContentSourceID {\n type: 'course' | 'classroom';\n id: string;\n /**\n * Optional tag filter for scoped study sessions.\n * When present, creates a TagFilteredContentSource instead of a regular course source.\n */\n tagFilter?: TagFilter;\n}\n\n// #region docs_StudyContentSource\n/**\n * Interface for sources that provide study content to SessionController.\n *\n * Content sources return scored candidates via getWeightedCards(), which\n * SessionController uses to populate study queues.\n *\n * See: packages/db/docs/navigators-architecture.md\n */\nexport interface StudyContentSource {\n /**\n * Get cards with suitability scores for presentation.\n *\n * Returns unified scored candidates that can be sorted and selected by SessionController.\n * The card origin ('new' | 'review' | 'failed') is determined by provenance metadata.\n *\n * @param limit - Maximum number of cards to return\n * @returns Cards sorted by score descending\n */\n getWeightedCards(limit: number): Promise<WeightedCard[]>;\n}\n// #endregion docs_StudyContentSource\n\nexport async function getStudySource(\n source: ContentSourceID,\n user: UserDBInterface\n): Promise<StudyContentSource> {\n if (source.type === 'classroom') {\n return await StudentClassroomDB.factory(source.id, user);\n } else {\n // Check if this is a tag-filtered course source\n if (hasActiveFilter(source.tagFilter)) {\n return new TagFilteredContentSource(source.id, source.tagFilter!, user);\n }\n\n // Regular course source\n return getDataLayer().getCourseDB(source.id) as unknown as StudyContentSource;\n }\n}\n","import { CourseConfig, CourseElo, DataShape, SkuilderCourseData } from '@vue-skuilder/common';\nimport { StudyContentSource, StudySessionItem } from './contentSource';\nimport { TagStub, Tag, QualifiedCardID } from '../types/types-legacy';\nimport { DataLayerResult } from '../types/db';\nimport { NavigationStrategyManager } from './navigationStrategyManager';\n\n/**\n * Course content and management\n */\nexport interface CoursesDBInterface {\n /**\n * Get course config\n */\n getCourseConfig(courseId: string): Promise<CourseConfig>;\n\n /**\n * Get a list of all courses\n */\n getCourseList(): Promise<CourseConfig[]>;\n\n disambiguateCourse(courseId: string, disambiguator: string): Promise<void>;\n}\n\nexport interface CourseInfo {\n cardCount: number;\n registeredUsers: number;\n}\n\nexport interface CourseDBInterface extends NavigationStrategyManager, StudyContentSource {\n /**\n * Get course config\n */\n getCourseConfig(): Promise<CourseConfig>;\n\n getCourseID(): string;\n\n /**\n * Set course config\n */\n updateCourseConfig(cfg: CourseConfig): Promise<PouchDB.Core.Response>;\n\n getCourseInfo(): Promise<CourseInfo>;\n\n getCourseDoc<T extends SkuilderCourseData>(\n id: string,\n options?: PouchDB.Core.GetOptions\n ): Promise<T>;\n getCourseDocs<T extends SkuilderCourseData>(\n ids: string[],\n options?: PouchDB.Core.AllDocsOptions\n ): Promise<PouchDB.Core.AllDocsWithKeysResponse<{} & T>>;\n\n /**\n * Get cards sorted by ELO rating\n */\n getCardsByELO(\n elo: number,\n limit?: number\n ): Promise<\n {\n courseID: string;\n cardID: string;\n elo?: number;\n }[]\n >;\n\n /**\n * Get ELO data for specific cards\n */\n getCardEloData(cardIds: string[]): Promise<CourseElo[]>;\n\n /**\n * Update card ELO rating\n */\n updateCardElo(cardId: string, elo: CourseElo): Promise<PouchDB.Core.Response>;\n\n /**\n * Get cards centered at a particular ELO rating\n */\n getCardsCenteredAtELO(\n options: { limit: number; elo: 'user' | 'random' | number },\n filter?: (card: QualifiedCardID) => boolean\n ): Promise<StudySessionItem[]>;\n\n /**\n * Get tags for a card\n */\n getAppliedTags(cardId: string): Promise<PouchDB.Query.Response<TagStub>>;\n\n /**\n * Get tags for multiple cards in a single batch query.\n * More efficient than calling getAppliedTags() for each card.\n *\n * This method reduces redundant database operations when multiple filters\n * need tag data for the same cards. The Pipeline uses this to pre-hydrate\n * tags on WeightedCard objects before filters run.\n *\n * @param cardIds - Array of card IDs to fetch tags for\n * @returns Map from cardId to array of tag names\n */\n getAppliedTagsBatch(cardIds: string[]): Promise<Map<string, string[]>>;\n\n /**\n * Add a tag to a card\n */\n addTagToCard(cardId: string, tagId: string, updateELO?: boolean): Promise<PouchDB.Core.Response>;\n\n /**\n * Remove a tag from a card\n */\n removeTagFromCard(cardId: string, tagId: string): Promise<PouchDB.Core.Response>;\n\n /**\n * Create a new tag\n */\n createTag(tagName: string, author: string): Promise<PouchDB.Core.Response>;\n\n /**\n * Get a tag by name\n */\n getTag(tagName: string): Promise<Tag>;\n\n /**\n * Update a tag\n */\n updateTag(tag: Tag): Promise<PouchDB.Core.Response>;\n\n /**\n * Get all tag stubs for a course\n */\n getCourseTagStubs(): Promise<PouchDB.Core.AllDocsResponse<Tag>>;\n\n /**\n * Add a note to the course\n */\n addNote(\n codeCourse: string,\n shape: DataShape,\n data: unknown,\n author: string,\n tags: string[],\n uploads?: { [key: string]: PouchDB.Core.FullAttachment },\n elo?: CourseElo\n ): Promise<DataLayerResult>;\n\n removeCard(cardId: string): Promise<PouchDB.Core.Response>;\n\n getInexperiencedCards(): Promise<\n {\n courseId: string;\n cardId: string;\n count: number;\n elo: CourseElo;\n }[]\n >;\n\n /**\n * Search for cards by text content\n * @param query Text to search for\n * @returns Array of matching card data\n */\n searchCards(query: string): Promise<any[]>;\n\n /**\n * Find documents using PouchDB query syntax\n * @param request PouchDB find request\n * @returns Query response\n */\n find(request: PouchDB.Find.FindRequest<any>): Promise<PouchDB.Find.FindResponse<any>>;\n}\n","// db/src/core/interfaces.ts\n\nimport { UserDBInterface, UserDBReader } from './userDB';\nimport { CourseDBInterface, CoursesDBInterface } from './courseDB';\nimport { ClassroomDBInterface } from './classroomDB';\nimport { AdminDBInterface } from './adminDB';\n\n/**\n * Main factory interface for data access\n */\nexport interface DataLayerProvider {\n /**\n * Get the user database interface\n */\n getUserDB(): UserDBInterface;\n\n /**\n * Create a UserDBReader for a specific user (admin access required)\n * Uses session authentication to verify requesting user is admin\n * @param targetUsername - The username to create a reader for\n * @throws Error if requesting user is not 'admin'\n */\n createUserReaderForUser(targetUsername: string): Promise<UserDBReader>;\n\n /**\n * Get a course database interface\n */\n getCourseDB(courseId: string): CourseDBInterface;\n\n /**\n * Get the courses-lookup interface\n */\n getCoursesDB(): CoursesDBInterface;\n\n /**\n * Get a classroom database interface\n */\n getClassroomDB(classId: string, type: 'student' | 'teacher'): Promise<ClassroomDBInterface>;\n\n /**\n * Get the admin database interface\n */\n getAdminDB(): AdminDBInterface;\n\n /**\n * Initialize the data layer\n */\n initialize(): Promise<void>;\n\n /**\n * Teardown the data layer\n */\n teardown(): Promise<void>;\n\n /**\n * Check if this data layer is read-only\n */\n isReadOnly(): boolean;\n}\n","import {\n ActivityRecord,\n CourseRegistration,\n CourseRegistrationDoc,\n ScheduledCard,\n} from '@db/core/types/user';\nimport { CourseElo, Status } from '@vue-skuilder/common';\nimport { Moment } from 'moment';\nimport { CardHistory, CardRecord, QualifiedCardID } from '../types/types-legacy';\nimport { UserConfig } from '../types/user';\nimport { DocumentUpdater } from '@db/study';\n\n/**\n * Read-only user data operations\n */\nexport interface UserDBReader {\n get<T>(id: string): Promise<T & PouchDB.Core.RevisionIdMeta>;\n getUsername(): string;\n isLoggedIn(): boolean;\n\n /**\n * Get user configuration\n */\n getConfig(): Promise<UserConfig>;\n\n /**\n * Get cards that the user has seen\n */\n getSeenCards(courseId?: string): Promise<string[]>;\n\n /**\n * Get cards that are actively scheduled for review\n */\n getActiveCards(): Promise<QualifiedCardID[]>;\n\n /**\n * Get user's course registrations\n */\n getCourseRegistrationsDoc(): Promise<CourseRegistrationDoc>;\n\n /**\n * Get the registration doc for a specific course.\n * @param courseId\n */\n getCourseRegDoc(courseId: string): Promise<CourseRegistration>;\n\n /**\n * Get user's active courses\n */\n getActiveCourses(): Promise<CourseRegistration[]>;\n\n /**\n * Get user's pending reviews\n */\n getPendingReviews(courseId?: string): Promise<ScheduledCard[]>;\n\n getActivityRecords(): Promise<ActivityRecord[]>;\n\n /**\n * Get strategy-specific state for a course.\n *\n * Strategies use this to persist preferences, learned patterns, or temporal\n * tracking data across sessions. Each strategy owns its own namespace.\n *\n * @param courseId - The course this state applies to\n * @param strategyKey - Unique key identifying the strategy (typically class name)\n * @returns The strategy's data payload, or null if no state exists\n */\n getStrategyState<T>(courseId: string, strategyKey: string): Promise<T | null>;\n\n /**\n * Get user's classroom registrations\n */\n getUserClassrooms(): Promise<ClassroomRegistrationDoc>;\n\n /**\n * Get user's active classes\n */\n getActiveClasses(): Promise<string[]>;\n\n getCourseInterface(courseId: string): Promise<UsrCrsDataInterface>;\n}\n\n/**\n * User data mutation operations\n */\nexport interface UserDBWriter extends DocumentUpdater {\n /**\n * Update user configuration\n */\n setConfig(config: Partial<UserConfig>): Promise<void>;\n\n /**\n * Record a user's interaction with a card\n */\n putCardRecord<T extends CardRecord>(record: T): Promise<CardHistory<CardRecord>>;\n\n /**\n * Register user for a course\n */\n registerForCourse(courseId: string, previewMode?: boolean): Promise<PouchDB.Core.Response>;\n\n /**\n * Drop a course registration\n */\n dropCourse(courseId: string, dropStatus?: string): Promise<PouchDB.Core.Response>;\n\n /**\n * Schedule a card for review\n */\n scheduleCardReview(review: {\n user: string;\n course_id: string;\n card_id: string;\n time: Moment;\n scheduledFor: 'course' | 'classroom';\n schedulingAgentId: string;\n }): Promise<void>;\n\n /**\n * Remove a scheduled card review\n */\n removeScheduledCardReview(reviewId: string): Promise<void>;\n\n /**\n * Register user for a classroom\n */\n registerForClassroom(\n classId: string,\n registerAs: 'student' | 'teacher' | 'aide' | 'admin'\n ): Promise<PouchDB.Core.Response>;\n\n /**\n * Drop user from classroom\n */\n dropFromClassroom(classId: string): Promise<PouchDB.Core.Response>;\n\n /**\n * Update user's ELO rating for a course\n */\n updateUserElo(courseId: string, elo: CourseElo): Promise<PouchDB.Core.Response>;\n\n /**\n * Reset all user data (progress, registrations, etc.) while preserving authentication\n */\n resetUserData(): Promise<{ status: Status; error?: string }>;\n\n /**\n * Store strategy-specific state for a course.\n *\n * Strategies use this to persist preferences, learned patterns, or temporal\n * tracking data across sessions. Each strategy owns its own namespace.\n *\n * @param courseId - The course this state applies to\n * @param strategyKey - Unique key identifying the strategy (typically class name)\n * @param data - The strategy's data payload to store\n */\n putStrategyState<T>(courseId: string, strategyKey: string, data: T): Promise<void>;\n\n /**\n * Delete strategy-specific state for a course.\n *\n * @param courseId - The course this state applies to\n * @param strategyKey - Unique key identifying the strategy (typically class name)\n */\n deleteStrategyState(courseId: string, strategyKey: string): Promise<void>;\n}\n\n/**\n * Authentication and account management operations\n */\nexport interface UserDBAuthenticator {\n /**\n * Create a new user account\n */\n createAccount(\n username: string,\n password: string\n ): Promise<{\n status: Status;\n error: string;\n }>;\n\n /**\n * Log in as a user\n */\n login(\n username: string,\n password: string\n ): Promise<{\n ok: boolean;\n name?: string;\n roles?: string[];\n }>;\n\n /**\n * Log out the current user\n */\n logout(): Promise<{\n ok: boolean;\n }>;\n}\n\n/**\n * Complete user database interface - combines all user operations\n * This maintains backward compatibility with existing code\n */\nexport interface UserDBInterface extends UserDBReader, UserDBWriter, UserDBAuthenticator {}\n\nexport interface UserCourseSettings {\n [setting: string]: string | number | boolean;\n}\n\nexport interface UserCourseSetting {\n key: string;\n value: string | number | boolean;\n}\n\n// [ ] reconsider here. Should maybe be generic type based on <T extends StudyContentSource> ?\nexport interface UsrCrsDataInterface {\n getScheduledReviewCount(): Promise<number>;\n getCourseSettings(): Promise<UserCourseSettings>;\n updateCourseSettings(updates: UserCourseSetting[]): void; // [ ] return a result of some sort?\n // getRegistrationDoc(): Promise<CourseRegistration>;\n}\n\nexport type ClassroomRegistrationDesignation = 'student' | 'teacher' | 'aide' | 'admin';\n\nexport interface ClassroomRegistration {\n classID: string;\n registeredAs: ClassroomRegistrationDesignation;\n}\n\nexport interface ClassroomRegistrationDoc {\n registrations: ClassroomRegistration[];\n}\n","export * from './adminDB';\nexport * from './classroomDB';\nexport * from './contentSource';\nexport * from './courseDB';\nexport * from './dataLayerProvider';\n\nexport * from './userDB';\n","import { CourseElo } from '@vue-skuilder/common';\nimport { Moment } from 'moment';\n\nexport interface SessionTrackingData {\n peekSessionCount: number;\n studySessionCount: number;\n sessionCount: number; // total\n firstSessionDate: string;\n lastSessionDate: string;\n signupPrompted: boolean;\n promptDismissalCount: number;\n studyModeAcknowledged: boolean;\n}\n\nexport interface UserConfig {\n darkMode: boolean;\n likesConfetti: boolean;\n sessionTimeLimit: number; // Session time limit in minutes\n email?: string; // Optional email for verification flows (added for enhanced auth)\n\n // Session tracking for trial enforcement (per-course)\n // Key is courseId (e.g., 'letterspractice-basic')\n sessionTracking?: Record<string, SessionTrackingData>;\n}\n\nexport interface ActivityRecord {\n timeStamp: number | string;\n [key: string]: any;\n}\n\nexport interface CourseRegistration {\n status?: 'active' | 'dropped' | 'maintenance-mode' | 'preview';\n courseID: string;\n admin: boolean;\n moderator: boolean;\n user: boolean;\n settings?: {\n [setting: string]: string | number | boolean;\n };\n elo: number | CourseElo;\n}\n\ninterface StudyWeights {\n [courseID: string]: number;\n}\n\nexport interface CourseRegistrationDoc {\n courses: CourseRegistration[];\n studyWeight: StudyWeights;\n}\n\nexport interface ScheduledCard {\n _id: PouchDB.Core.DocumentId;\n\n /**\n * The docID of the card to be reviewed\n */\n cardId: PouchDB.Core.DocumentId;\n /**\n * The ID of the course\n */\n courseId: string;\n /**\n * The time at which the card becomes eligible for review.\n *\n * (Should probably be UTC adjusted so that performance is\n * not wonky across time zones)\n * \n * Note: Stored as ISO string for PouchDB serialization compatibility,\n * but can be consumed as Moment objects via moment.utc(reviewTime)\n */\n reviewTime: string | Moment;\n\n /**\n * The time at which this scheduled event was created.\n * \n * Note: Stored as ISO string for PouchDB serialization compatibility,\n * but can be consumed as Moment objects via moment.utc(scheduledAt)\n */\n scheduledAt: string | Moment;\n\n /**\n * Classifying whether this card is scheduled on behalf of a\n * user-registered course or by as assigned content from a\n * user-registered classroom\n */\n scheduledFor: 'course' | 'classroom';\n\n /**\n * The ID of the course or classroom that requested this card\n */\n schedulingAgentId: string;\n}\n","import { DocType, DocTypePrefixes } from './types-legacy';\n\n/**\n * Template literal type for strategy state document IDs.\n *\n * Format: `STRATEGY_STATE-{courseId}-{strategyKey}`\n */\nexport type StrategyStateId =\n `${(typeof DocTypePrefixes)[DocType.STRATEGY_STATE]}::${string}::${string}`;\n\n/**\n * Document storing strategy-specific state in the user database.\n *\n * Each strategy can persist its own state (user preferences, learned patterns,\n * temporal tracking, etc.) using this document type. The state is scoped to\n * a (user, course, strategy) tuple.\n *\n * ## Use Cases\n *\n * 1. **Explicit user preferences**: User configures tag filters, difficulty\n * preferences, or learning goals. UI writes to strategy state.\n *\n * 2. **Learned/temporal state**: Strategy tracks patterns over time, e.g.,\n * \"when did I last introduce confusable concepts together?\"\n *\n * 3. **Adaptive personalization**: Strategy infers user preferences from\n * behavior and stores them for future sessions.\n *\n * ## Storage Location\n *\n * These documents live in the **user database**, not the course database.\n * They sync with the user's data across devices.\n *\n * ## Document ID Format\n *\n * `STRATEGY_STATE::{courseId}::{strategyKey}`\n *\n * Example: `STRATEGY_STATE::piano-basics::UserTagPreferenceFilter`\n *\n * @template T - The shape of the strategy-specific data payload\n */\nexport interface StrategyStateDoc<T = unknown> {\n _id: StrategyStateId;\n _rev?: string;\n docType: DocType.STRATEGY_STATE;\n\n /**\n * The course this state applies to.\n */\n courseId: string;\n\n /**\n * Unique key identifying the strategy instance.\n * Typically the strategy class name (e.g., \"UserTagPreferenceFilter\",\n * \"InterferenceMitigatorNavigator\").\n *\n * If a course has multiple instances of the same strategy type with\n * different configurations, use a more specific key.\n */\n strategyKey: string;\n\n /**\n * Strategy-specific data payload.\n * Each strategy defines its own schema for this field.\n */\n data: T;\n\n /**\n * ISO timestamp of last update.\n * Use `moment.utc(updatedAt)` to parse into a Moment object.\n */\n updatedAt: string;\n}\n\n/**\n * Build the document ID for a strategy state document.\n *\n * @param courseId - The course ID\n * @param strategyKey - The strategy key (typically class name)\n * @returns The document ID in format `STRATEGY_STATE::{courseId}::{strategyKey}`\n */\nexport function buildStrategyStateId(courseId: string, strategyKey: string): StrategyStateId {\n return `STRATEGY_STATE::${courseId}::${strategyKey}`;\n}\n","import { CourseElo, Status, ParsedCard, BulkImportCardData } from '@vue-skuilder/common';\nimport { CourseDBInterface } from '../../core/interfaces/courseDB';\nimport { ImportResult, BulkCardProcessorConfig } from './types';\nimport { logger } from '../../util/logger';\n\n/**\n * Processes multiple cards from bulk text input\n *\n * @param parsedCards - Array of parsed cards to import\n * @param courseDB - Course database interface\n * @param config - Configuration for the card processor\n * @returns Array of import results\n */\nexport async function importParsedCards(\n parsedCards: ParsedCard[],\n courseDB: CourseDBInterface,\n config: BulkCardProcessorConfig\n): Promise<ImportResult[]> {\n const results: ImportResult[] = [];\n\n for (const parsedCard of parsedCards) {\n try {\n // processCard takes a ParsedCard and returns an ImportResult\n const result = await processCard(parsedCard, courseDB, config);\n results.push(result);\n } catch (error) {\n logger.error('Error processing card:', error);\n // Reconstruct originalText from parsedCard for this specific catch block\n // This is a fallback if processCard itself throws an unhandled error.\n // Normally, processCard should return an ImportResult with status 'error'.\n let errorOriginalText = parsedCard.markdown;\n if (parsedCard.tags && parsedCard.tags.length > 0) {\n errorOriginalText += `\\ntags: ${parsedCard.tags.join(', ')}`;\n }\n if (parsedCard.elo !== undefined) {\n errorOriginalText += `\\nelo: ${parsedCard.elo}`;\n }\n results.push({\n originalText: errorOriginalText,\n status: 'error',\n message: `Error processing card: ${\n error instanceof Error ? error.message : 'Unknown error'\n }`,\n });\n }\n }\n\n return results;\n}\n\n/**\n * Processes a single parsed card\n *\n * @param parsedCard - Parsed card data\n * @param courseDB - Course database interface\n * @param config - Configuration for the card processor\n * @returns Import result for the card\n */\nasync function processCard(\n parsedCard: ParsedCard,\n courseDB: CourseDBInterface,\n config: BulkCardProcessorConfig\n): Promise<ImportResult> {\n const { markdown, tags, elo } = parsedCard;\n\n // Build the original text representation including metadata\n let originalText = markdown;\n if (tags.length > 0) {\n originalText += `\\ntags: ${tags.join(', ')}`;\n }\n if (elo !== undefined) {\n originalText += `\\nelo: ${elo}`;\n }\n\n // Create card data object\n const cardData: BulkImportCardData = {\n Input: markdown,\n Uploads: [], // No uploads for bulk import\n };\n\n const tagsElo: CourseElo['tags'] = {};\n for (const tag of tags) {\n tagsElo[tag] = {\n score: elo || 0,\n count: 1,\n };\n }\n\n try {\n const result = await courseDB.addNote(\n config.courseCode,\n config.dataShape,\n cardData,\n config.userName,\n tags,\n undefined, // attachments\n elo\n ? {\n global: {\n score: elo,\n count: 1,\n },\n tags: tagsElo,\n misc: {},\n }\n : undefined\n );\n\n if (result.status === Status.ok) {\n return {\n originalText,\n status: 'success',\n message: 'Card added successfully.',\n cardId: result.id ? result.id : '(unknown)',\n };\n } else {\n return {\n originalText,\n status: 'error',\n message: result.message || 'Failed to add card to database. Unknown error.',\n };\n }\n } catch (error) {\n logger.error('Error adding note:', error);\n return {\n originalText,\n status: 'error',\n message: `Error adding card: ${error instanceof Error ? error.message : 'Unknown error'}`,\n };\n }\n}\n\n/**\n * Validates the configuration for bulk card processing\n *\n * @param config - Configuration to validate\n * @returns Object with validation result and error message if any\n */\nexport function validateProcessorConfig(config: Partial<BulkCardProcessorConfig>): {\n isValid: boolean;\n errorMessage?: string;\n} {\n if (!config.dataShape) {\n return {\n isValid: false,\n errorMessage: 'No data shape provided for card processing',\n };\n }\n\n if (!config.courseCode) {\n return {\n isValid: false,\n errorMessage: 'No course code provided for card processing',\n };\n }\n\n if (!config.userName) {\n return {\n isValid: false,\n errorMessage: 'No user name provided for card processing',\n };\n }\n\n return { isValid: true };\n}\n","import { DataShape } from '@vue-skuilder/common';\n\n/**\n * Interface representing the result of a bulk import operation for a single card\n */\nexport interface ImportResult {\n /** The original text input for the card */\n originalText: string;\n /** Status of the import operation */\n status: 'success' | 'error';\n /** Message describing the result or error */\n message: string;\n /** ID of the newly created card (only for success) */\n cardId?: string;\n}\n\n/**\n * Configuration for the bulk card processor\n */\nexport interface BulkCardProcessorConfig {\n /** The data shape to use for the cards */\n dataShape: DataShape;\n /** The course code used for adding notes */\n courseCode: string;\n /** The username of the current user */\n userName: string;\n}\n","export * from './cardProcessor.js';\nexport * from './types.js';","// Export all core interfaces and types\n\nexport * from './interfaces';\nexport * from './types/types-legacy';\nexport * from './types/user';\nexport * from './types/strategyState';\nexport * from '../util/Loggable';\nexport * from './util';\nexport * from './navigators';\nexport * from './bulkImport';\n","export * from './core';\n\nexport { default as CourseLookup } from './impl/couch/courseLookupDB';\n\nexport * from './study';\n\nexport * from './util';\nexport * from './factory';\n\n// Export CouchDB user types for use in Express backend\nexport type {\n UserAccountStatus,\n Entitlement,\n UserEntitlements,\n CouchDbUserDoc,\n} from './impl/couch/users.types';\n","import moment from 'moment';\nimport { CardHistory, CardRecord, UserDBInterface } from '@db/core';\nimport { isReview, StudySessionItem } from '@db/impl/couch';\nimport { newInterval } from '../SpacedRepetition';\nimport { logger } from '@db/util/logger';\n\n/**\n * Service responsible for Spaced Repetition System (SRS) scheduling logic.\n */\nexport class SrsService {\n private user: UserDBInterface;\n\n constructor(user: UserDBInterface) {\n this.user = user;\n }\n\n /**\n * Calculates the next review time for a card based on its history and\n * schedules it in the user's database.\n * @param history The full history of the card.\n * @param item The study session item, used to determine if a previous review needs to be cleared.\n */\n public async scheduleReview(\n history: CardHistory<CardRecord>,\n item: StudySessionItem\n ): Promise<void> {\n const nextInterval = newInterval(this.user, history);\n const nextReviewTime = moment.utc().add(nextInterval, 'seconds');\n\n if (isReview(item)) {\n logger.info(`[SrsService] Removing previously scheduled review for: ${item.cardID}`);\n void this.user.removeScheduledCardReview(item.reviewID);\n }\n\n void this.user.scheduleCardReview({\n user: this.user.getUsername(),\n course_id: history.courseID,\n card_id: history.cardID,\n time: nextReviewTime,\n scheduledFor: item.contentSourceType,\n schedulingAgentId: item.contentSourceID,\n });\n }\n}\n","import { CardHistory, CardRecord, QuestionRecord } from '@db/core/types/types-legacy';\nimport { areQuestionRecords } from '@db/core/util';\nimport { Update } from '@db/impl/couch/updateQueue';\nimport moment from 'moment';\nimport { logger } from '../util/logger';\n\ntype Moment = moment.Moment;\nconst duration = moment.duration;\n\nexport interface DocumentUpdater {\n update<T extends PouchDB.Core.Document<object>>(id: string, update: Update<T>): Promise<T>;\n}\n\n/**\n * Returns the minimum number of seconds that should pass before a\n * card is redisplayed for review / practice.\n *\n * @param cardHistory The user's history working with the given card\n */\nexport function newInterval(user: DocumentUpdater, cardHistory: CardHistory<CardRecord>): number {\n if (areQuestionRecords(cardHistory)) {\n return newQuestionInterval(user, cardHistory);\n } else {\n return 100000; // random - replace\n }\n}\n\nfunction newQuestionInterval(user: DocumentUpdater, cardHistory: CardHistory<QuestionRecord>) {\n const records = cardHistory.records;\n const currentAttempt = records[records.length - 1];\n const lastInterval: number = lastSuccessfulInterval(records);\n\n if (lastInterval > cardHistory.bestInterval) {\n cardHistory.bestInterval = lastInterval;\n // update bestInterval on cardHistory in db\n void user.update<CardHistory<QuestionRecord>>(cardHistory._id, {\n bestInterval: lastInterval,\n });\n }\n\n if (currentAttempt.isCorrect) {\n const skill = Math.min(1.0, Math.max(0.0, currentAttempt.performance as number));\n logger.debug(`Demontrated skill: \\t${skill}`);\n const interval: number = lastInterval * (0.75 + skill);\n cardHistory.lapses = getLapses(cardHistory.records);\n cardHistory.streak = getStreak(cardHistory.records);\n\n if (\n cardHistory.lapses &&\n cardHistory.streak &&\n cardHistory.bestInterval &&\n (cardHistory.lapses >= 0 || cardHistory.streak >= 0)\n ) {\n // weighted average of best-ever performance vs current performance, based\n // on how often the card has been failed, and the current streak of success\n const ret =\n (cardHistory.lapses * interval + cardHistory.streak * cardHistory.bestInterval) /\n (cardHistory.lapses + cardHistory.streak);\n logger.debug(`Weighted average interval calculation:\n\\t(${cardHistory.lapses} * ${interval} + ${cardHistory.streak} * ${cardHistory.bestInterval}) / (${cardHistory.lapses} + ${cardHistory.streak}) = ${ret}`);\n return ret;\n } else {\n return interval;\n }\n } else {\n return 0;\n }\n}\n\n/**\n * Returns the amount of time, in seconds, of the most recent successful\n * interval for this card. An interval is successful if the user answers\n * a question correctly on the first attempt.\n *\n * @param cardHistory The record of user attempts with the question\n */\nfunction lastSuccessfulInterval(cardHistory: QuestionRecord[]): number {\n for (let i = cardHistory.length - 1; i >= 1; i--) {\n if (cardHistory[i].priorAttemps === 0 && cardHistory[i].isCorrect) {\n const lastInterval = secondsBetween(cardHistory[i - 1].timeStamp, cardHistory[i].timeStamp);\n const ret = Math.max(lastInterval, 20 * 60 * 60);\n logger.debug(`Last interval w/ this card was: ${lastInterval}s, returning ${ret}s`);\n return ret;\n }\n }\n\n return getInitialInterval(cardHistory); // used as a magic number here - indicates no prior intervals\n}\n\nfunction getStreak(records: QuestionRecord[]): number {\n let streak = 0;\n let index = records.length - 1;\n\n while (index >= 0 && records[index].isCorrect) {\n index--;\n streak++;\n }\n\n return streak;\n}\nfunction getLapses(records: QuestionRecord[]): number {\n return records.filter((r) => r.isCorrect === false).length;\n}\n\nfunction getInitialInterval(cardHistory: QuestionRecord[]): number {\n logger.warn(`history of length: ${cardHistory.length} ignored!`);\n\n // todo make this a data-driven service, relying on:\n // - global experience w/ the card (ie, what interval\n // seems to be working well across the population)\n // - the individual user (how do they respond in general\n // when compared to the population)\n return 60 * 60 * 24 * 3; // 3 days\n}\n\n/**\n * Returns the time in seconds between two Moment objects\n * @param start The first time\n * @param end The second time\n */\nfunction secondsBetween(start: Moment, end: Moment): number {\n // assertion guard against mis-typed json from database\n start = moment(start);\n end = moment(end);\n const ret = duration(end.diff(start)).asSeconds();\n // console.log(`From start: ${start} to finish: ${end} is ${ret} seconds`);\n return ret;\n}\n","import { adjustCourseScores, toCourseElo } from '@vue-skuilder/common';\nimport { DataLayerProvider, UserDBInterface, CourseRegistrationDoc } from '@db/core';\nimport { StudySessionRecord } from '../SessionController';\nimport { logger } from '@db/util/logger';\n\n/**\n * Service responsible for ELO rating calculations and updates.\n */\nexport class EloService {\n private dataLayer: DataLayerProvider;\n private user: UserDBInterface;\n\n constructor(dataLayer: DataLayerProvider, user: UserDBInterface) {\n this.dataLayer = dataLayer;\n this.user = user;\n }\n\n /**\n * Updates both user and card ELO ratings based on user performance.\n * @param userScore Score between 0-1 representing user performance\n * @param course_id Course identifier\n * @param card_id Card identifier \n * @param userCourseRegDoc User's course registration document (will be mutated)\n * @param currentCard Current card session record\n * @param k Optional K-factor for ELO calculation\n */\n public async updateUserAndCardElo(\n userScore: number,\n course_id: string,\n card_id: string,\n userCourseRegDoc: CourseRegistrationDoc,\n currentCard: StudySessionRecord,\n k?: number\n ): Promise<void> {\n if (k) {\n logger.warn(`k value interpretation not currently implemented`);\n }\n const courseDB = this.dataLayer.getCourseDB(currentCard.card.course_id);\n const userElo = toCourseElo(userCourseRegDoc.courses.find((c) => c.courseID === course_id)!.elo);\n const cardElo = (await courseDB.getCardEloData([currentCard.card.card_id]))[0];\n\n if (cardElo && userElo) {\n const eloUpdate = adjustCourseScores(userElo, cardElo, userScore);\n userCourseRegDoc.courses.find((c) => c.courseID === course_id)!.elo = eloUpdate.userElo;\n\n const results = await Promise.allSettled([\n this.user.updateUserElo(course_id, eloUpdate.userElo),\n courseDB.updateCardElo(card_id, eloUpdate.cardElo),\n ]);\n\n // Check the results of each operation\n const userEloStatus = results[0].status === 'fulfilled';\n const cardEloStatus = results[1].status === 'fulfilled';\n\n if (userEloStatus && cardEloStatus) {\n const user = (results[0] as PromiseFulfilledResult<any>).value;\n const card = (results[1] as PromiseFulfilledResult<any>).value;\n\n if (user.ok && card && card.ok) {\n logger.info(\n `[EloService] Updated ELOS:\n \\tUser: ${JSON.stringify(eloUpdate.userElo)})\n \\tCard: ${JSON.stringify(eloUpdate.cardElo)})\n `\n );\n }\n } else {\n // Log which operations succeeded and which failed\n logger.warn(\n `[EloService] Partial ELO update:\n \\tUser ELO update: ${userEloStatus ? 'SUCCESS' : 'FAILED'}\n \\tCard ELO update: ${cardEloStatus ? 'SUCCESS' : 'FAILED'}`\n );\n\n if (!userEloStatus && results[0].status === 'rejected') {\n logger.error('[EloService] User ELO update error:', results[0].reason);\n }\n\n if (!cardEloStatus && results[1].status === 'rejected') {\n logger.error('[EloService] Card ELO update error:', results[1].reason);\n }\n }\n }\n }\n}","import {\n CardHistory,\n CardRecord,\n CourseRegistrationDoc,\n isQuestionRecord,\n QuestionRecord,\n StudySessionItem,\n} from '@db/core';\nimport { logger } from '@db/util/logger';\nimport { ResponseResult, StudySessionRecord } from '../SessionController';\nimport { EloService } from './EloService';\nimport { SrsService } from './SrsService';\n\n/**\n * Service responsible for orchestrating the complete response processing workflow.\n * Coordinates SRS scheduling and ELO updates for user card interactions.\n */\nexport class ResponseProcessor {\n private srsService: SrsService;\n private eloService: EloService;\n\n constructor(srsService: SrsService, eloService: EloService) {\n this.srsService = srsService;\n this.eloService = eloService;\n }\n\n /**\n * Processes a user's response to a card, handling SRS scheduling and ELO updates.\n * @param cardRecord User's response record\n * @param cardHistory Promise resolving to the card's history\n * @param studySessionItem Current study session item\n * @param courseRegistrationDoc User's course registration (for ELO updates)\n * @param currentCard Current study session record\n * @param courseId Course identifier\n * @param cardId Card identifier\n * @param maxAttemptsPerView Maximum attempts allowed per view\n * @param maxSessionViews Maximum session views for this card\n * @param sessionViews Current number of session views\n * @returns ResponseResult with navigation and UI instructions\n */\n public async processResponse(\n cardRecord: CardRecord,\n cardHistory: Promise<CardHistory<CardRecord>>,\n studySessionItem: StudySessionItem,\n courseRegistrationDoc: CourseRegistrationDoc,\n currentCard: StudySessionRecord,\n courseId: string,\n cardId: string,\n maxAttemptsPerView: number,\n maxSessionViews: number,\n sessionViews: number\n ): Promise<ResponseResult> {\n // Handle non-question records (simple dismiss)\n if (!isQuestionRecord(cardRecord)) {\n return {\n nextCardAction: 'dismiss-success',\n shouldLoadNextCard: true,\n isCorrect: true, // non-question records are considered \"correct\"\n shouldClearFeedbackShadow: true,\n };\n }\n\n // Debug logging for response processing\n // logger.debug('[ResponseProcessor] Processing response', {\n // cardId,\n // courseId,\n // isCorrect: cardRecord.isCorrect,\n // performance: cardRecord.performance,\n // priorAttempts: cardRecord.priorAttemps,\n // currentSessionViews: sessionViews,\n // maxSessionViews,\n // maxAttemptsPerView,\n // currentCardRecordsLength: currentCard.records.length,\n // studySessionSourceType: studySessionItem.contentSourceType,\n // studySessionSourceID: studySessionItem.contentSourceID,\n // studySessionItemId: studySessionItem.cardID,\n // studySessionItemType: studySessionItem.contentSourceType,\n\n // cardRecordTimestamp: cardRecord.timeStamp,\n // cardRecordResponseTime: cardRecord.timeSpent,\n // });\n\n try {\n const history = await cardHistory;\n\n // Debug logging for card history\n // logger.debug('[ResponseProcessor] History loaded:', {\n // cardId,\n // historyRecordsCount: history.records.length,\n // historyRecords: history.records.map((record) => ({\n // timeStamp: record.timeStamp,\n // isCorrect: 'isCorrect' in record ? record.isCorrect : 'N/A',\n // performance: 'performance' in record ? record.performance : 'N/A',\n // priorAttempts: 'priorAttemps' in record ? record.priorAttemps : 'N/A',\n // })),\n // firstInteraction: history.records.length === 1,\n // lastRecord: history.records[history.records.length - 1],\n // });\n\n // Handle correct responses\n if (cardRecord.isCorrect) {\n return this.processCorrectResponse(\n cardRecord,\n history,\n studySessionItem,\n courseRegistrationDoc,\n currentCard,\n courseId,\n cardId\n );\n } else {\n // Handle incorrect responses\n return this.processIncorrectResponse(\n cardRecord,\n history,\n courseRegistrationDoc,\n currentCard,\n courseId,\n cardId,\n maxAttemptsPerView,\n maxSessionViews,\n sessionViews\n );\n }\n } catch (e: unknown) {\n logger.error('[ResponseProcessor] Failed to load card history', { e, cardId });\n throw e;\n }\n }\n\n /**\n * Handles processing for correct responses: SRS scheduling and ELO updates.\n */\n private processCorrectResponse(\n cardRecord: QuestionRecord,\n history: CardHistory<CardRecord>,\n studySessionItem: StudySessionItem,\n courseRegistrationDoc: CourseRegistrationDoc,\n currentCard: StudySessionRecord,\n courseId: string,\n cardId: string\n ): ResponseResult {\n // Only schedule and update ELO for first-time attempts\n if (cardRecord.priorAttemps === 0) {\n // Schedule the card for future review based on performance (async, non-blocking)\n void this.srsService.scheduleReview(history, studySessionItem);\n\n // Update ELO ratings\n if (history.records.length === 1) {\n // First interaction with this card - standard ELO update (async, non-blocking)\n const userScore = 0.5 + (cardRecord.performance as number) / 2;\n void this.eloService.updateUserAndCardElo(\n userScore,\n courseId,\n cardId,\n courseRegistrationDoc,\n currentCard\n );\n } else {\n // Multiple interactions - reduce K-factor to limit ELO volatility (async, non-blocking)\n const k = Math.ceil(32 / history.records.length);\n const userScore = 0.5 + (cardRecord.performance as number) / 2;\n void this.eloService.updateUserAndCardElo(\n userScore,\n courseId,\n cardId,\n courseRegistrationDoc,\n currentCard,\n k\n );\n }\n\n logger.info(\n '[ResponseProcessor] Processed correct response with SRS scheduling and ELO update'\n );\n\n return {\n nextCardAction: 'dismiss-success',\n shouldLoadNextCard: true,\n isCorrect: true,\n performanceScore: cardRecord.performance as number,\n shouldClearFeedbackShadow: true,\n };\n } else {\n logger.info(\n '[ResponseProcessor] Processed correct response (retry attempt - no scheduling/ELO)'\n );\n\n return {\n nextCardAction: 'marked-failed',\n shouldLoadNextCard: true,\n isCorrect: true,\n performanceScore: cardRecord.performance as number,\n shouldClearFeedbackShadow: true,\n };\n }\n }\n\n /**\n * Handles processing for incorrect responses: ELO updates only.\n */\n private processIncorrectResponse(\n cardRecord: QuestionRecord,\n history: CardHistory<CardRecord>,\n courseRegistrationDoc: CourseRegistrationDoc,\n currentCard: StudySessionRecord,\n courseId: string,\n cardId: string,\n maxAttemptsPerView: number,\n maxSessionViews: number,\n sessionViews: number\n ): ResponseResult {\n // Update ELO for first-time failures (not subsequent attempts on same card) (async, non-blocking)\n if (history.records.length !== 1 && cardRecord.priorAttemps === 0) {\n void this.eloService.updateUserAndCardElo(\n 0, // Failed response = 0 score\n courseId,\n cardId,\n courseRegistrationDoc,\n currentCard\n );\n logger.info('[ResponseProcessor] Processed incorrect response with ELO update');\n } else {\n logger.info('[ResponseProcessor] Processed incorrect response (no ELO update needed)');\n }\n\n // Determine navigation based on attempt limits\n if (currentCard.records.length >= maxAttemptsPerView) {\n if (sessionViews >= maxSessionViews) {\n // Too many session views - dismiss completely with ELO penalty (async, non-blocking)\n void this.eloService.updateUserAndCardElo(\n 0,\n courseId,\n cardId,\n courseRegistrationDoc,\n currentCard\n );\n return {\n nextCardAction: 'dismiss-failed',\n shouldLoadNextCard: true,\n isCorrect: false,\n shouldClearFeedbackShadow: true,\n };\n } else {\n // Mark as failed for later retry\n return {\n nextCardAction: 'marked-failed',\n shouldLoadNextCard: true,\n isCorrect: false,\n shouldClearFeedbackShadow: true,\n };\n }\n } else {\n // Allow more attempts on same card\n return {\n nextCardAction: 'none',\n shouldLoadNextCard: false,\n isCorrect: false,\n shouldClearFeedbackShadow: true,\n };\n }\n }\n}\n","import {\n displayableDataToViewData,\n CardData,\n DisplayableData,\n isCourseElo,\n toCourseElo,\n} from '@vue-skuilder/common';\nimport { StudySessionItem } from '@db/impl/couch';\nimport { logger } from '@db/util/logger';\nimport { CourseDBInterface } from '@db/core';\n\n/**\n * Extract audio URLs from arbitrary field data using heuristic pattern matching.\n * This is a \"worse is better\" approach - catches obvious URLs, silently ignores edge cases.\n */\nfunction parseAudioURIs(data: unknown): string[] {\n if (typeof data !== 'string') return [];\n\n // Match URLs ending in common audio extensions\n const audioPattern = /https?:\\/\\/[^\\s\"'<>]+\\.(wav|mp3|ogg|m4a|aac|webm)/gi;\n return data.match(audioPattern) ?? [];\n}\n\n/**\n * Prefetch an audio file by loading it into browser cache.\n * Resolves when the audio is ready to play (or on error, to avoid blocking).\n */\nfunction prefetchAudio(url: string): Promise<void> {\n return new Promise((resolve) => {\n const audio = new Audio();\n audio.preload = 'auto';\n\n const cleanup = () => {\n audio.oncanplaythrough = null;\n audio.onerror = null;\n };\n\n audio.oncanplaythrough = () => {\n cleanup();\n resolve();\n };\n\n audio.onerror = () => {\n cleanup();\n logger.warn(`[CardHydrationService] Failed to prefetch audio: ${url}`);\n resolve(); // Don't block hydration on failed prefetch\n };\n\n audio.src = url;\n });\n}\n\nexport interface HydratedCard<TView = unknown> {\n item: StudySessionItem;\n view: TView;\n data: any[];\n}\n\n/**\n * Service responsible for managing hydrated (ready-to-display) cards.\n * Uses a Map-based cache for direct ID lookup - no ordering assumptions.\n * SessionController owns all ordering decisions.\n */\nexport class CardHydrationService<TView = unknown> {\n private hydratedCards: Map<string, HydratedCard<TView>> = new Map();\n private hydrationInFlight: Set<string> = new Set();\n private hydrationInProgress: boolean = false;\n\n constructor(\n private getViewComponent: (viewId: string) => TView,\n private getCourseDB: (courseId: string) => CourseDBInterface,\n private getItemsToHydrate: () => StudySessionItem[]\n ) {}\n\n /**\n * Get a hydrated card by ID.\n * @returns Hydrated card or null if not in cache\n */\n public getHydratedCard(cardId: string): HydratedCard<TView> | null {\n return this.hydratedCards.get(cardId) ?? null;\n }\n\n /**\n * Check if a card is hydrated.\n */\n public hasHydratedCard(cardId: string): boolean {\n return this.hydratedCards.has(cardId);\n }\n\n /**\n * Remove a card from the cache (call on successful dismiss to free memory).\n */\n public removeCard(cardId: string): void {\n this.hydratedCards.delete(cardId);\n }\n\n /**\n * Check if hydration should be triggered and start background hydration if needed.\n */\n public async ensureHydratedCards(): Promise<void> {\n void this.fillHydratedCards();\n }\n\n /**\n * Wait for a specific card to become hydrated.\n * @returns Promise that resolves to a hydrated card or null\n */\n public async waitForCard(cardId: string): Promise<HydratedCard<TView> | null> {\n // If already hydrated, return immediately\n if (this.hydratedCards.has(cardId)) {\n return this.hydratedCards.get(cardId)!;\n }\n\n // Start hydration if not already in progress\n if (!this.hydrationInProgress) {\n void this.fillHydratedCards();\n }\n\n // Wait for the specific card to become available\n const maxWaitMs = 10000; // 10 second timeout\n const pollIntervalMs = 25;\n let elapsed = 0;\n\n while (elapsed < maxWaitMs) {\n if (this.hydratedCards.has(cardId)) {\n return this.hydratedCards.get(cardId)!;\n }\n\n // If the card is not in flight and not hydrated, it may have failed\n if (!this.hydrationInFlight.has(cardId) && !this.hydrationInProgress) {\n break;\n }\n\n await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));\n elapsed += pollIntervalMs;\n }\n\n return this.hydratedCards.get(cardId) ?? null;\n }\n\n /**\n * Get current hydrated cache size.\n */\n public get hydratedCount(): number {\n return this.hydratedCards.size;\n }\n\n /**\n * Get list of currently hydrated card IDs (for debugging).\n */\n public getHydratedCardIds(): string[] {\n return Array.from(this.hydratedCards.keys());\n }\n\n /**\n * Fill the hydrated cache by hydrating items from getItemsToHydrate().\n * This is a pure cache-warming operation - no queue mutation.\n */\n private async fillHydratedCards(): Promise<void> {\n if (this.hydrationInProgress) {\n return; // Prevent concurrent hydration\n }\n\n this.hydrationInProgress = true;\n\n try {\n const itemsToHydrate = this.getItemsToHydrate();\n\n for (const item of itemsToHydrate) {\n // Skip if already hydrated or in flight\n if (this.hydratedCards.has(item.cardID) || this.hydrationInFlight.has(item.cardID)) {\n continue;\n }\n\n try {\n await this.hydrateCard(item);\n } catch (e) {\n logger.error(`[CardHydrationService] Error hydrating card ${item.cardID}:`, e);\n }\n }\n } finally {\n this.hydrationInProgress = false;\n }\n }\n\n /**\n * Hydrate a single card and add to cache.\n */\n private async hydrateCard(item: StudySessionItem): Promise<void> {\n if (this.hydratedCards.has(item.cardID) || this.hydrationInFlight.has(item.cardID)) {\n return; // Already hydrated or in progress\n }\n\n this.hydrationInFlight.add(item.cardID);\n\n try {\n const courseDB = this.getCourseDB(item.courseID);\n const cardData = await courseDB.getCourseDoc<CardData>(item.cardID);\n\n if (!isCourseElo(cardData.elo)) {\n cardData.elo = toCourseElo(cardData.elo);\n }\n\n const view = this.getViewComponent(cardData.id_view);\n const dataDocs = await Promise.all(\n cardData.id_displayable_data.map((id: string) =>\n courseDB.getCourseDoc<DisplayableData>(id, {\n attachments: true,\n binary: true,\n })\n )\n );\n\n // Extract audio URLs from all data fields and prefetch them\n const audioToPrefetch: string[] = [];\n dataDocs.forEach((dd) => {\n dd.data.forEach((f) => {\n audioToPrefetch.push(...parseAudioURIs(f.data));\n });\n });\n\n // Dedupe and prefetch, waiting for browser cache to be ready\n const uniqueAudioUrls = [...new Set(audioToPrefetch)];\n if (uniqueAudioUrls.length > 0) {\n logger.debug(\n `[CardHydrationService] Prefetching ${uniqueAudioUrls.length} audio files for card ${item.cardID}`\n );\n await Promise.allSettled(uniqueAudioUrls.map(prefetchAudio));\n }\n\n const data = dataDocs.map(displayableDataToViewData).reverse();\n\n this.hydratedCards.set(item.cardID, {\n item,\n view,\n data,\n });\n\n logger.debug(`[CardHydrationService] Hydrated card ${item.cardID}`);\n } finally {\n this.hydrationInFlight.delete(item.cardID);\n }\n }\n}\n","export class ItemQueue<T> {\n private q: T[] = [];\n private seenCardIds: string[] = [];\n private _dequeueCount: number = 0;\n public get dequeueCount(): number {\n return this._dequeueCount;\n }\n\n public add(item: T, cardId: string) {\n if (this.seenCardIds.find((d) => d === cardId)) {\n return; // do not re-add a card to the same queue\n }\n\n this.seenCardIds.push(cardId);\n this.q.push(item);\n }\n\n public addAll(items: T[], cardIdExtractor: (item: T) => string) {\n items.forEach((i) => this.add(i, cardIdExtractor(i)));\n }\n\n public get length() {\n return this.q.length;\n }\n\n public peek(index: number): T {\n return this.q[index];\n }\n\n public dequeue(cardIdExtractor?: (item: T) => string): T | null {\n if (this.q.length !== 0) {\n this._dequeueCount++;\n const item = this.q.splice(0, 1)[0];\n\n // Remove cardId from seenCardIds when dequeuing to allow re-queueing\n if (cardIdExtractor) {\n const cardId = cardIdExtractor(item);\n const index = this.seenCardIds.indexOf(cardId);\n if (index > -1) {\n this.seenCardIds.splice(index, 1);\n }\n }\n\n return item;\n } else {\n return null;\n }\n }\n\n public get toString(): string {\n return (\n `${typeof this.q[0]}:\\n` +\n this.q\n .map((i) => `\\t${(i as any).courseID}+${(i as any).cardID}: ${(i as any).status}`)\n .join('\\n')\n );\n }\n}","import { SrsService } from './services/SrsService';\nimport { EloService } from './services/EloService';\nimport { ResponseProcessor } from './services/ResponseProcessor';\nimport { CardHydrationService, HydratedCard } from './services/CardHydrationService';\nimport { ItemQueue } from './ItemQueue';\nimport {\n isReview,\n StudyContentSource,\n StudySessionFailedItem,\n StudySessionItem,\n StudySessionNewItem,\n StudySessionReviewItem,\n} from '@db/impl/couch';\n\nimport { CardRecord, CardHistory, CourseRegistrationDoc } from '@db/core';\nimport { Loggable } from '@db/util';\nimport { getCardOrigin } from '@db/core/navigators';\nimport { SourceMixer, QuotaRoundRobinMixer, SourceBatch } from './SourceMixer';\n\nexport interface StudySessionRecord {\n card: {\n course_id: string;\n card_id: string;\n card_elo: number;\n };\n item: StudySessionItem;\n records: CardRecord[];\n}\n\nimport { DataLayerProvider } from '@db/core';\nimport { logger } from '@db/util/logger';\n\nexport type SessionAction =\n | 'dismiss-success'\n | 'dismiss-failed'\n | 'marked-failed'\n | 'dismiss-error';\n\nexport interface ResponseResult {\n // Navigation\n nextCardAction: Exclude<SessionAction, 'dismiss-error'> | 'none';\n shouldLoadNextCard: boolean;\n\n // UI Data (let view decide how to render)\n isCorrect: boolean;\n performanceScore?: number; // for shadow color calculation\n\n // Cleanup\n shouldClearFeedbackShadow: boolean;\n}\n\ninterface SessionServices {\n response: ResponseProcessor;\n}\n\nexport class SessionController<TView = unknown> extends Loggable {\n _className = 'SessionController';\n\n public services: SessionServices;\n private srsService: SrsService;\n private eloService: EloService;\n private hydrationService: CardHydrationService<TView>;\n private mixer: SourceMixer;\n\n private sources: StudyContentSource[];\n // dataLayer and getViewComponent now injected into CardHydrationService\n private _sessionRecord: StudySessionRecord[] = [];\n public set sessionRecord(r: StudySessionRecord[]) {\n this._sessionRecord = r;\n }\n\n // Session card stores\n private _currentCard: HydratedCard<TView> | null = null;\n\n private reviewQ: ItemQueue<StudySessionReviewItem> = new ItemQueue<StudySessionReviewItem>();\n private newQ: ItemQueue<StudySessionNewItem> = new ItemQueue<StudySessionNewItem>();\n private failedQ: ItemQueue<StudySessionFailedItem> = new ItemQueue<StudySessionFailedItem>();\n // END Session card stores\n\n private startTime: Date;\n private endTime: Date;\n private _secondsRemaining: number;\n public get secondsRemaining(): number {\n return this._secondsRemaining;\n }\n public get report(): string {\n return `${this.reviewQ.dequeueCount} reviews, ${this.newQ.dequeueCount} new cards`;\n }\n public get detailedReport(): string {\n return this.newQ.toString + '\\n' + this.reviewQ.toString + '\\n' + this.failedQ.toString;\n }\n // @ts-expect-error NodeJS.Timeout type not available in browser context\n private _intervalHandle: NodeJS.Timeout;\n\n /**\n * @param sources - Array of content sources to mix for the session\n * @param time - Session duration in seconds\n * @param dataLayer - Data layer provider\n * @param getViewComponent - Function to resolve view components\n * @param mixer - Optional source mixer strategy (defaults to QuotaRoundRobinMixer)\n */\n constructor(\n sources: StudyContentSource[],\n time: number,\n dataLayer: DataLayerProvider,\n getViewComponent: (viewId: string) => TView,\n mixer?: SourceMixer\n ) {\n super();\n\n this.mixer = mixer || new QuotaRoundRobinMixer();\n this.srsService = new SrsService(dataLayer.getUserDB());\n this.eloService = new EloService(dataLayer, dataLayer.getUserDB());\n\n this.hydrationService = new CardHydrationService<TView>(\n getViewComponent,\n (courseId: string) => dataLayer.getCourseDB(courseId),\n () => this._getItemsToHydrate()\n );\n\n this.services = {\n response: new ResponseProcessor(this.srsService, this.eloService),\n };\n\n this.sources = sources;\n this.startTime = new Date();\n this._secondsRemaining = time;\n this.endTime = new Date(this.startTime.valueOf() + 1000 * this._secondsRemaining);\n\n this.log(`Session constructed:\n startTime: ${this.startTime}\n endTime: ${this.endTime}`);\n }\n\n private tick() {\n this._secondsRemaining = Math.floor((this.endTime.valueOf() - Date.now()) / 1000);\n // this.log(this.secondsRemaining);\n\n if (this._secondsRemaining <= 0) {\n clearInterval(this._intervalHandle);\n }\n }\n\n /**\n * Returns a rough, erring toward conservative, guess at\n * the amount of time the failed cards queue will require\n * to clean up.\n *\n * (seconds)\n */\n private estimateCleanupTime(): number {\n let time: number = 0;\n for (let i = 0; i < this.failedQ.length; i++) {\n const c = this.failedQ.peek(i);\n // this.log(`Failed card ${c.qualifiedID} found`)\n\n const record = this._sessionRecord.find((r) => r.item.cardID === c.cardID);\n let cardTime = 0;\n\n if (record) {\n // this.log(`Card Record Found...`);\n for (let j = 0; j < record.records.length; j++) {\n cardTime += record.records[j].timeSpent;\n }\n cardTime = cardTime / record.records.length;\n time += cardTime;\n }\n }\n\n const ret: number = time / 1000;\n this.log(`Failed card cleanup estimate: ${Math.round(ret)}`);\n return ret;\n }\n\n /**\n * Extremely rough, conservative, estimate of amound of time to complete\n * all scheduled reviews\n */\n private estimateReviewTime(): number {\n const ret = 5 * this.reviewQ.length;\n this.log(`Review card time estimate: ${ret}`);\n return ret;\n }\n\n public async prepareSession() {\n // All content sources must implement getWeightedCards()\n if (this.sources.some((s) => typeof s.getWeightedCards !== 'function')) {\n throw new Error(\n '[SessionController] All content sources must implement getWeightedCards().'\n );\n }\n\n await this.getWeightedContent();\n await this.hydrationService.ensureHydratedCards();\n\n this._intervalHandle = setInterval(() => {\n this.tick();\n }, 1000);\n }\n\n public addTime(seconds: number) {\n this.endTime = new Date(this.endTime.valueOf() + 1000 * seconds);\n }\n\n public get failedCount(): number {\n return this.failedQ.length;\n }\n\n public toString() {\n return `Session: ${this.reviewQ.length} Reviews, ${this.newQ.length} New, ${this.failedQ.length} failed`;\n }\n public reportString() {\n return `${this.reviewQ.dequeueCount} Reviews, ${this.newQ.dequeueCount} New, ${this.failedQ.dequeueCount} failed`;\n }\n\n /**\n * Returns debug information about the current session state.\n * Used by SessionControllerDebug component for runtime inspection.\n */\n public getDebugInfo() {\n // Check if sources support weighted cards\n const supportsWeightedCards = this.sources.some(\n (s) => typeof s.getWeightedCards === 'function'\n );\n\n const extractQueueItems = (queue: ItemQueue<any>, limit: number = 10) => {\n const items = [];\n for (let i = 0; i < Math.min(queue.length, limit); i++) {\n const item = queue.peek(i);\n items.push({\n courseID: item.courseID || 'unknown',\n cardID: item.cardID || 'unknown',\n status: item.status || 'unknown',\n });\n }\n return items;\n };\n\n return {\n api: {\n mode: supportsWeightedCards ? 'weighted' : 'legacy',\n description: supportsWeightedCards\n ? 'Using getWeightedCards() API with scored candidates'\n : 'ERROR: getWeightedCards() not a function.',\n },\n reviewQueue: {\n length: this.reviewQ.length,\n dequeueCount: this.reviewQ.dequeueCount,\n items: extractQueueItems(this.reviewQ),\n },\n newQueue: {\n length: this.newQ.length,\n dequeueCount: this.newQ.dequeueCount,\n items: extractQueueItems(this.newQ),\n },\n failedQueue: {\n length: this.failedQ.length,\n dequeueCount: this.failedQ.dequeueCount,\n items: extractQueueItems(this.failedQ),\n },\n hydratedCache: {\n count: this.hydrationService.hydratedCount,\n cardIds: this.hydrationService.getHydratedCardIds(),\n },\n };\n }\n\n /**\n * Fetch content using the getWeightedCards API and mix across sources.\n *\n * This method:\n * 1. Fetches weighted cards from each source\n * 2. Fetches full review data (we need ScheduledCard fields for queue)\n * 3. Uses SourceMixer to balance content across sources\n * 4. Populates review and new card queues with mixed results\n */\n private async getWeightedContent() {\n const limit = 20; // Initial batch size per source\n\n // Collect batches from each source\n const batches: SourceBatch[] = [];\n\n for (let i = 0; i < this.sources.length; i++) {\n const source = this.sources[i];\n try {\n // Fetch weighted cards for mixing\n const weighted = await source.getWeightedCards!(limit);\n\n batches.push({\n sourceIndex: i,\n weighted,\n });\n } catch (error) {\n this.error(`Failed to get content from source ${i}:`, error);\n // Re-throw if this is the only source - we can't proceed without any content\n if (this.sources.length === 1) {\n throw new Error(`Cannot start session: failed to load content from source ${i}`);\n }\n }\n }\n\n // Verify we got content from at least one source\n if (batches.length === 0) {\n throw new Error(\n `Cannot start session: failed to load content from all ${this.sources.length} source(s). ` +\n `Check logs for details.`\n );\n }\n\n // Mix weighted cards across sources using configured strategy\n const mixedWeighted = this.mixer.mix(batches, limit * this.sources.length);\n\n // Split mixed results by card origin\n const reviewWeighted = mixedWeighted.filter((w) => getCardOrigin(w) === 'review');\n const newWeighted = mixedWeighted.filter((w) => getCardOrigin(w) === 'new');\n\n logger.debug(`[reviews] got ${reviewWeighted.length} reviews from mixer`);\n\n // Populate review queue from mixed results (already sorted by mixer)\n let report = 'Mixed content session created with:\\n';\n for (const w of reviewWeighted) {\n const reviewItem: StudySessionReviewItem = {\n cardID: w.cardId,\n courseID: w.courseId,\n contentSourceType: 'course',\n contentSourceID: w.courseId,\n reviewID: w.reviewID!,\n status: 'review',\n };\n this.reviewQ.add(reviewItem, reviewItem.cardID);\n report += `Review: ${w.courseId}::${w.cardId} (score: ${w.score.toFixed(2)})\\n`;\n }\n\n // Populate new card queue from mixed results (already sorted by mixer)\n for (const w of newWeighted) {\n const newItem: StudySessionNewItem = {\n cardID: w.cardId,\n courseID: w.courseId,\n contentSourceType: 'course',\n contentSourceID: w.courseId,\n status: 'new',\n };\n this.newQ.add(newItem, newItem.cardID);\n report += `New: ${w.courseId}::${w.cardId} (score: ${w.score.toFixed(2)})\\n`;\n }\n\n this.log(report);\n }\n\n /**\n * Returns items that should be pre-hydrated.\n * Deterministic: top N items from each queue to ensure coverage.\n * Failed queue items will typically already be hydrated (from initial render).\n */\n private _getItemsToHydrate(): StudySessionItem[] {\n const items: StudySessionItem[] = [];\n const ITEMS_PER_QUEUE = 2;\n\n for (let i = 0; i < Math.min(ITEMS_PER_QUEUE, this.reviewQ.length); i++) {\n items.push(this.reviewQ.peek(i));\n }\n for (let i = 0; i < Math.min(ITEMS_PER_QUEUE, this.newQ.length); i++) {\n items.push(this.newQ.peek(i));\n }\n for (let i = 0; i < Math.min(ITEMS_PER_QUEUE, this.failedQ.length); i++) {\n items.push(this.failedQ.peek(i));\n }\n\n return items;\n }\n\n /**\n * Selects the next item to present to the user.\n * Nondeterministic: uses probability to balance between queues based on session state.\n */\n private _selectNextItemToHydrate(): StudySessionItem | null {\n const choice = Math.random();\n let newBound: number = 0.1;\n let reviewBound: number = 0.75;\n\n if (this.reviewQ.length === 0 && this.failedQ.length === 0 && this.newQ.length === 0) {\n // all queues empty - session is over (and course is complete?)\n return null;\n }\n\n if (this._secondsRemaining < 2 && this.failedQ.length === 0) {\n // session is over!\n return null;\n }\n\n // If timer expired, only return failed cards\n if (this._secondsRemaining <= 0) {\n if (this.failedQ.length > 0) {\n return this.failedQ.peek(0);\n } else {\n return null; // No more failed cards, session over\n }\n }\n\n // supply new cards at start of session\n if (this.newQ.dequeueCount < this.sources.length && this.newQ.length) {\n return this.newQ.peek(0);\n }\n\n const cleanupTime = this.estimateCleanupTime();\n const reviewTime = this.estimateReviewTime();\n const availableTime = this._secondsRemaining - (cleanupTime + reviewTime);\n\n // if time-remaing vs (reviewQ + failureQ) looks good,\n // lean toward newQ\n if (availableTime > 20) {\n newBound = 0.5;\n reviewBound = 0.9;\n }\n // else if time-remaining vs failureQ looks good,\n // lean toward reviewQ\n else if (this._secondsRemaining - cleanupTime > 20) {\n newBound = 0.05;\n reviewBound = 0.9;\n }\n // else (time-remaining vs failureQ looks bad!)\n // lean heavily toward failureQ\n else {\n newBound = 0.01;\n reviewBound = 0.1;\n }\n\n // exclude possibility of drawing from empty queues\n if (this.failedQ.length === 0) {\n reviewBound = 1;\n }\n if (this.reviewQ.length === 0) {\n newBound = reviewBound;\n }\n\n if (choice < newBound && this.newQ.length) {\n return this.newQ.peek(0);\n } else if (choice < reviewBound && this.reviewQ.length) {\n return this.reviewQ.peek(0);\n } else if (this.failedQ.length) {\n return this.failedQ.peek(0);\n } else {\n this.log(`No more cards available for the session!`);\n return null;\n }\n }\n\n public async nextCard(\n action: SessionAction = 'dismiss-success'\n ): Promise<HydratedCard<TView> | null> {\n // dismiss (or sort to failedQ) the current card\n this.dismissCurrentCard(action);\n\n if (this._secondsRemaining <= 0 && this.failedQ.length === 0) {\n this._currentCard = null;\n return null;\n }\n\n // Get what SessionController thinks should be next\n const nextItem = this._selectNextItemToHydrate();\n if (!nextItem) {\n this._currentCard = null;\n return null;\n }\n\n // Look up in hydration cache\n let card = this.hydrationService.getHydratedCard(nextItem.cardID);\n\n // If not ready, wait for it\n if (!card) {\n card = await this.hydrationService.waitForCard(nextItem.cardID);\n }\n\n // Remove from source queue now that we're consuming it\n this.removeItemFromQueue(nextItem);\n\n // Trigger background hydration to maintain cache (async, non-blocking)\n await this.hydrationService.ensureHydratedCards();\n\n this._currentCard = card;\n return card;\n }\n\n /**\n * Public API for processing user responses to cards.\n * @param cardRecord User's response record\n * @param cardHistory Promise resolving to the card's history\n * @param courseRegistrationDoc User's course registration document\n * @param currentCard Current study session record\n * @param courseId Course identifier\n * @param cardId Card identifier\n * @param maxAttemptsPerView Maximum attempts allowed per view\n * @param maxSessionViews Maximum session views for this card\n * @param sessionViews Current number of session views\n * @returns ResponseResult with navigation and UI instructions\n */\n public async submitResponse(\n cardRecord: CardRecord,\n cardHistory: Promise<CardHistory<CardRecord>>,\n courseRegistrationDoc: CourseRegistrationDoc,\n currentCard: StudySessionRecord,\n courseId: string,\n cardId: string,\n maxAttemptsPerView: number,\n maxSessionViews: number,\n sessionViews: number\n ): Promise<ResponseResult> {\n const studySessionItem: StudySessionItem = {\n ...currentCard.item,\n };\n\n return await this.services.response.processResponse(\n cardRecord,\n cardHistory,\n studySessionItem,\n courseRegistrationDoc,\n currentCard,\n courseId,\n cardId,\n maxAttemptsPerView,\n maxSessionViews,\n sessionViews\n );\n }\n\n private dismissCurrentCard(action: SessionAction = 'dismiss-success') {\n if (this._currentCard) {\n // this.log(`Running dismissCurrentCard on ${this._currentCard!.qualifiedID}`);\n // if (action.includes('dismiss')) {\n // if (this._currentCard.status === 'review' ||\n // this._currentCard.status === 'failed-review') {\n // removeScheduledCardReview(this.user.getUsername(),\n // (this._currentCard as StudySessionReviewItem).reviewID);\n // this.log(`Dismissed review card: ${this._currentCard.qualifiedID}`)\n // }\n // }\n\n if (action === 'dismiss-success') {\n // Remove from hydration cache to free memory\n this.hydrationService.removeCard(this._currentCard.item.cardID);\n // schedule a review - currently done in Study.vue\n } else if (action === 'marked-failed') {\n // Card stays in hydration cache for re-use (no removeCard call)\n\n let failedItem: StudySessionFailedItem;\n\n if (isReview(this._currentCard.item)) {\n failedItem = {\n cardID: this._currentCard.item.cardID,\n courseID: this._currentCard.item.courseID,\n contentSourceID: this._currentCard.item.contentSourceID,\n contentSourceType: this._currentCard.item.contentSourceType,\n status: 'failed-review',\n reviewID: this._currentCard.item.reviewID,\n };\n } else {\n failedItem = {\n cardID: this._currentCard.item.cardID,\n courseID: this._currentCard.item.courseID,\n contentSourceID: this._currentCard.item.contentSourceID,\n contentSourceType: this._currentCard.item.contentSourceType,\n status: 'failed-new',\n };\n }\n\n this.failedQ.add(failedItem, failedItem.cardID);\n } else if (action === 'dismiss-error') {\n // Remove from cache on error as well\n this.hydrationService.removeCard(this._currentCard.item.cardID);\n } else if (action === 'dismiss-failed') {\n // Remove from cache - card has been fully processed after failure cleanup\n this.hydrationService.removeCard(this._currentCard.item.cardID);\n }\n }\n }\n\n /**\n * Remove an item from its source queue after consumption by nextCard().\n */\n private removeItemFromQueue(item: StudySessionItem): void {\n // Check each queue - item should be at the front of one of them\n if (this.reviewQ.peek(0)?.cardID === item.cardID) {\n this.reviewQ.dequeue((queueItem) => queueItem.cardID);\n } else if (this.newQ.peek(0)?.cardID === item.cardID) {\n this.newQ.dequeue((queueItem) => queueItem.cardID);\n } else if (this.failedQ.peek(0)?.cardID === item.cardID) {\n this.failedQ.dequeue((queueItem) => queueItem.cardID);\n }\n }\n}\n","export * from './Loggable';\nexport * from './packer';\nexport * from './migrator';\nexport * from './dataDirectory';\n","// packages/db/src/util/packer/CouchDBToStaticPacker.ts\n\nimport { CardData, DocType, Tag } from '../../core/types/types-legacy';\nimport { logger } from '../logger';\n// CourseConfig interface - simplified for packer use\n\nimport { CourseConfig } from '@vue-skuilder/common';\nimport {\n ChunkMetadata,\n DesignDocument,\n IndexMetadata,\n PackedCourseData,\n PackerConfig,\n StaticCourseManifest,\n AttachmentData,\n} from './types';\nimport { FileSystemAdapter } from '../migrator/FileSystemAdapter';\n\nexport class CouchDBToStaticPacker {\n private config: PackerConfig;\n private sourceDB: PouchDB.Database | null = null;\n\n constructor(config: Partial<PackerConfig> = {}) {\n this.config = {\n chunkSize: 1000,\n includeAttachments: true,\n ...config,\n };\n }\n\n /**\n * Pack a CouchDB course database into static data structures\n */\n async packCourse(sourceDB: PouchDB.Database, courseId: string): Promise<PackedCourseData> {\n logger.info(`Starting static pack for course: ${courseId}`);\n this.sourceDB = sourceDB;\n\n const manifest: StaticCourseManifest = {\n version: '1.0.0',\n courseId,\n courseName: '',\n courseConfig: null,\n lastUpdated: new Date().toISOString(),\n documentCount: 0,\n chunks: [],\n indices: [],\n designDocs: [],\n };\n\n // 1. Extract course config\n const courseConfig = await this.extractCourseConfig(sourceDB);\n manifest.courseName = courseConfig.name;\n manifest.courseConfig = courseConfig;\n\n // 2. Extract and process design documents\n manifest.designDocs = await this.extractDesignDocs(sourceDB);\n\n // 3. Extract all documents by type and create chunks\n const docsByType = await this.extractDocumentsByType(sourceDB);\n\n // 4. Extract attachments if enabled\n const attachments = new Map<string, AttachmentData>();\n if (this.config.includeAttachments) {\n await this.extractAllAttachments(docsByType, attachments);\n }\n\n // 5. Create chunks and prepare chunk data\n const chunks = new Map<string, any[]>();\n for (const [docType, docs] of Object.entries(docsByType)) {\n const chunkMetadata = this.createChunks(docs, docType as DocType);\n manifest.chunks.push(...chunkMetadata);\n manifest.documentCount += docs.length;\n\n // Prepare chunk data\n this.prepareChunkData(chunkMetadata, docs, chunks);\n }\n\n // 6. Build indices\n const indices = new Map<string, any>();\n manifest.indices = await this.buildIndices(docsByType, manifest.designDocs, indices);\n\n return {\n manifest,\n chunks,\n indices,\n attachments,\n };\n }\n\n /**\n * Pack a CouchDB course database and write the static files to disk\n */\n async packCourseToFiles(\n sourceDB: PouchDB.Database, \n courseId: string, \n outputDir: string, \n fsAdapter: FileSystemAdapter\n ): Promise<{ \n manifest: StaticCourseManifest; \n filesWritten: number; \n attachmentsFound: number; \n }> {\n logger.info(`Packing course ${courseId} to files in ${outputDir}`);\n \n // First, pack the course data\n const packedData = await this.packCourse(sourceDB, courseId);\n \n // Write the files using the FileSystemAdapter\n const filesWritten = await this.writePackedDataToFiles(packedData, outputDir, fsAdapter);\n \n return {\n manifest: packedData.manifest,\n filesWritten,\n attachmentsFound: packedData.attachments ? packedData.attachments.size : 0,\n };\n }\n\n /**\n * Write packed course data to files using FileSystemAdapter\n */\n private async writePackedDataToFiles(\n packedData: PackedCourseData,\n outputDir: string,\n fsAdapter: FileSystemAdapter\n ): Promise<number> {\n let totalFiles = 0;\n \n // Ensure output directory exists\n await fsAdapter.ensureDir(outputDir);\n \n // Write manifest\n const manifestPath = fsAdapter.joinPath(outputDir, 'manifest.json');\n await fsAdapter.writeJson(manifestPath, packedData.manifest, { spaces: 2 });\n totalFiles++;\n logger.info(`Wrote manifest: ${manifestPath}`);\n \n // Create subdirectories\n const chunksDir = fsAdapter.joinPath(outputDir, 'chunks');\n const indicesDir = fsAdapter.joinPath(outputDir, 'indices');\n await fsAdapter.ensureDir(chunksDir);\n await fsAdapter.ensureDir(indicesDir);\n \n // Write chunks\n for (const [chunkId, chunkData] of packedData.chunks) {\n const chunkPath = fsAdapter.joinPath(chunksDir, `${chunkId}.json`);\n await fsAdapter.writeJson(chunkPath, chunkData);\n totalFiles++;\n }\n logger.info(`Wrote ${packedData.chunks.size} chunk files`);\n \n // Write indices\n for (const [indexName, indexData] of packedData.indices) {\n const indexPath = fsAdapter.joinPath(indicesDir, `${indexName}.json`);\n await fsAdapter.writeJson(indexPath, indexData, { spaces: 2 });\n totalFiles++;\n }\n logger.info(`Wrote ${packedData.indices.size} index files`);\n \n // Write attachments\n if (packedData.attachments && packedData.attachments.size > 0) {\n for (const [attachmentPath, attachmentData] of packedData.attachments) {\n const fullAttachmentPath = fsAdapter.joinPath(outputDir, attachmentPath);\n \n // Ensure attachment directory exists\n const attachmentDir = fsAdapter.dirname(fullAttachmentPath);\n await fsAdapter.ensureDir(attachmentDir);\n \n // Write binary file\n await fsAdapter.writeFile(fullAttachmentPath, attachmentData.buffer);\n totalFiles++;\n }\n logger.info(`Wrote ${packedData.attachments.size} attachment files`);\n }\n \n return totalFiles;\n }\n\n private async extractCourseConfig(db: PouchDB.Database): Promise<CourseConfig> {\n try {\n return await db.get<CourseConfig>('CourseConfig');\n } catch (error) {\n logger.error('Failed to extract course config:', error);\n throw new Error('Course config not found');\n }\n }\n\n private async extractDesignDocs(db: PouchDB.Database): Promise<DesignDocument[]> {\n const result = await db.allDocs({\n startkey: '_design/',\n endkey: '_design/\\ufff0',\n include_docs: true,\n });\n\n return result.rows.map((row) => ({\n _id: row.id,\n views: (row.doc as any).views || {},\n }));\n }\n\n private async extractDocumentsByType(db: PouchDB.Database): Promise<Record<DocType, any[]>> {\n const allDocs = await db.allDocs({ include_docs: true });\n const docsByType: Record<string, any[]> = {};\n\n for (const row of allDocs.rows) {\n if (row.id.startsWith('_')) continue; // Skip design docs\n\n const doc = row.doc as any;\n if (doc.docType) {\n if (!docsByType[doc.docType]) {\n docsByType[doc.docType] = [];\n }\n docsByType[doc.docType].push(doc);\n }\n }\n\n return docsByType as Record<DocType, any[]>;\n }\n\n private createChunks(docs: any[], docType: DocType): ChunkMetadata[] {\n const chunks: ChunkMetadata[] = [];\n const sortedDocs = docs.sort((a, b) => a._id.localeCompare(b._id));\n\n for (let i = 0; i < sortedDocs.length; i += this.config.chunkSize) {\n const chunk = sortedDocs.slice(i, i + this.config.chunkSize);\n const chunkId = `${docType}-${String(Math.floor(i / this.config.chunkSize)).padStart(4, '0')}`;\n\n chunks.push({\n id: chunkId,\n docType,\n startKey: chunk[0]._id,\n endKey: chunk[chunk.length - 1]._id,\n documentCount: chunk.length,\n path: `chunks/${chunkId}.json`,\n });\n }\n\n return chunks;\n }\n\n private prepareChunkData(\n chunkMetadata: ChunkMetadata[],\n docs: any[],\n chunks: Map<string, any[]>\n ): void {\n const sortedDocs = docs.sort((a, b) => a._id.localeCompare(b._id));\n\n for (const chunk of chunkMetadata) {\n const chunkDocs = sortedDocs.filter(\n (doc) => doc._id >= chunk.startKey && doc._id <= chunk.endKey\n );\n\n // Clean documents for storage\n const cleanedDocs = chunkDocs.map((doc) => {\n const cleaned = { ...doc };\n delete cleaned._rev; // Remove revision info\n \n if (this.config.includeAttachments && cleaned._attachments) {\n // Transform attachment stubs to file paths\n cleaned._attachments = this.transformAttachmentStubs(cleaned._attachments, cleaned._id);\n } else if (!this.config.includeAttachments) {\n delete cleaned._attachments;\n }\n \n return cleaned;\n });\n\n chunks.set(chunk.id, cleanedDocs);\n }\n }\n\n private async buildIndices(\n docsByType: Record<DocType, any[]>,\n designDocs: DesignDocument[],\n indices: Map<string, any>\n ): Promise<IndexMetadata[]> {\n const indexMetadata: IndexMetadata[] = [];\n\n // Build ELO index\n if (docsByType[DocType.CARD]) {\n const eloIndexMeta = await this.buildEloIndex(\n docsByType[DocType.CARD] as CardData[],\n indices\n );\n indexMetadata.push(eloIndexMeta);\n }\n\n // Build tag indices\n if (docsByType[DocType.TAG]) {\n const tagIndexMeta = await this.buildTagIndex(docsByType[DocType.TAG] as Tag[], indices);\n indexMetadata.push(tagIndexMeta);\n }\n\n // Build indices from design documents using CouchDB view queries\n for (const designDoc of designDocs) {\n for (const [viewName, viewDef] of Object.entries(designDoc.views)) {\n if (viewDef.map) {\n logger.info(`Processing view: ${designDoc._id}/${viewName}`);\n const indexMeta = await this.buildViewIndex(viewName, designDoc, indices);\n if (indexMeta) {\n indexMetadata.push(indexMeta);\n logger.info(`Successfully built index: ${indexMeta.name}`);\n } else {\n logger.warn(`Skipped view index: ${designDoc._id}/${viewName}`);\n }\n }\n }\n }\n\n return indexMetadata;\n }\n\n private async buildEloIndex(\n cards: CardData[],\n indices: Map<string, any>\n ): Promise<IndexMetadata> {\n // Build a B-tree like structure for ELO queries\n const eloIndex: Array<{ elo: number; cardId: string }> = [];\n\n for (const card of cards) {\n if (card.elo?.global?.score) {\n eloIndex.push({\n elo: card.elo.global.score,\n cardId: (card as any)._id,\n });\n }\n }\n\n // Sort by ELO for efficient range queries\n eloIndex.sort((a, b) => a.elo - b.elo);\n\n // Create buckets for faster lookup\n const buckets: Record<number, string[]> = {};\n const bucketSize = 50; // ELO points per bucket\n\n for (const entry of eloIndex) {\n const bucket = Math.floor(entry.elo / bucketSize) * bucketSize;\n if (!buckets[bucket]) buckets[bucket] = [];\n buckets[bucket].push(entry.cardId);\n }\n\n // Store the index data\n indices.set('elo', {\n sorted: eloIndex,\n buckets: buckets,\n stats: {\n min: eloIndex[0]?.elo || 0,\n max: eloIndex[eloIndex.length - 1]?.elo || 0,\n count: eloIndex.length,\n },\n });\n\n return {\n name: 'elo',\n type: 'btree',\n path: 'indices/elo.json',\n };\n }\n\n private async buildTagIndex(tags: Tag[], indices: Map<string, any>): Promise<IndexMetadata> {\n // Build inverted index for tags\n const tagIndex: Record<\n string,\n {\n cardIds: string[];\n snippet: string;\n count: number;\n }\n > = {};\n\n for (const tag of tags) {\n tagIndex[tag.name] = {\n cardIds: tag.taggedCards,\n snippet: tag.snippet,\n count: tag.taggedCards.length,\n };\n }\n\n // Also build a reverse index (card -> tags)\n const cardToTags: Record<string, string[]> = {};\n for (const tag of tags) {\n for (const cardId of tag.taggedCards) {\n if (!cardToTags[cardId]) cardToTags[cardId] = [];\n cardToTags[cardId].push(tag.name);\n }\n }\n\n indices.set('tags', {\n byTag: tagIndex,\n byCard: cardToTags,\n });\n\n return {\n name: 'tags',\n type: 'hash',\n path: 'indices/tags.json',\n };\n }\n\n /**\n * Build view index by querying CouchDB views directly instead of parsing map functions\n */\n private async buildViewIndex(\n viewName: string,\n designDoc: DesignDocument,\n indices: Map<string, any>\n ): Promise<IndexMetadata | null> {\n if (!this.sourceDB) {\n logger.error('Source database not available for view querying');\n return null;\n }\n\n try {\n const designDocId = designDoc._id; // e.g., \"_design/elo\"\n const designDocName = designDocId.replace('_design/', ''); // Extract just \"elo\"\n const viewPath = `${designDocName}/${viewName}`;\n \n logger.info(`Querying CouchDB view: ${viewPath}`);\n \n // Query the view directly from CouchDB using PouchDB format: \"designDocName/viewName\"\n const viewResults = await this.sourceDB.query(viewPath, {\n include_docs: false,\n });\n\n if (!viewResults.rows || viewResults.rows.length === 0) {\n logger.warn(`View ${viewPath} returned no results`);\n return null;\n }\n\n logger.info(`Successfully queried view ${viewPath}: ${viewResults.rows.length} results`);\n\n // Format the results for static consumption\n const formattedResults = this.formatViewResults(viewName, viewResults.rows, designDoc);\n \n const indexName = `view-${designDoc._id.replace('_design/', '')}-${viewName}`;\n indices.set(indexName, formattedResults);\n\n return {\n name: indexName,\n type: 'view',\n path: `indices/${indexName}.json`,\n };\n } catch (error) {\n logger.error(`Failed to query view ${designDoc._id}/${viewName}:`, error);\n // Return null to gracefully skip this view rather than failing the entire pack\n return null;\n }\n }\n\n /**\n * Format CouchDB view results for static consumption\n */\n private formatViewResults(\n viewName: string,\n viewRows: Array<{ key: any; value: any; id: string }>,\n designDoc: DesignDocument\n ): any {\n const baseResult = {\n type: 'couchdb-view',\n viewName,\n designDoc: designDoc._id,\n results: viewRows,\n metadata: {\n resultCount: viewRows.length,\n generatedAt: new Date().toISOString(),\n },\n };\n\n // Apply view-specific formatting\n switch (viewName) {\n case 'elo':\n return this.formatEloViewIndex(viewRows, baseResult);\n case 'getTags':\n return this.formatTagsViewIndex(viewRows, baseResult);\n case 'cardsByInexperience':\n return this.formatInexperienceViewIndex(viewRows, baseResult);\n default:\n return this.formatGenericViewIndex(viewRows, baseResult);\n }\n }\n\n /**\n * Format ELO view results - convert to sorted array format\n */\n private formatEloViewIndex(\n viewRows: Array<{ key: any; value: any; id: string }>,\n baseResult: any\n ): any {\n // Sort by ELO score (key) for efficient range queries\n const sortedResults = viewRows.sort((a, b) => {\n if (typeof a.key === 'number' && typeof b.key === 'number') {\n return a.key - b.key;\n }\n return 0;\n });\n\n return {\n ...baseResult,\n sorted: sortedResults,\n stats: {\n min: sortedResults[0]?.key || 0,\n max: sortedResults[sortedResults.length - 1]?.key || 0,\n count: sortedResults.length,\n },\n };\n }\n\n /**\n * Format tags view results - convert to tag mapping format\n */\n private formatTagsViewIndex(\n viewRows: Array<{ key: any; value: any; id: string }>,\n baseResult: any\n ): any {\n // Group by tag name (key)\n const tagMap: Record<string, string[]> = {};\n \n for (const row of viewRows) {\n const tagName = row.key;\n if (typeof tagName === 'string') {\n if (!tagMap[tagName]) {\n tagMap[tagName] = [];\n }\n tagMap[tagName].push(row.id);\n }\n }\n\n return {\n ...baseResult,\n byTag: tagMap,\n tagCount: Object.keys(tagMap).length,\n };\n }\n\n /**\n * Format inexperience view results - convert to experience-based format\n */\n private formatInexperienceViewIndex(\n viewRows: Array<{ key: any; value: any; id: string }>,\n baseResult: any\n ): any {\n // Sort by inexperience level (key) - lower numbers = less experience\n const sortedResults = viewRows.sort((a, b) => {\n if (typeof a.key === 'number' && typeof b.key === 'number') {\n return a.key - b.key;\n }\n return 0;\n });\n\n return {\n ...baseResult,\n sorted: sortedResults,\n stats: {\n minInexperience: sortedResults[0]?.key || 0,\n maxInexperience: sortedResults[sortedResults.length - 1]?.key || 0,\n count: sortedResults.length,\n },\n };\n }\n\n /**\n * Format generic view results - fallback for unknown views\n */\n private formatGenericViewIndex(\n _viewRows: Array<{ key: any; value: any; id: string }>,\n baseResult: any\n ): any {\n return {\n ...baseResult,\n // Keep results as-is for unknown view types\n };\n }\n\n /**\n * Extract all attachments from documents and download binary data\n */\n private async extractAllAttachments(\n docsByType: Record<DocType, any[]>,\n attachments: Map<string, AttachmentData>\n ): Promise<void> {\n logger.info('Extracting attachments...');\n \n const allDocs: any[] = [];\n for (const docs of Object.values(docsByType)) {\n allDocs.push(...docs);\n }\n\n const docsWithAttachments = allDocs.filter(doc => doc._attachments && Object.keys(doc._attachments).length > 0);\n \n if (docsWithAttachments.length === 0) {\n logger.info('No attachments found');\n return;\n }\n\n logger.info(`Found ${docsWithAttachments.length} documents with attachments`);\n\n // Process attachments concurrently\n const extractionPromises = docsWithAttachments.map(doc => \n this.extractDocumentAttachments(doc, attachments)\n );\n\n await Promise.all(extractionPromises);\n \n logger.info(`Extracted ${attachments.size} attachment files`);\n }\n\n /**\n * Extract attachments for a single document\n */\n private async extractDocumentAttachments(\n doc: any,\n attachments: Map<string, AttachmentData>\n ): Promise<void> {\n if (!doc._attachments || !this.sourceDB) {\n return;\n }\n\n const docId = doc._id;\n \n for (const [attachmentName, metadata] of Object.entries(doc._attachments as Record<string, any>)) {\n try {\n // Download attachment binary data\n const attachmentResponse = await this.sourceDB.getAttachment(docId, attachmentName);\n \n // Convert to buffer\n let buffer: Buffer;\n if (attachmentResponse instanceof ArrayBuffer) {\n buffer = Buffer.from(attachmentResponse);\n } else if (Buffer.isBuffer(attachmentResponse)) {\n buffer = attachmentResponse;\n } else {\n // For browser environments, the response might be a Blob\n const blob = attachmentResponse as Blob;\n buffer = Buffer.from(await blob.arrayBuffer());\n }\n\n // Generate filename with proper extension\n const extension = this.getFileExtension(metadata.content_type);\n const filename = `${attachmentName}${extension}`;\n const attachmentPath = `attachments/${docId}/${filename}`;\n \n // Store attachment data\n attachments.set(attachmentPath, {\n docId,\n attachmentName,\n filename,\n path: attachmentPath,\n contentType: metadata.content_type,\n length: metadata.length || buffer.length,\n digest: metadata.digest,\n buffer,\n });\n\n logger.debug(`Extracted attachment: ${attachmentPath}`);\n } catch (error) {\n logger.error(`Failed to extract attachment ${docId}/${attachmentName}:`, error);\n throw new Error(`Failed to extract attachment ${docId}/${attachmentName}: ${error}`);\n }\n }\n }\n\n /**\n * Transform attachment stubs to include file paths\n */\n private transformAttachmentStubs(\n attachments: Record<string, any>,\n docId: string\n ): Record<string, any> {\n const transformed: Record<string, any> = {};\n \n for (const [attachmentName, metadata] of Object.entries(attachments)) {\n const extension = this.getFileExtension(metadata.content_type);\n const filename = `${attachmentName}${extension}`;\n const attachmentPath = `attachments/${docId}/${filename}`;\n \n transformed[attachmentName] = {\n path: attachmentPath,\n content_type: metadata.content_type,\n length: metadata.length,\n digest: metadata.digest,\n stub: false, // No longer a stub - we have the actual file\n };\n }\n \n return transformed;\n }\n\n /**\n * Get file extension from content type\n */\n private getFileExtension(contentType: string): string {\n const extensionMap: Record<string, string> = {\n 'image/jpeg': '.jpg',\n 'image/jpg': '.jpg', \n 'image/png': '.png',\n 'image/gif': '.gif',\n 'image/webp': '.webp',\n 'audio/mpeg': '.mp3',\n 'audio/mp3': '.mp3',\n 'audio/wav': '.wav',\n 'audio/ogg': '.ogg',\n 'video/mp4': '.mp4',\n 'video/webm': '.webm',\n 'application/pdf': '.pdf',\n 'text/plain': '.txt',\n 'application/json': '.json',\n };\n \n return extensionMap[contentType] || '';\n }\n}\n","// packages/db/src/util/migrator/StaticToCouchDBMigrator.ts\n\nimport { logger } from '../logger';\nimport { StaticCourseManifest, ChunkMetadata, DesignDocument } from '../packer/types';\nimport {\n MigrationOptions,\n MigrationResult,\n DEFAULT_MIGRATION_OPTIONS,\n DocumentCounts,\n RestoreProgress,\n AggregatedDocument,\n RestoreResults,\n AttachmentUploadResult,\n} from './types';\nimport { validateStaticCourse, validateMigration } from './validation';\nimport { FileSystemAdapter, FileSystemError } from './FileSystemAdapter';\n\n// Fallback for environments without FileSystemAdapter (backward compatibility)\nlet nodeFS: any = null;\nlet nodePath: any = null;\ntry {\n if (typeof window === 'undefined' && typeof process !== 'undefined' && process.versions?.node) {\n nodeFS = eval('require')('fs');\n nodePath = eval('require')('path');\n nodeFS.promises = nodeFS.promises || eval('require')('fs').promises;\n }\n} catch {\n // fs not available, will use fetch\n}\n\nexport class StaticToCouchDBMigrator {\n private options: MigrationOptions;\n private progressCallback?: (progress: RestoreProgress) => void;\n private fs?: FileSystemAdapter;\n\n constructor(options: Partial<MigrationOptions> = {}, fileSystemAdapter?: FileSystemAdapter) {\n this.options = {\n ...DEFAULT_MIGRATION_OPTIONS,\n ...options,\n };\n this.fs = fileSystemAdapter;\n }\n\n /**\n * Set a progress callback to receive updates during migration\n */\n setProgressCallback(callback: (progress: RestoreProgress) => void): void {\n this.progressCallback = callback;\n }\n\n /**\n * Migrate a static course to CouchDB\n */\n async migrateCourse(staticPath: string, targetDB: PouchDB.Database): Promise<MigrationResult> {\n const startTime = Date.now();\n const result: MigrationResult = {\n success: false,\n documentsRestored: 0,\n attachmentsRestored: 0,\n designDocsRestored: 0,\n courseConfigRestored: 0,\n errors: [] as string[],\n warnings: [] as string[],\n migrationTime: 0,\n };\n\n try {\n logger.info(`Starting migration from ${staticPath} to CouchDB`);\n\n // Phase 1: Validate static course\n this.reportProgress('manifest', 0, 1, 'Validating static course...');\n const validation = await validateStaticCourse(staticPath, this.fs);\n if (!validation.valid) {\n result.errors.push(...validation.errors);\n throw new Error(`Static course validation failed: ${validation.errors.join(', ')}`);\n }\n result.warnings.push(...validation.warnings);\n\n // Phase 2: Load manifest\n this.reportProgress('manifest', 1, 1, 'Loading course manifest...');\n const manifest = await this.loadManifest(staticPath);\n logger.info(`Loaded manifest for course: ${manifest.courseId} (${manifest.courseName})`);\n\n // Phase 3: Restore design documents\n this.reportProgress(\n 'design_docs',\n 0,\n manifest.designDocs.length,\n 'Restoring design documents...'\n );\n const designDocResults = await this.restoreDesignDocuments(manifest.designDocs, targetDB);\n result.designDocsRestored = designDocResults.restored;\n result.errors.push(...designDocResults.errors);\n result.warnings.push(...designDocResults.warnings);\n\n // Phase 3.5: Restore CourseConfig\n this.reportProgress('course_config', 0, 1, 'Restoring CourseConfig document...');\n const courseConfigResults = await this.restoreCourseConfig(manifest, targetDB);\n result.courseConfigRestored = courseConfigResults.restored;\n result.errors.push(...courseConfigResults.errors);\n result.warnings.push(...courseConfigResults.warnings);\n this.reportProgress('course_config', 1, 1, 'CourseConfig document restored');\n\n // Phase 4: Aggregate and restore documents\n const expectedCounts = this.calculateExpectedCounts(manifest);\n this.reportProgress(\n 'documents',\n 0,\n manifest.documentCount,\n 'Aggregating documents from chunks...'\n );\n const documents = await this.aggregateDocuments(staticPath, manifest);\n\n // Filter out CourseConfig documents to prevent conflicts with Phase 3.5\n const filteredDocuments = documents.filter((doc) => doc._id !== 'CourseConfig');\n if (documents.length !== filteredDocuments.length) {\n result.warnings.push(\n `Filtered out ${documents.length - filteredDocuments.length} CourseConfig document(s) from chunks to prevent conflicts`\n );\n }\n\n this.reportProgress(\n 'documents',\n filteredDocuments.length,\n manifest.documentCount,\n 'Uploading documents to CouchDB...'\n );\n const docResults = await this.uploadDocuments(filteredDocuments, targetDB);\n result.documentsRestored = docResults.restored;\n result.errors.push(...docResults.errors);\n result.warnings.push(...docResults.warnings);\n\n // Phase 5: Upload attachments\n const docsWithAttachments = documents.filter(\n (doc) => doc._attachments && Object.keys(doc._attachments).length > 0\n );\n this.reportProgress('attachments', 0, docsWithAttachments.length, 'Uploading attachments...');\n const attachmentResults = await this.uploadAttachments(\n staticPath,\n docsWithAttachments,\n targetDB\n );\n result.attachmentsRestored = attachmentResults.restored;\n result.errors.push(...attachmentResults.errors);\n result.warnings.push(...attachmentResults.warnings);\n\n // Phase 6: Validation (if enabled)\n if (this.options.validateRoundTrip) {\n this.reportProgress('validation', 0, 1, 'Validating migration...');\n const validationResult = await validateMigration(targetDB, expectedCounts, manifest);\n if (!validationResult.valid) {\n result.warnings.push('Migration validation found issues');\n validationResult.issues.forEach((issue) => {\n if (issue.type === 'error') {\n result.errors.push(`Validation: ${issue.message}`);\n } else {\n result.warnings.push(`Validation: ${issue.message}`);\n }\n });\n }\n this.reportProgress('validation', 1, 1, 'Migration validation completed');\n }\n\n // Success!\n result.success = result.errors.length === 0;\n result.migrationTime = Date.now() - startTime;\n\n logger.info(`Migration completed in ${result.migrationTime}ms`);\n logger.info(`Documents restored: ${result.documentsRestored}`);\n logger.info(`Attachments restored: ${result.attachmentsRestored}`);\n logger.info(`Design docs restored: ${result.designDocsRestored}`);\n logger.info(`CourseConfig restored: ${result.courseConfigRestored}`);\n\n if (result.errors.length > 0) {\n logger.error(`Migration completed with ${result.errors.length} errors`);\n }\n if (result.warnings.length > 0) {\n logger.warn(`Migration completed with ${result.warnings.length} warnings`);\n }\n } catch (error) {\n result.success = false;\n result.migrationTime = Date.now() - startTime;\n const errorMessage = error instanceof Error ? error.message : String(error);\n result.errors.push(`Migration failed: ${errorMessage}`);\n logger.error('Migration failed:', error);\n\n // Cleanup on failure if requested\n if (this.options.cleanupOnFailure) {\n try {\n await this.cleanupFailedMigration(targetDB);\n } catch (cleanupError) {\n logger.error('Failed to cleanup after migration failure:', cleanupError);\n result.warnings.push('Failed to cleanup after migration failure');\n }\n }\n }\n\n return result;\n }\n\n /**\n * Load and parse the manifest file\n */\n private async loadManifest(staticPath: string): Promise<StaticCourseManifest> {\n try {\n let manifestContent: string;\n let manifestPath: string;\n\n if (this.fs) {\n // Use injected file system adapter (preferred)\n manifestPath = this.fs.joinPath(staticPath, 'manifest.json');\n manifestContent = await this.fs.readFile(manifestPath);\n } else {\n // Fallback to legacy behavior for backward compatibility\n manifestPath =\n nodeFS && nodePath\n ? nodePath.join(staticPath, 'manifest.json')\n : `${staticPath}/manifest.json`;\n\n if (nodeFS && this.isLocalPath(staticPath)) {\n // Node.js file system access\n manifestContent = await nodeFS.promises.readFile(manifestPath, 'utf8');\n } else {\n // Browser/fetch access\n const response = await fetch(manifestPath);\n if (!response.ok) {\n throw new Error(`Failed to fetch manifest: ${response.status} ${response.statusText}`);\n }\n manifestContent = await response.text();\n }\n }\n\n const manifest: StaticCourseManifest = JSON.parse(manifestContent);\n\n // Basic validation\n if (!manifest.version || !manifest.courseId || !manifest.chunks) {\n throw new Error('Invalid manifest structure');\n }\n\n return manifest;\n } catch (error) {\n const errorMessage =\n error instanceof FileSystemError\n ? error.message\n : `Failed to load manifest: ${error instanceof Error ? error.message : String(error)}`;\n throw new Error(errorMessage);\n }\n }\n\n /**\n * Restore design documents to CouchDB\n */\n private async restoreDesignDocuments(\n designDocs: DesignDocument[],\n db: PouchDB.Database\n ): Promise<{ restored: number; errors: string[]; warnings: string[] }> {\n const result = { restored: 0, errors: [] as string[], warnings: [] as string[] };\n\n for (let i = 0; i < designDocs.length; i++) {\n const designDoc = designDocs[i];\n this.reportProgress('design_docs', i, designDocs.length, `Restoring ${designDoc._id}...`);\n\n try {\n // Check if design document already exists\n let existingDoc;\n try {\n existingDoc = await db.get(designDoc._id);\n } catch {\n // Document doesn't exist, which is fine\n }\n\n // Prepare the document for insertion\n const docToInsert: any = {\n _id: designDoc._id,\n views: designDoc.views,\n };\n\n // If document exists, include the revision for update\n if (existingDoc) {\n docToInsert._rev = existingDoc._rev;\n logger.debug(`Updating existing design document: ${designDoc._id}`);\n } else {\n logger.debug(`Creating new design document: ${designDoc._id}`);\n }\n\n await db.put(docToInsert);\n result.restored++;\n } catch (error) {\n const errorMessage = `Failed to restore design document ${designDoc._id}: ${error instanceof Error ? error.message : String(error)}`;\n result.errors.push(errorMessage);\n logger.error(errorMessage);\n }\n }\n\n this.reportProgress(\n 'design_docs',\n designDocs.length,\n designDocs.length,\n `Restored ${result.restored} design documents`\n );\n return result;\n }\n\n /**\n * Aggregate documents from all chunks\n */\n private async aggregateDocuments(\n staticPath: string,\n manifest: StaticCourseManifest\n ): Promise<AggregatedDocument[]> {\n const allDocuments: AggregatedDocument[] = [];\n const documentMap = new Map<string, AggregatedDocument>(); // For deduplication\n\n for (let i = 0; i < manifest.chunks.length; i++) {\n const chunk = manifest.chunks[i];\n this.reportProgress(\n 'documents',\n allDocuments.length,\n manifest.documentCount,\n `Loading chunk ${chunk.id}...`\n );\n\n try {\n const documents = await this.loadChunk(staticPath, chunk);\n\n for (const doc of documents) {\n if (!doc._id) {\n logger.warn(`Document without _id found in chunk ${chunk.id}, skipping`);\n continue;\n }\n\n // Handle potential duplicates (shouldn't happen, but be safe)\n if (documentMap.has(doc._id)) {\n logger.warn(`Duplicate document ID found: ${doc._id}, using latest version`);\n }\n\n documentMap.set(doc._id, doc);\n }\n } catch (error) {\n throw new Error(\n `Failed to load chunk ${chunk.id}: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n }\n\n // Convert map to array\n allDocuments.push(...documentMap.values());\n\n logger.info(\n `Aggregated ${allDocuments.length} unique documents from ${manifest.chunks.length} chunks`\n );\n return allDocuments;\n }\n\n /**\n * Load documents from a single chunk file\n */\n private async loadChunk(staticPath: string, chunk: ChunkMetadata): Promise<any[]> {\n try {\n let chunkContent: string;\n let chunkPath: string;\n\n if (this.fs) {\n // Use injected file system adapter (preferred)\n chunkPath = this.fs.joinPath(staticPath, chunk.path);\n chunkContent = await this.fs.readFile(chunkPath);\n } else {\n // Fallback to legacy behavior for backward compatibility\n chunkPath =\n nodeFS && nodePath\n ? nodePath.join(staticPath, chunk.path)\n : `${staticPath}/${chunk.path}`;\n\n if (nodeFS && this.isLocalPath(staticPath)) {\n // Node.js file system access\n chunkContent = await nodeFS.promises.readFile(chunkPath, 'utf8');\n } else {\n // Browser/fetch access\n const response = await fetch(chunkPath);\n if (!response.ok) {\n throw new Error(`Failed to fetch chunk: ${response.status} ${response.statusText}`);\n }\n chunkContent = await response.text();\n }\n }\n\n const documents = JSON.parse(chunkContent);\n\n if (!Array.isArray(documents)) {\n throw new Error('Chunk file does not contain an array of documents');\n }\n\n return documents;\n } catch (error) {\n const errorMessage =\n error instanceof FileSystemError\n ? error.message\n : `Failed to load chunk: ${error instanceof Error ? error.message : String(error)}`;\n throw new Error(errorMessage);\n }\n }\n\n /**\n * Upload documents to CouchDB in batches\n */\n private async uploadDocuments(\n documents: AggregatedDocument[],\n db: PouchDB.Database\n ): Promise<{ restored: number; errors: string[]; warnings: string[] }> {\n const result = { restored: 0, errors: [] as string[], warnings: [] as string[] };\n const batchSize = this.options.chunkBatchSize;\n\n for (let i = 0; i < documents.length; i += batchSize) {\n const batch = documents.slice(i, i + batchSize);\n this.reportProgress(\n 'documents',\n i,\n documents.length,\n `Uploading batch ${Math.floor(i / batchSize) + 1}...`\n );\n\n try {\n // Prepare documents for bulk insert\n const docsToInsert = batch.map((doc) => {\n const cleanDoc = { ...doc };\n // Remove _rev if present (CouchDB will assign new revision)\n delete cleanDoc._rev;\n // Remove _attachments - these are uploaded separately in Phase 5\n delete cleanDoc._attachments;\n\n return cleanDoc;\n });\n\n const bulkResult = await db.bulkDocs(docsToInsert);\n\n // Process results\n for (let j = 0; j < bulkResult.length; j++) {\n const docResult = bulkResult[j];\n const originalDoc = batch[j];\n\n if ('error' in docResult) {\n const errorMessage = `Failed to upload document ${originalDoc._id}: ${docResult.error} - ${docResult.reason}`;\n result.errors.push(errorMessage);\n logger.error(errorMessage);\n } else {\n result.restored++;\n }\n }\n } catch (error) {\n let errorMessage: string;\n if (error instanceof Error) {\n errorMessage = `Failed to upload document batch starting at index ${i}: ${error.message}`;\n } else if (error && typeof error === 'object' && 'message' in error) {\n errorMessage = `Failed to upload document batch starting at index ${i}: ${(error as any).message}`;\n } else {\n errorMessage = `Failed to upload document batch starting at index ${i}: ${JSON.stringify(error)}`;\n }\n result.errors.push(errorMessage);\n logger.error(errorMessage);\n }\n }\n\n this.reportProgress(\n 'documents',\n documents.length,\n documents.length,\n `Uploaded ${result.restored} documents`\n );\n return result;\n }\n\n /**\n * Upload attachments from filesystem to CouchDB\n */\n private async uploadAttachments(\n staticPath: string,\n documents: AggregatedDocument[],\n db: PouchDB.Database\n ): Promise<{ restored: number; errors: string[]; warnings: string[] }> {\n const result = { restored: 0, errors: [] as string[], warnings: [] as string[] };\n let processedDocs = 0;\n\n for (const doc of documents) {\n this.reportProgress(\n 'attachments',\n processedDocs,\n documents.length,\n `Processing attachments for ${doc._id}...`\n );\n processedDocs++;\n\n if (!doc._attachments) {\n continue;\n }\n\n for (const [attachmentName, attachmentMeta] of Object.entries(doc._attachments)) {\n try {\n const uploadResult = await this.uploadSingleAttachment(\n staticPath,\n doc._id,\n attachmentName,\n attachmentMeta as any,\n db\n );\n\n if (uploadResult.success) {\n result.restored++;\n } else {\n result.errors.push(uploadResult.error || 'Unknown attachment upload error');\n }\n } catch (error) {\n const errorMessage = `Failed to upload attachment ${doc._id}/${attachmentName}: ${error instanceof Error ? error.message : String(error)}`;\n result.errors.push(errorMessage);\n logger.error(errorMessage);\n }\n }\n }\n\n this.reportProgress(\n 'attachments',\n documents.length,\n documents.length,\n `Uploaded ${result.restored} attachments`\n );\n return result;\n }\n\n /**\n * Upload a single attachment file\n */\n private async uploadSingleAttachment(\n staticPath: string,\n docId: string,\n attachmentName: string,\n attachmentMeta: any,\n db: PouchDB.Database\n ): Promise<AttachmentUploadResult> {\n const result: AttachmentUploadResult = {\n success: false,\n attachmentName,\n docId,\n };\n\n try {\n // Get the file path from the attachment metadata\n if (!attachmentMeta.path) {\n result.error = 'Attachment metadata missing file path';\n return result;\n }\n\n // Load the attachment data\n let attachmentData: ArrayBuffer | Buffer;\n let attachmentPath: string;\n\n if (this.fs) {\n // Use injected file system adapter (preferred)\n attachmentPath = this.fs.joinPath(staticPath, attachmentMeta.path);\n attachmentData = await this.fs.readBinary(attachmentPath);\n } else {\n // Fallback to legacy behavior for backward compatibility\n attachmentPath =\n nodeFS && nodePath\n ? nodePath.join(staticPath, attachmentMeta.path)\n : `${staticPath}/${attachmentMeta.path}`;\n\n if (nodeFS && this.isLocalPath(staticPath)) {\n // Node.js file system access\n attachmentData = await nodeFS.promises.readFile(attachmentPath);\n } else {\n // Browser/fetch access\n const response = await fetch(attachmentPath);\n if (!response.ok) {\n result.error = `Failed to fetch attachment: ${response.status} ${response.statusText}`;\n return result;\n }\n attachmentData = await response.arrayBuffer();\n }\n }\n\n // Get current document revision (needed for putAttachment)\n const doc = await db.get(docId);\n \n // Upload to CouchDB\n await db.putAttachment(\n docId,\n attachmentName,\n doc._rev,\n attachmentData as any, // PouchDB accepts both ArrayBuffer and Buffer\n attachmentMeta.content_type\n );\n\n result.success = true;\n } catch (error) {\n result.error = error instanceof Error ? error.message : String(error);\n }\n\n return result;\n }\n\n /**\n * Restore CourseConfig document from manifest\n */\n private async restoreCourseConfig(\n manifest: StaticCourseManifest,\n targetDB: PouchDB.Database\n ): Promise<RestoreResults> {\n const results: RestoreResults = {\n restored: 0,\n errors: [],\n warnings: [],\n };\n\n try {\n // Validate courseConfig exists\n if (!manifest.courseConfig) {\n results.warnings.push(\n 'No courseConfig found in manifest, skipping CourseConfig document creation'\n );\n return results;\n }\n\n // Create CourseConfig document\n const courseConfigDoc: { [key: string]: any; _id: string; _rev?: string } = {\n _id: 'CourseConfig',\n ...manifest.courseConfig,\n courseID: manifest.courseId,\n };\n delete courseConfigDoc._rev;\n\n // Upload to CouchDB\n await targetDB.put(courseConfigDoc);\n results.restored = 1;\n\n logger.info(`CourseConfig document created for course: ${manifest.courseId}`);\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : JSON.stringify(error);\n results.errors.push(`Failed to restore CourseConfig: ${errorMessage}`);\n logger.error('CourseConfig restoration failed:', error);\n }\n\n return results;\n }\n\n /**\n * Calculate expected document counts from manifest\n */\n private calculateExpectedCounts(manifest: StaticCourseManifest): DocumentCounts {\n const counts: DocumentCounts = {};\n\n // Count documents by type from chunks\n for (const chunk of manifest.chunks) {\n counts[chunk.docType] = (counts[chunk.docType] || 0) + chunk.documentCount;\n }\n\n // Count design documents\n if (manifest.designDocs.length > 0) {\n counts['_design'] = manifest.designDocs.length;\n }\n\n return counts;\n }\n\n /**\n * Clean up database after failed migration\n */\n private async cleanupFailedMigration(db: PouchDB.Database): Promise<void> {\n logger.info('Cleaning up failed migration...');\n\n try {\n // Get all documents and delete them\n const allDocs = await db.allDocs();\n const docsToDelete = allDocs.rows.map((row) => ({\n _id: row.id,\n _rev: row.value.rev,\n _deleted: true,\n }));\n\n if (docsToDelete.length > 0) {\n await db.bulkDocs(docsToDelete);\n logger.info(`Cleaned up ${docsToDelete.length} documents from failed migration`);\n }\n } catch (error) {\n logger.error('Failed to cleanup documents:', error);\n throw error;\n }\n }\n\n /**\n * Report progress to callback if available\n */\n private reportProgress(\n phase: RestoreProgress['phase'],\n current: number,\n total: number,\n message: string\n ): void {\n if (this.progressCallback) {\n this.progressCallback({\n phase,\n current,\n total,\n message,\n });\n }\n }\n\n /**\n * Check if a path is a local file path (vs URL)\n */\n private isLocalPath(path: string): boolean {\n return !path.startsWith('http://') && !path.startsWith('https://');\n }\n}\n","// packages/db/src/util/migrator/types.ts\n\nexport interface MigrationOptions {\n chunkBatchSize: number;\n validateRoundTrip: boolean;\n cleanupOnFailure: boolean;\n timeout: number; // milliseconds\n}\n\nexport interface MigrationResult {\n success: boolean;\n documentsRestored: number;\n attachmentsRestored: number;\n designDocsRestored: number;\n courseConfigRestored: number;\n errors: string[];\n warnings: string[];\n migrationTime: number;\n tempDatabaseName?: string;\n}\n\nexport interface ValidationResult {\n valid: boolean;\n documentCountMatch: boolean;\n attachmentIntegrity: boolean;\n viewFunctionality: boolean;\n issues: ValidationIssue[];\n}\n\nexport interface ValidationIssue {\n type: 'error' | 'warning';\n category: 'documents' | 'attachments' | 'views' | 'metadata' | 'course_config';\n message: string;\n details?: any;\n}\n\nexport interface DocumentCounts {\n [docType: string]: number;\n}\n\nexport interface RestoreProgress {\n phase: 'manifest' | 'design_docs' | 'course_config' | 'documents' | 'attachments' | 'validation';\n current: number;\n total: number;\n message: string;\n}\n\nexport interface StaticCourseValidation {\n valid: boolean;\n manifestExists: boolean;\n chunksExist: boolean;\n attachmentsExist: boolean;\n errors: string[];\n warnings: string[];\n courseId?: string;\n courseName?: string;\n}\n\nexport interface AggregatedDocument {\n _id: string;\n _attachments?: Record<string, any>;\n docType: string;\n [key: string]: any;\n}\n\nexport interface RestoreResults {\n restored: number;\n errors: string[];\n warnings: string[];\n}\n\nexport interface AttachmentUploadResult {\n success: boolean;\n attachmentName: string;\n docId: string;\n error?: string;\n}\n\nexport const DEFAULT_MIGRATION_OPTIONS: MigrationOptions = {\n chunkBatchSize: 100,\n validateRoundTrip: false,\n cleanupOnFailure: true,\n timeout: 300000, // 5 minutes\n};\n","// packages/db/src/util/migrator/validation.ts\n\nimport { logger } from '../logger';\nimport { StaticCourseValidation, ValidationResult, DocumentCounts, ValidationIssue } from './types';\nimport { StaticCourseManifest } from '../packer/types';\nimport { FileSystemAdapter, FileSystemError } from './FileSystemAdapter';\n\n// Check if we're in Node.js environment and fs is available\nlet nodeFS: any = null;\ntry {\n if (typeof window === 'undefined' && typeof process !== 'undefined' && process.versions?.node) {\n nodeFS = eval('require')('fs');\n nodeFS.promises = nodeFS.promises || eval('require')('fs').promises;\n }\n} catch {\n // fs not available\n}\n\n/**\n * Validate that a static course directory contains all required files\n */\nexport async function validateStaticCourse(\n staticPath: string,\n fs?: FileSystemAdapter\n): Promise<StaticCourseValidation> {\n const validation: StaticCourseValidation = {\n valid: true,\n manifestExists: false,\n chunksExist: false,\n attachmentsExist: false,\n errors: [],\n warnings: [],\n };\n\n try {\n // Check if path exists and is directory\n if (fs) {\n // Use injected file system adapter (preferred)\n const stats = await fs.stat(staticPath);\n if (!stats.isDirectory()) {\n validation.errors.push(`Path is not a directory: ${staticPath}`);\n validation.valid = false;\n return validation;\n }\n } else if (!nodeFS) {\n // Fallback validation failed\n validation.errors.push('File system access not available - validation skipped');\n validation.valid = false;\n return validation;\n } else {\n // Legacy fallback\n const stats = await nodeFS.promises.stat(staticPath);\n if (!stats.isDirectory()) {\n validation.errors.push(`Path is not a directory: ${staticPath}`);\n validation.valid = false;\n return validation;\n }\n }\n\n // Check for manifest.json\n let manifestPath: string = `${staticPath}/manifest.json`;\n try {\n if (fs) {\n // Use injected file system adapter (preferred)\n manifestPath = fs.joinPath(staticPath, 'manifest.json');\n if (await fs.exists(manifestPath)) {\n validation.manifestExists = true;\n\n // Parse manifest to get course info\n const manifestContent = await fs.readFile(manifestPath);\n const manifest: StaticCourseManifest = JSON.parse(manifestContent);\n validation.courseId = manifest.courseId;\n validation.courseName = manifest.courseName;\n\n // Validate manifest structure\n if (\n !manifest.version ||\n !manifest.courseId ||\n !manifest.chunks ||\n !Array.isArray(manifest.chunks)\n ) {\n validation.errors.push('Invalid manifest structure');\n validation.valid = false;\n }\n } else {\n validation.errors.push(`Manifest not found: ${manifestPath}`);\n validation.valid = false;\n }\n } else {\n // Legacy fallback\n manifestPath = `${staticPath}/manifest.json`;\n await nodeFS.promises.access(manifestPath);\n validation.manifestExists = true;\n\n // Parse manifest to get course info\n const manifestContent = await nodeFS.promises.readFile(manifestPath, 'utf8');\n const manifest: StaticCourseManifest = JSON.parse(manifestContent);\n validation.courseId = manifest.courseId;\n validation.courseName = manifest.courseName;\n\n // Validate manifest structure\n if (\n !manifest.version ||\n !manifest.courseId ||\n !manifest.chunks ||\n !Array.isArray(manifest.chunks)\n ) {\n validation.errors.push('Invalid manifest structure');\n validation.valid = false;\n }\n }\n } catch (error) {\n const errorMessage =\n error instanceof FileSystemError\n ? error.message\n : `Manifest not found or invalid: ${manifestPath}`;\n validation.errors.push(errorMessage);\n validation.valid = false;\n }\n\n // Check for chunks directory\n let chunksPath: string = `${staticPath}/chunks`;\n try {\n if (fs) {\n // Use injected file system adapter (preferred)\n chunksPath = fs.joinPath(staticPath, 'chunks');\n if (await fs.exists(chunksPath)) {\n const chunksStats = await fs.stat(chunksPath);\n if (chunksStats.isDirectory()) {\n validation.chunksExist = true;\n } else {\n validation.errors.push(`Chunks path is not a directory: ${chunksPath}`);\n validation.valid = false;\n }\n } else {\n validation.errors.push(`Chunks directory not found: ${chunksPath}`);\n validation.valid = false;\n }\n } else {\n // Legacy fallback\n chunksPath = `${staticPath}/chunks`;\n const chunksStats = await nodeFS.promises.stat(chunksPath);\n if (chunksStats.isDirectory()) {\n validation.chunksExist = true;\n } else {\n validation.errors.push(`Chunks path is not a directory: ${chunksPath}`);\n validation.valid = false;\n }\n }\n } catch (error) {\n const errorMessage =\n error instanceof FileSystemError\n ? error.message\n : `Chunks directory not found: ${chunksPath}`;\n validation.errors.push(errorMessage);\n validation.valid = false;\n }\n\n // Check for attachments directory (optional - course might not have attachments)\n let attachmentsPath: string;\n try {\n if (fs) {\n // Use injected file system adapter (preferred)\n attachmentsPath = fs.joinPath(staticPath, 'attachments');\n if (await fs.exists(attachmentsPath)) {\n const attachmentsStats = await fs.stat(attachmentsPath);\n if (attachmentsStats.isDirectory()) {\n validation.attachmentsExist = true;\n }\n } else {\n // Attachments directory is optional\n validation.warnings.push(\n `Attachments directory not found: ${attachmentsPath} (this is OK if course has no attachments)`\n );\n }\n } else {\n // Legacy fallback\n attachmentsPath = `${staticPath}/attachments`;\n const attachmentsStats = await nodeFS.promises.stat(attachmentsPath);\n if (attachmentsStats.isDirectory()) {\n validation.attachmentsExist = true;\n }\n }\n } catch (error) {\n // Attachments directory is optional\n attachmentsPath = attachmentsPath! || `${staticPath}/attachments`;\n const warningMessage =\n error instanceof FileSystemError\n ? error.message\n : `Attachments directory not found: ${attachmentsPath} (this is OK if course has no attachments)`;\n validation.warnings.push(warningMessage);\n }\n } catch (error) {\n validation.errors.push(\n `Failed to validate static course: ${error instanceof Error ? error.message : String(error)}`\n );\n validation.valid = false;\n }\n\n return validation;\n}\n\n/**\n * Validate the result of a migration by checking document counts and integrity\n */\nexport async function validateMigration(\n targetDB: PouchDB.Database,\n expectedCounts: DocumentCounts,\n manifest: StaticCourseManifest\n): Promise<ValidationResult> {\n const validation: ValidationResult = {\n valid: true,\n documentCountMatch: false,\n attachmentIntegrity: false,\n viewFunctionality: false,\n issues: [],\n };\n\n try {\n logger.info('Starting migration validation...');\n\n // 1. Validate document counts\n const actualCounts = await getActualDocumentCounts(targetDB);\n validation.documentCountMatch = compareDocumentCounts(\n expectedCounts,\n actualCounts,\n validation.issues\n );\n\n // 2. Validate CourseConfig document\n await validateCourseConfig(targetDB, manifest, validation.issues);\n\n // 3. Validate design documents and views\n validation.viewFunctionality = await validateViews(targetDB, manifest, validation.issues);\n\n // 4. Validate attachment integrity (sample check)\n validation.attachmentIntegrity = await validateAttachmentIntegrity(targetDB, validation.issues);\n\n // Overall validation result\n validation.valid =\n validation.documentCountMatch &&\n validation.viewFunctionality &&\n validation.attachmentIntegrity;\n\n logger.info(`Migration validation completed. Valid: ${validation.valid}`);\n if (validation.issues.length > 0) {\n logger.info(`Validation issues: ${validation.issues.length}`);\n validation.issues.forEach((issue) => {\n if (issue.type === 'error') {\n logger.error(`${issue.category}: ${issue.message}`);\n } else {\n logger.warn(`${issue.category}: ${issue.message}`);\n }\n });\n }\n } catch (error) {\n validation.valid = false;\n validation.issues.push({\n type: 'error',\n category: 'metadata',\n message: `Validation failed: ${error instanceof Error ? error.message : String(error)}`,\n });\n }\n\n return validation;\n}\n\n/**\n * Get actual document counts by type from the database\n */\nasync function getActualDocumentCounts(db: PouchDB.Database): Promise<DocumentCounts> {\n const counts: DocumentCounts = {};\n\n try {\n const allDocs = await db.allDocs({ include_docs: true });\n\n for (const row of allDocs.rows) {\n if (row.id.startsWith('_design/')) {\n // Count design documents separately\n counts['_design'] = (counts['_design'] || 0) + 1;\n continue;\n }\n\n const doc = row.doc as any;\n if (doc && doc.docType) {\n counts[doc.docType] = (counts[doc.docType] || 0) + 1;\n } else {\n // Documents without docType\n counts['unknown'] = (counts['unknown'] || 0) + 1;\n }\n }\n } catch (error) {\n logger.error('Failed to get actual document counts:', error);\n }\n\n return counts;\n}\n\n/**\n * Compare expected vs actual document counts\n */\nfunction compareDocumentCounts(\n expected: DocumentCounts,\n actual: DocumentCounts,\n issues: ValidationIssue[]\n): boolean {\n let countsMatch = true;\n\n // Check each expected document type\n for (const [docType, expectedCount] of Object.entries(expected)) {\n const actualCount = actual[docType] || 0;\n\n if (actualCount !== expectedCount) {\n countsMatch = false;\n issues.push({\n type: 'error',\n category: 'documents',\n message: `Document count mismatch for ${docType}: expected ${expectedCount}, got ${actualCount}`,\n });\n }\n }\n\n // Check for unexpected document types\n for (const [docType, actualCount] of Object.entries(actual)) {\n if (!expected[docType] && docType !== '_design') {\n issues.push({\n type: 'warning',\n category: 'documents',\n message: `Unexpected document type found: ${docType} (${actualCount} documents)`,\n });\n }\n }\n\n return countsMatch;\n}\n\n/**\n * Validate that CourseConfig document exists and is properly structured\n */\nasync function validateCourseConfig(\n db: PouchDB.Database,\n manifest: StaticCourseManifest,\n issues: ValidationIssue[]\n): Promise<void> {\n try {\n // Check if CourseConfig document exists\n const courseConfig = await db.get('CourseConfig');\n if (!courseConfig) {\n issues.push({\n type: 'error',\n category: 'course_config',\n message: 'CourseConfig document not found after migration',\n });\n return;\n }\n\n // Verify courseID field is present\n if (!(courseConfig as any).courseID) {\n issues.push({\n type: 'warning',\n category: 'course_config',\n message: 'CourseConfig document missing courseID field',\n });\n }\n\n // Verify courseID matches manifest\n if ((courseConfig as any).courseID !== manifest.courseId) {\n issues.push({\n type: 'warning',\n category: 'course_config',\n message: `CourseConfig courseID mismatch: expected ${manifest.courseId}, got ${(courseConfig as any).courseID}`,\n });\n }\n\n logger.debug('CourseConfig document validation passed');\n } catch (error) {\n if ((error as any).status === 404) {\n issues.push({\n type: 'error',\n category: 'course_config',\n message: 'CourseConfig document not found in database',\n });\n } else {\n issues.push({\n type: 'error',\n category: 'course_config',\n message: `Failed to validate CourseConfig document: ${error instanceof Error ? error.message : String(error)}`,\n });\n }\n }\n}\n\n/**\n * Validate that design documents and views are working correctly\n */\nasync function validateViews(\n db: PouchDB.Database,\n manifest: StaticCourseManifest,\n issues: ValidationIssue[]\n): Promise<boolean> {\n let viewsValid = true;\n\n try {\n // Check that design documents exist\n for (const designDoc of manifest.designDocs) {\n try {\n const doc = await db.get(designDoc._id);\n if (!doc) {\n viewsValid = false;\n issues.push({\n type: 'error',\n category: 'views',\n message: `Design document not found: ${designDoc._id}`,\n });\n continue;\n }\n\n // Test each view in the design document\n for (const viewName of Object.keys(designDoc.views)) {\n try {\n const viewPath = `${designDoc._id}/${viewName}`;\n await db.query(viewPath, { limit: 1 });\n // If we get here, the view is accessible (even if it returns no results)\n } catch (viewError) {\n viewsValid = false;\n issues.push({\n type: 'error',\n category: 'views',\n message: `View not accessible: ${designDoc._id}/${viewName} - ${viewError}`,\n });\n }\n }\n } catch (error) {\n viewsValid = false;\n issues.push({\n type: 'error',\n category: 'views',\n message: `Failed to validate design document ${designDoc._id}: ${error}`,\n });\n }\n }\n } catch (error) {\n viewsValid = false;\n issues.push({\n type: 'error',\n category: 'views',\n message: `View validation failed: ${error instanceof Error ? error.message : String(error)}`,\n });\n }\n\n return viewsValid;\n}\n\n/**\n * Validate attachment integrity by checking a sample of attachments\n */\nasync function validateAttachmentIntegrity(\n db: PouchDB.Database,\n issues: ValidationIssue[]\n): Promise<boolean> {\n let attachmentsValid = true;\n\n try {\n // Get documents with attachments (sample check)\n const allDocs = await db.allDocs({\n include_docs: true,\n limit: 10, // Sample first 10 documents for performance\n });\n\n let attachmentCount = 0;\n let validAttachments = 0;\n\n for (const row of allDocs.rows) {\n const doc = row.doc as any;\n if (doc && doc._attachments) {\n for (const [attachmentName, _attachmentMeta] of Object.entries(doc._attachments)) {\n attachmentCount++;\n\n try {\n // Try to access the attachment\n const attachment = await db.getAttachment(doc._id, attachmentName);\n if (attachment) {\n validAttachments++;\n }\n } catch (attachmentError) {\n attachmentsValid = false;\n issues.push({\n type: 'error',\n category: 'attachments',\n message: `Attachment not accessible: ${doc._id}/${attachmentName} - ${attachmentError}`,\n });\n }\n }\n }\n }\n\n if (attachmentCount === 0) {\n // No attachments found - this is OK\n issues.push({\n type: 'warning',\n category: 'attachments',\n message: 'No attachments found in sampled documents',\n });\n } else {\n logger.info(`Validated ${validAttachments}/${attachmentCount} sampled attachments`);\n }\n } catch (error) {\n attachmentsValid = false;\n issues.push({\n type: 'error',\n category: 'attachments',\n message: `Attachment validation failed: ${error instanceof Error ? error.message : String(error)}`,\n });\n }\n\n return attachmentsValid;\n}\n","// packages/db/src/util/migrator/FileSystemAdapter.ts\n\n/**\n * Abstraction for file system operations needed by the migrator.\n * This allows dependency injection of file system operations,\n * avoiding bundling issues with Node.js fs module.\n */\nexport interface FileSystemAdapter {\n /**\n * Read a text file and return its contents as a string\n */\n readFile(filePath: string): Promise<string>;\n\n /**\n * Read a binary file and return its contents as a Buffer\n */\n readBinary(filePath: string): Promise<Buffer>;\n\n /**\n * Check if a file or directory exists\n */\n exists(filePath: string): Promise<boolean>;\n\n /**\n * Get file/directory statistics\n */\n stat(filePath: string): Promise<FileStats>;\n\n /**\n * Write text data to a file\n */\n writeFile(filePath: string, data: string | Buffer): Promise<void>;\n\n /**\n * Write JSON data to a file with formatting\n */\n writeJson(filePath: string, data: any, options?: { spaces?: number }): Promise<void>;\n\n /**\n * Ensure a directory exists, creating it and parent directories if needed\n */\n ensureDir(dirPath: string): Promise<void>;\n\n /**\n * Join path segments into a complete path\n */\n joinPath(...segments: string[]): string;\n\n /**\n * Get the directory name of a path\n */\n dirname(filePath: string): string;\n\n /**\n * Check if a path is absolute\n */\n isAbsolute(filePath: string): boolean;\n}\n\nexport interface FileStats {\n isDirectory(): boolean;\n isFile(): boolean;\n size: number;\n}\n\n/**\n * Error thrown when file system operations fail\n */\nexport class FileSystemError extends Error {\n constructor(\n message: string,\n public readonly operation: string,\n public readonly filePath: string,\n public readonly cause?: Error\n ) {\n super(message);\n this.name = 'FileSystemError';\n }\n}","import { WeightedCard } from '@db/core/navigators';\n\n/**\n * Represents a batch of content fetched from a single StudyContentSource.\n */\nexport interface SourceBatch {\n sourceIndex: number;\n weighted: WeightedCard[];\n}\n\n/**\n * Strategy interface for mixing content from multiple sources into a unified\n * set of weighted candidates.\n *\n * Different implementations can provide different balancing strategies:\n * - QuotaRoundRobinMixer: Equal representation per source\n * - MinMaxNormalizingMixer: Score normalization then global sort\n * - PercentileBucketMixer: Bucketed round-robin\n * etc.\n */\nexport interface SourceMixer {\n /**\n * Mix weighted cards from multiple sources into a unified, ordered list.\n *\n * @param batches - Content batches from each source\n * @param limit - Target number of cards to return\n * @returns Mixed and ordered weighted cards\n */\n mix(batches: SourceBatch[], limit: number): WeightedCard[];\n}\n\n/**\n * Simple quota-based mixer: allocates equal representation to each source,\n * taking the top-N cards by score from each.\n *\n * Guarantees balanced representation across sources regardless of absolute\n * score differences. A low-scoring source gets the same quota as a high-scoring\n * source.\n *\n * This is the KISS approach - simple, predictable, and fair in terms of\n * source representation (though not necessarily optimal in terms of absolute\n * card quality).\n */\nexport class QuotaRoundRobinMixer implements SourceMixer {\n mix(batches: SourceBatch[], limit: number): WeightedCard[] {\n if (batches.length === 0) {\n return [];\n }\n\n const quotaPerSource = Math.ceil(limit / batches.length);\n const mixed: WeightedCard[] = [];\n\n for (const batch of batches) {\n // Sort this source's cards by score descending\n const sortedBatch = [...batch.weighted].sort((a, b) => b.score - a.score);\n\n // Take top quotaPerSource from this source\n const topFromSource = sortedBatch.slice(0, quotaPerSource);\n mixed.push(...topFromSource);\n }\n\n // Sort the mixed result by score descending and return up to limit\n return mixed.sort((a, b) => b.score - a.score).slice(0, limit);\n }\n}\n","export * from './SessionController';\nexport * from './SourceMixer';\nexport * from './SpacedRepetition';\nexport * from './TagFilteredContentSource';\n"],"mappings":"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","names":["DocType","log","moment","Navigators","NavigatorRole","toCourseElo","toCourseElo","moment","blankCourseElo","toCourseElo","filterAllDocsByPrefix","getCourseDB","moment","init_classroomDB","getStartAndEndKeys","REVIEW_TIME_FORMAT","init_adminDB","init_classroomDB","getStartAndEndKeys","fetch","Status","log","fetch","moment","process","getCourseDB","filterAllDocsByPrefix","getStartAndEndKeys","REVIEW_TIME_FORMAT","init_adminDB","init_classroomDB","Status","moment","uuid","log","record","init_adminDB","init_classroomDB","path","Status","init_courseDB","elo","init_courseDB","CouchDBSyncStrategy","CouchDataLayerProvider","StaticDataLayerProvider","hasActiveFilter","init_classroomDB","init_courseDB","init_courseDB","Status","moment","moment","moment","toCourseElo","toCourseElo","getCourseDB","nodeFS","fs","nodeFS","path"]}
|