import { T as TagStub, a as Tag, S as SkuilderCourseData, Q as QualifiedCardID } from '../../types-legacy-4tlwHnXo.js'; import { Moment } from 'moment'; import { A as AdminDBInterface, g as AssignedContent, h as StudyContentSource, i as StudentClassroomDBInterface, U as UserDBInterface, G as GeneratorResult, T as TeacherClassroomDBInterface, b as CoursesDBInterface, C as CourseDBInterface, d as CourseInfo, D as DataLayerResult, e as ContentNavigationStrategyData, f as ContentNavigator, R as ReplanHints, S as StudySessionItem, j as ScheduledCard } from '../../contentSource-C-0t0y0V.js'; export { q as ContentSourceID, k as StudySessionFailedItem, l as StudySessionFailedNewItem, m as StudySessionFailedReviewItem, n as StudySessionNewItem, o as StudySessionReviewItem, r as getStudySource, p as isReview } from '../../contentSource-C-0t0y0V.js'; import * as _vue_skuilder_common from '@vue-skuilder/common'; import { ClassroomConfig, DataShape, CourseElo, CourseConfig as CourseConfig$1 } from '@vue-skuilder/common'; import { S as SyncStrategy, A as AccountCreationResult, a as AuthenticationResult } from '../../SyncStrategy-CyATpyLQ.js'; type NamespacedDatashape = string; interface DataShape55 { name: NamespacedDatashape; questionTypes: PouchDB.Core.DocumentId[]; serializedZodSchema?: string; } type NamespacedQuestion = string; interface QuestionType55 { name: NamespacedQuestion; viewList: string[]; dataShapeList: string[]; } interface CourseOrchestrationConfig { /** * Random seed used to determine user cohort assignments. * Changing this value re-shuffles all users into new cohorts. */ salt: string; } /** * metadata about a defined course * * Note: `courseID` is generated server-side. It is not present on * new courses at the time of writing, client-side, but always * present (!) when a CourseConfig is retrieved from the database */ interface CourseConfig { courseID?: string; name: string; description: string; public: boolean; deleted: boolean; creator: string; admins: string[]; moderators: string[]; dataShapes: DataShape55[]; questionTypes: QuestionType55[]; disambiguator?: string; orchestration?: CourseOrchestrationConfig; /** * Opt-in client-side replication of the course database. * * When enabled, the client replicates the course DB to a local PouchDB * instance on first visit (full one-shot sync) and performs incremental * sync on subsequent visits. Pipeline scoring, tag hydration, and card * lookup then run against the local replica — eliminating network round * trips from the study-session hot path. * * **Read/write split:** The local DB is a read-only snapshot. All writes * (card ELO updates, tag mutations, etc.) continue to target the remote * CouchDB. This avoids propagating per-interaction ELO noise to every * syncing client — the remote DB aggregates writes from all users, and * each client's local snapshot is refreshed on the next page load. * * Defaults to `undefined` (disabled). Courses with small, relatively * static content databases (e.g. < 50 MB) are good candidates. */ localSync?: { enabled: boolean; }; } declare class AdminDB implements AdminDBInterface { private usersDB; constructor(); getUsers(): Promise[]>; getCourses(): Promise; removeCourse(id: string): Promise<{ ok: boolean; id: string; rev: string; }>; getClassrooms(): Promise<{ _id: string; students: string[]; teachers: string[]; name: string; birthYear?: number; classMeetingSchedule: string; peerAssist: boolean; joinCode: string; }[]>; } declare const CLASSROOM_CONFIG = "ClassroomConfig"; type ClassroomMessage = object; declare abstract class ClassroomDBBase { _id: string; protected _db: PouchDB.Database; protected _cfg: ClassroomConfig; protected _initComplete: boolean; protected readonly _content_prefix: string; protected get _content_searchkeys(): { startkey: string; endkey: string; }; protected abstract init(): Promise; getAssignedContent(): Promise; protected getContentId(content: AssignedContent): string; get ready(): boolean; getConfig(): ClassroomConfig; } declare class StudentClassroomDB extends ClassroomDBBase implements StudyContentSource, StudentClassroomDBInterface { private userMessages; private _user; private constructor(); init(): Promise; static factory(classID: string, user: UserDBInterface): Promise; setChangeFcn(f: (value: unknown) => object): void; /** * Get cards with suitability scores for presentation. * * Gathers new cards from assigned content (courses, tags, cards) and * pending reviews scheduled for this classroom. Assigns score=1.0 to all. * * @param limit - Maximum number of cards to return * @returns Cards sorted by score descending (all scores = 1.0) */ getWeightedCards(limit: number): Promise; } /** * Interface for managing a classroom. */ declare class TeacherClassroomDB extends ClassroomDBBase implements TeacherClassroomDBInterface { private _stuDb; private constructor(); init(): Promise; static factory(classID: string): Promise; removeContent(content: AssignedContent): Promise; assignContent(content: AssignedContent): Promise; } declare const ClassroomLookupDB: () => PouchDB.Database; declare function getClassroomDB(classID: string, version: 'student' | 'teacher'): PouchDB.Database; declare function getClassroomConfig(classID: string): Promise; /** * * @param courseID id of the course (quilt) being added to * @param codeCourse * @param shape * @param data the datashape data - data required for this shape * @param author * @param uploads optional additional media uploads: img0, img1, ..., aud0, aud1,... * @returns */ declare function addNote55(courseID: string, codeCourse: string, shape: DataShape, data: unknown, author: string, tags: string[], uploads?: { [x: string]: PouchDB.Core.FullAttachment; }, elo?: CourseElo): Promise; declare function getCredentialledCourseConfig(courseID: string): Promise; /** Assciates a tag with a card. NB: DB stores tags as separate documents, with a list of card IDs. Consider renaming to `addCardToTag` to reflect this. NB: tags are created if they don't already exist @param updateELO whether to update the ELO of the card with the new tag. Default true. @package */ declare function addTagToCard(courseID: string, cardID: string, tagID: string, author: string, updateELO?: boolean): Promise; declare function getTagID(tagName: string): string; declare class CoursesDB implements CoursesDBInterface { _courseIDs: string[] | undefined; constructor(courseIDs?: string[]); getCourseList(): Promise; getCourseConfig(courseId: string): Promise; disambiguateCourse(courseId: string, disambiguator: string): Promise; } declare class CourseDB implements CourseDBInterface { /** * Primary database handle used for all **read** operations (queries, gets). * * When local sync is active, this points to the local PouchDB replica for * fast, network-free reads. Otherwise it points to the remote CouchDB. */ private db; /** * Remote database handle used for all **write** operations. * * Always points to the remote CouchDB so that writes (ELO updates, tag * mutations, admin operations) aggregate on the server. The local replica * is a read-only snapshot that refreshes on the next page load. * * When local sync is NOT active, this is the same instance as `this.db`. */ private remoteDB; private id; private _getCurrentUser; private updateQueue; /** * @param id - Course ID * @param userLookup - Async function returning the current user DB * @param localDB - Optional local PouchDB replica for reads. When provided, * `this.db` uses the local replica and `this.remoteDB` stays remote. * The UpdateQueue reads from remote and writes to remote (local `_rev` * values may be stale, so read-modify-write cycles must go through * the remote DB to avoid conflicts). */ constructor(id: string, userLookup: () => Promise, localDB?: PouchDB.Database); getCourseID(): string; getCourseInfo(): Promise; getInexperiencedCards(limit?: number): Promise<{ courseId: string; cardId: any; count: any; elo: any; }[]>; getCardsByEloLimits(options?: { low: number; high: number; limit: number; page: number; }): Promise; getCardEloData(id: string[]): Promise; /** * Returns the lowest and highest `global` ELO ratings in the course */ getELOBounds(): Promise<{ low: any; high: any; }>; removeCard(id: string): Promise; getCardDisplayableDataIDs(id: string[]): Promise<{ [card: string]: string[]; }>; getCardsByELO(elo: number, cardLimit?: number): Promise<{ courseID: string; cardID: any; elo: any; }[]>; getCourseConfig(): Promise; updateCourseConfig(cfg: CourseConfig$1): Promise; updateCardElo(cardId: string, elo: CourseElo): Promise; getAppliedTags(cardId: string): Promise>; getAppliedTagsBatch(cardIds: string[]): Promise>; getAllCardIds(): Promise; addTagToCard(cardId: string, tagId: string, updateELO?: boolean): Promise; removeTagFromCard(cardId: string, tagId: string): Promise; createTag(name: string, author: string): Promise; getTag(tagId: string): Promise>; updateTag(tag: Tag): Promise; getCourseTagStubs(): Promise>; addNote(codeCourse: string, shape: DataShape, data: unknown, author: string, tags: string[], uploads?: { [key: string]: PouchDB.Core.FullAttachment; }, elo?: CourseElo): Promise; getCourseDoc(id: string, options?: PouchDB.Core.GetOptions): Promise>; getCourseDocs(ids: string[], options?: PouchDB.Core.AllDocsOptions): Promise>; getNavigationStrategy(id: string): Promise; getAllNavigationStrategies(): Promise; addNavigationStrategy(data: ContentNavigationStrategyData): Promise; updateNavigationStrategy(id: string, data: ContentNavigationStrategyData): Promise; /** * Creates an instantiated navigator for this course. * * Handles multiple generators by wrapping them in CompositeGenerator. * This is the preferred method for getting a ready-to-use navigator. * * @param user - User database interface * @returns Instantiated ContentNavigator ready for use */ createNavigator(user: UserDBInterface): Promise; /** * Get cards with suitability scores for presentation. * * This is the PRIMARY API for content sources going forward. Delegates to the * course's configured NavigationStrategy to get scored candidates. * * @param limit - Maximum number of cards to return * @returns Cards sorted by score descending */ private _pendingHints; /** * Session-scoped cache of the broad ELO-neighbor pool used by * getCardsCenteredAtELO. The `elo` view query re-indexes on first touch per * call (PouchDB 9 ignores `stale`), so without this each plan/replan pays * ~1.5-2s. The pool is fetched once and re-ranked against the live (roaming) * ELO in memory on subsequent calls. */ private _eloPoolCache; private readonly _eloPoolTtlMs; /** * Cached assembled navigator (Pipeline). createNavigator() reads strategy * docs and builds a fresh Pipeline every call — whose internal `_tagCache` * and `_cachedOrchestration` are designed to make replans cheap but never * survive, because the instance is discarded each run. Caching it lets those * caches persist across plan/replan within a session (SessionController holds * one CourseDB instance for the session's lifetime). Rebuilt on user change, * TTL expiry, or explicit invalidation after a strategy-doc write. */ private _cachedNavigator; private readonly _navigatorTtlMs; setEphemeralHints(hints: ReplanHints): void; getWeightedCards(limit: number): Promise; /** * Return the assembled navigator, reusing the cached instance when possible. * Reuse preserves the Pipeline's per-session caches (tags, orchestration * context) across replans, which is the dominant per-replan cost once the * ELO-pool cost is removed. Rebuilds on user change or TTL expiry. */ private _getCachedNavigator; /** * Drop the cached navigator so the next getWeightedCards() rebuilds it. * Call after mutating this course's navigation strategy documents. */ invalidateNavigatorCache(): void; getCardsCenteredAtELO(options?: { limit: number; elo: 'user' | 'random' | number; }, filter?: (a: QualifiedCardID) => boolean): Promise; searchCards(query: string): Promise; find(request: PouchDB.Find.FindRequest): Promise>; } /** * Returns a list of registered datashapes for the specified * course. * @param courseID The ID of the course */ declare function getCourseDataShapes(courseID: string): Promise<_vue_skuilder_common.DataShape55[]>; declare function getCredentialledDataShapes(courseID: string): Promise<_vue_skuilder_common.DataShape55[]>; declare function getCourseQuestionTypes(courseID: string): Promise<_vue_skuilder_common.QuestionType55[]>; declare function getCourseTagStubs(courseID: string): Promise>; declare function deleteTag(courseID: string, tagName: string): Promise; declare function createTag(courseID: string, tagName: string, author: string): Promise; declare function updateTag(tag: Tag): Promise; declare function getTag(courseID: string, tagName: string): Promise; declare function removeTagFromCard(courseID: string, cardID: string, tagID: string): Promise; /** * Returns an array of ancestor tag IDs, where: * return[0] = parent, * return[1] = grandparent, * return[2] = great grandparent, * etc. * * If ret is empty, the tag itself is a root */ declare function getAncestorTagIDs(courseID: string, tagID: string): string[]; declare function getChildTagStubs(courseID: string, tagID: string): Promise>; declare function getAppliedTags(id_course: string, id_card: string): Promise>; declare function updateCardElo(courseID: string, cardID: string, elo: CourseElo): Promise; declare function updateCredentialledCourseConfig(courseID: string, config: CourseConfig$1): Promise; /** * Sync state for a single course database. */ type CourseSyncState = 'not-started' | 'checking-config' | 'syncing' | 'warming-views' | 'ready' | 'disabled' | 'error'; /** * Detailed sync status for observability. */ interface CourseSyncStatus { state: CourseSyncState; /** Number of documents replicated (set after sync completes) */ docsReplicated?: number; /** Total replication time in ms */ syncTimeMs?: number; /** View warming time in ms */ viewWarmTimeMs?: number; /** Error message if state is 'error' */ error?: string; } /** * Service that manages local PouchDB replicas of course databases. * * Usage: * ```typescript * const syncService = CourseSyncService.getInstance(); * * // Trigger sync (typically on app load / pre-session) * await syncService.ensureSynced(courseId); * * // Get local DB for reads (returns null if sync not ready/enabled) * const localDB = syncService.getLocalDB(courseId); * ``` * * The service is a singleton — course sync state is shared across the app. */ /** * Tuning knobs for one-shot remote→local replication. * Defaults are chosen for one-shot snapshot replication of bounded course * corpora (text/JSON docs, no large attachments). PouchDB's built-in * defaults (100/10) target live continuous sync — too conservative for * cold-load UX on a course of a few thousand docs. * * Apps with smaller/larger corpora can override via * `initializeDataLayer({ options: { courseSync: { replication: {...} } } })`. */ interface ReplicationOptions { /** Docs per `_bulk_get` HTTP request. Higher = fewer roundtrips. */ batchSize?: number; /** Concurrent batches in flight. */ batchesLimit?: number; } declare class CourseSyncService { private static instance; private entries; private replicationOptions; private constructor(); static getInstance(): CourseSyncService; /** * Apply replication tuning. Typically called once from `initializeDataLayer`. * Partial overrides merge with defaults. */ configure(opts: { replication?: ReplicationOptions; }): void; /** * Reset the singleton (for testing). */ static resetInstance(): void; /** * Ensure a course's local replica is synced. * * On first call for a course: * 1. Fetches CourseConfig from remote to check localSync.enabled * 2. If enabled, performs one-shot replication remote → local * 3. Pre-warms PouchDB view indices (elo, getTags) * * On subsequent calls: returns immediately if already synced, or awaits * the in-flight sync if one is in progress. * * Safe to call multiple times — concurrent calls coalesce to one sync. * * @param courseId - The course to sync * @param forceEnabled - Skip the CourseConfig check and sync regardless. * Useful when the caller already knows local sync is desired (e.g., * LettersPractice hardcodes this). */ ensureSynced(courseId: string, forceEnabled?: boolean): Promise; /** * Get the local PouchDB for a course, or null if not available. * * Returns null when: * - Local sync is not enabled for this course * - Sync has not been triggered yet * - Sync is still in progress * - Sync failed */ getLocalDB(courseId: string): PouchDB.Database | null; /** * Check whether a course has a ready local replica. */ isReady(courseId: string): boolean; /** * Get detailed sync status for a course. */ getStatus(courseId: string): CourseSyncStatus; private performSync; /** * Check CourseConfig.localSync.enabled on the remote DB. */ private checkLocalSyncEnabled; /** * One-shot replication from remote to local. */ private replicate; /** * Pre-warm PouchDB view indices by running a minimal query against each * design doc. This forces PouchDB to build the MapReduce index now * (during a loading phase) rather than on first pipeline query. */ private warmViewIndices; /** * Check whether the local replica's `db-epoch` doc matches the remote. * * The seed script (and optionally upload-cards) writes a `db-epoch` * document with a numeric timestamp. If the remote epoch differs from * the local copy, the remote DB was recreated (e.g., `yarn db:seed`) * and the local PouchDB is stale. * * Returns `true` if stale (epoch mismatch or remote has epoch but local * doesn't). Returns `false` (not stale) if epochs match, or if the * remote doesn't have an epoch doc at all (backwards compat). */ private isLocalEpochStale; /** * Get a remote PouchDB handle for a course. */ private getRemoteDB; /** * Local DB naming convention. */ private localDBName; } /** * Sync strategy that implements full CouchDB remote synchronization * Handles account creation, authentication, and live sync with remote CouchDB server */ declare class CouchDBSyncStrategy implements SyncStrategy { private syncHandle?; setupRemoteDB(username: string): PouchDB.Database; getWriteDB(username: string): PouchDB.Database; startSync(localDB: PouchDB.Database, remoteDB: PouchDB.Database): void; stopSync?(): void; canCreateAccount(): boolean; canAuthenticate(): boolean; createAccount(username: string, password: string): Promise; authenticate(username: string, password: string): Promise; logout(): Promise; getCurrentUsername(): Promise; /** * Migrate data from funnel account to real account */ private migrateFunnelData; /** * Get remote CouchDB root database for authentication operations */ private getRemoteCouchRootDB; /** * Get remote user database for a specific user */ private getUserDB; } declare const localUserDB: PouchDB.Database; declare function hexEncode(str: string): string; /** * Creates PouchDB configuration with appropriate authentication method * - Uses HTTP Basic Auth when credentials are available (Node.js/MCP) * - Falls back to cookie auth for browser environments */ declare function createPouchDBConfig(): PouchDB.Configuration.RemoteDatabaseConfiguration; declare function getCourseDB(courseID: string): PouchDB.Database; declare function getLatestVersion(): Promise; /** * Checks the remote couchdb to see if a given username is available * @param username The username to be checked */ declare function usernameIsAvailable(username: string): Promise; declare function updateGuestAccountExpirationDate(guestDB: PouchDB.Database): void; declare function getCourseDocs(courseID: string, docIDs: string[], options?: PouchDB.Core.AllDocsOptions): Promise>; declare function getCourseDoc(courseID: string, docID: PouchDB.Core.DocumentId, options?: PouchDB.Core.GetOptions): Promise; /** * Returns *all* cards from the parameter courses, in * 'qualified' card format ("courseid-cardid") * * @param courseIDs A list of all course_ids to get cards from */ declare function getRandomCards(courseIDs: string[]): Promise; declare const REVIEW_TIME_FORMAT: string; declare function getCouchUserDB(username: string): PouchDB.Database; declare function scheduleCardReview(review: { user: string; course_id: string; card_id: PouchDB.Core.DocumentId; time: Moment; scheduledFor: ScheduledCard['scheduledFor']; schedulingAgentId: ScheduledCard['schedulingAgentId']; }): void; declare function filterAllDocsByPrefix(db: PouchDB.Database, prefix: string, opts?: PouchDB.Core.AllDocsOptions): Promise>; declare function getStartAndEndKeys(key: string): { startkey: string; endkey: string; }; export { AdminDB, CLASSROOM_CONFIG, ClassroomLookupDB, type ClassroomMessage, CouchDBSyncStrategy, CourseDB, CourseSyncService, type CourseSyncState, type CourseSyncStatus, CoursesDB, REVIEW_TIME_FORMAT, type ReplicationOptions, StudentClassroomDB, StudyContentSource, StudySessionItem, TeacherClassroomDB, addNote55, addTagToCard, createPouchDBConfig, createTag, deleteTag, filterAllDocsByPrefix, getAncestorTagIDs, getAppliedTags, getChildTagStubs, getClassroomConfig, getClassroomDB, getCouchUserDB, getCourseDB, getCourseDataShapes, getCourseDoc, getCourseDocs, getCourseQuestionTypes, getCourseTagStubs, getCredentialledCourseConfig, getCredentialledDataShapes, getLatestVersion, getRandomCards, getStartAndEndKeys, getTag, getTagID, hexEncode, localUserDB, removeTagFromCard, scheduleCardReview, updateCardElo, updateCredentialledCourseConfig, updateGuestAccountExpirationDate, updateTag, usernameIsAvailable };