@vue-skuilder/db 0.1.31-a → 0.1.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{contentSource-BmnmvH8C.d.ts → contentSource-Bdwkvqa8.d.ts} +35 -4
- package/dist/{contentSource-DfBbaLA-.d.cts → contentSource-DF1nUbPQ.d.cts} +35 -4
- package/dist/core/index.d.cts +48 -3
- package/dist/core/index.d.ts +48 -3
- package/dist/core/index.js +587 -56
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.mjs +586 -56
- package/dist/core/index.mjs.map +1 -1
- package/dist/{dataLayerProvider-BeRXVMs5.d.cts → dataLayerProvider-BKmVoyJR.d.ts} +20 -1
- package/dist/{dataLayerProvider-CG9GfaAY.d.ts → dataLayerProvider-BQdfJuBN.d.cts} +20 -1
- package/dist/impl/couch/index.d.cts +156 -4
- package/dist/impl/couch/index.d.ts +156 -4
- package/dist/impl/couch/index.js +805 -47
- package/dist/impl/couch/index.js.map +1 -1
- package/dist/impl/couch/index.mjs +804 -47
- package/dist/impl/couch/index.mjs.map +1 -1
- package/dist/impl/static/index.d.cts +3 -2
- package/dist/impl/static/index.d.ts +3 -2
- package/dist/impl/static/index.js +542 -37
- package/dist/impl/static/index.js.map +1 -1
- package/dist/impl/static/index.mjs +542 -37
- package/dist/impl/static/index.mjs.map +1 -1
- package/dist/index.d.cts +64 -3
- package/dist/index.d.ts +64 -3
- package/dist/index.js +1040 -90
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1030 -81
- package/dist/index.mjs.map +1 -1
- package/docs/navigators-architecture.md +64 -5
- package/package.json +3 -3
- package/src/core/interfaces/contentSource.ts +6 -0
- package/src/core/interfaces/courseDB.ts +6 -0
- package/src/core/interfaces/dataLayerProvider.ts +20 -0
- package/src/core/navigators/Pipeline.ts +414 -9
- package/src/core/navigators/PipelineAssembler.ts +23 -18
- package/src/core/navigators/PipelineDebugger.ts +115 -1
- package/src/core/navigators/filters/hierarchyDefinition.ts +78 -8
- package/src/core/navigators/generators/prescribed.ts +95 -0
- package/src/core/navigators/index.ts +55 -10
- package/src/impl/common/BaseUserDB.ts +4 -1
- package/src/impl/couch/CourseSyncService.ts +356 -0
- package/src/impl/couch/PouchDataLayerProvider.ts +21 -1
- package/src/impl/couch/courseDB.ts +60 -13
- package/src/impl/couch/index.ts +1 -0
- package/src/impl/static/courseDB.ts +5 -0
- package/src/study/ItemQueue.ts +42 -0
- package/src/study/SessionController.ts +195 -22
- package/src/study/SpacedRepetition.ts +7 -2
- package/tests/core/navigators/Pipeline.test.ts +1 -1
- package/tests/core/navigators/PipelineAssembler.test.ts +15 -14
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { U as UserDBInterface, a as UserDBReader, C as CourseDBInterface, b as CoursesDBInterface, c as ClassroomDBInterface, A as AdminDBInterface } from './contentSource-
|
|
1
|
+
import { U as UserDBInterface, a as UserDBReader, C as CourseDBInterface, b as CoursesDBInterface, c as ClassroomDBInterface, A as AdminDBInterface } from './contentSource-Bdwkvqa8.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Main factory interface for data access
|
|
@@ -43,6 +43,25 @@ interface DataLayerProvider {
|
|
|
43
43
|
* Check if this data layer is read-only
|
|
44
44
|
*/
|
|
45
45
|
isReadOnly(): boolean;
|
|
46
|
+
/**
|
|
47
|
+
* Trigger local replication of a course database.
|
|
48
|
+
*
|
|
49
|
+
* When a course opts in via `CourseConfig.localSync.enabled`, this method
|
|
50
|
+
* replicates the remote course DB to a local PouchDB instance. Subsequent
|
|
51
|
+
* `getCourseDB()` calls for that course will return a CourseDB that reads
|
|
52
|
+
* from the local replica (fast, no network) and writes to the remote
|
|
53
|
+
* (ELO updates, admin ops).
|
|
54
|
+
*
|
|
55
|
+
* Safe to call multiple times — concurrent calls coalesce. Returns when
|
|
56
|
+
* sync is complete (or immediately if already synced / disabled).
|
|
57
|
+
*
|
|
58
|
+
* Implementations that don't support local sync may no-op.
|
|
59
|
+
*
|
|
60
|
+
* @param courseId - The course to sync locally
|
|
61
|
+
* @param forceEnabled - Skip CourseConfig check and sync regardless.
|
|
62
|
+
* Use when the caller already knows local sync is desired.
|
|
63
|
+
*/
|
|
64
|
+
ensureCourseSynced?(courseId: string, forceEnabled?: boolean): Promise<void>;
|
|
46
65
|
}
|
|
47
66
|
|
|
48
67
|
export type { DataLayerProvider as D };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { U as UserDBInterface, a as UserDBReader, C as CourseDBInterface, b as CoursesDBInterface, c as ClassroomDBInterface, A as AdminDBInterface } from './contentSource-
|
|
1
|
+
import { U as UserDBInterface, a as UserDBReader, C as CourseDBInterface, b as CoursesDBInterface, c as ClassroomDBInterface, A as AdminDBInterface } from './contentSource-DF1nUbPQ.cjs';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Main factory interface for data access
|
|
@@ -43,6 +43,25 @@ interface DataLayerProvider {
|
|
|
43
43
|
* Check if this data layer is read-only
|
|
44
44
|
*/
|
|
45
45
|
isReadOnly(): boolean;
|
|
46
|
+
/**
|
|
47
|
+
* Trigger local replication of a course database.
|
|
48
|
+
*
|
|
49
|
+
* When a course opts in via `CourseConfig.localSync.enabled`, this method
|
|
50
|
+
* replicates the remote course DB to a local PouchDB instance. Subsequent
|
|
51
|
+
* `getCourseDB()` calls for that course will return a CourseDB that reads
|
|
52
|
+
* from the local replica (fast, no network) and writes to the remote
|
|
53
|
+
* (ELO updates, admin ops).
|
|
54
|
+
*
|
|
55
|
+
* Safe to call multiple times — concurrent calls coalesce. Returns when
|
|
56
|
+
* sync is complete (or immediately if already synced / disabled).
|
|
57
|
+
*
|
|
58
|
+
* Implementations that don't support local sync may no-op.
|
|
59
|
+
*
|
|
60
|
+
* @param courseId - The course to sync locally
|
|
61
|
+
* @param forceEnabled - Skip CourseConfig check and sync regardless.
|
|
62
|
+
* Use when the caller already knows local sync is desired.
|
|
63
|
+
*/
|
|
64
|
+
ensureCourseSynced?(courseId: string, forceEnabled?: boolean): Promise<void>;
|
|
46
65
|
}
|
|
47
66
|
|
|
48
67
|
export type { DataLayerProvider as D };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { T as TagStub, a as Tag, S as SkuilderCourseData, Q as QualifiedCardID } from '../../types-legacy-JXDxinpU.cjs';
|
|
2
2
|
import { Moment } from 'moment';
|
|
3
|
-
import { A as AdminDBInterface, g as AssignedContent, h as StudyContentSource, i as StudentClassroomDBInterface, U as UserDBInterface, W as WeightedCard, T as TeacherClassroomDBInterface, b as CoursesDBInterface, C as CourseDBInterface, d as CourseInfo, D as DataLayerResult, e as ContentNavigationStrategyData, f as ContentNavigator, S as StudySessionItem, j as ScheduledCard } from '../../contentSource-
|
|
4
|
-
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-
|
|
3
|
+
import { A as AdminDBInterface, g as AssignedContent, h as StudyContentSource, i as StudentClassroomDBInterface, U as UserDBInterface, W as WeightedCard, T as TeacherClassroomDBInterface, b as CoursesDBInterface, C as CourseDBInterface, d as CourseInfo, D as DataLayerResult, e as ContentNavigationStrategyData, f as ContentNavigator, S as StudySessionItem, j as ScheduledCard } from '../../contentSource-DF1nUbPQ.cjs';
|
|
4
|
+
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-DF1nUbPQ.cjs';
|
|
5
5
|
import * as _vue_skuilder_common from '@vue-skuilder/common';
|
|
6
6
|
import { ClassroomConfig, DataShape, CourseElo, CourseConfig as CourseConfig$1 } from '@vue-skuilder/common';
|
|
7
7
|
import { S as SyncStrategy, A as AccountCreationResult, a as AuthenticationResult } from '../../SyncStrategy-CyATpyLQ.cjs';
|
|
@@ -45,6 +45,27 @@ interface CourseConfig {
|
|
|
45
45
|
questionTypes: QuestionType55[];
|
|
46
46
|
disambiguator?: string;
|
|
47
47
|
orchestration?: CourseOrchestrationConfig;
|
|
48
|
+
/**
|
|
49
|
+
* Opt-in client-side replication of the course database.
|
|
50
|
+
*
|
|
51
|
+
* When enabled, the client replicates the course DB to a local PouchDB
|
|
52
|
+
* instance on first visit (full one-shot sync) and performs incremental
|
|
53
|
+
* sync on subsequent visits. Pipeline scoring, tag hydration, and card
|
|
54
|
+
* lookup then run against the local replica — eliminating network round
|
|
55
|
+
* trips from the study-session hot path.
|
|
56
|
+
*
|
|
57
|
+
* **Read/write split:** The local DB is a read-only snapshot. All writes
|
|
58
|
+
* (card ELO updates, tag mutations, etc.) continue to target the remote
|
|
59
|
+
* CouchDB. This avoids propagating per-interaction ELO noise to every
|
|
60
|
+
* syncing client — the remote DB aggregates writes from all users, and
|
|
61
|
+
* each client's local snapshot is refreshed on the next page load.
|
|
62
|
+
*
|
|
63
|
+
* Defaults to `undefined` (disabled). Courses with small, relatively
|
|
64
|
+
* static content databases (e.g. < 50 MB) are good candidates.
|
|
65
|
+
*/
|
|
66
|
+
localSync?: {
|
|
67
|
+
enabled: boolean;
|
|
68
|
+
};
|
|
48
69
|
}
|
|
49
70
|
|
|
50
71
|
declare class AdminDB implements AdminDBInterface {
|
|
@@ -156,11 +177,36 @@ declare class CoursesDB implements CoursesDBInterface {
|
|
|
156
177
|
disambiguateCourse(courseId: string, disambiguator: string): Promise<void>;
|
|
157
178
|
}
|
|
158
179
|
declare class CourseDB implements CourseDBInterface {
|
|
180
|
+
/**
|
|
181
|
+
* Primary database handle used for all **read** operations (queries, gets).
|
|
182
|
+
*
|
|
183
|
+
* When local sync is active, this points to the local PouchDB replica for
|
|
184
|
+
* fast, network-free reads. Otherwise it points to the remote CouchDB.
|
|
185
|
+
*/
|
|
159
186
|
private db;
|
|
187
|
+
/**
|
|
188
|
+
* Remote database handle used for all **write** operations.
|
|
189
|
+
*
|
|
190
|
+
* Always points to the remote CouchDB so that writes (ELO updates, tag
|
|
191
|
+
* mutations, admin operations) aggregate on the server. The local replica
|
|
192
|
+
* is a read-only snapshot that refreshes on the next page load.
|
|
193
|
+
*
|
|
194
|
+
* When local sync is NOT active, this is the same instance as `this.db`.
|
|
195
|
+
*/
|
|
196
|
+
private remoteDB;
|
|
160
197
|
private id;
|
|
161
198
|
private _getCurrentUser;
|
|
162
199
|
private updateQueue;
|
|
163
|
-
|
|
200
|
+
/**
|
|
201
|
+
* @param id - Course ID
|
|
202
|
+
* @param userLookup - Async function returning the current user DB
|
|
203
|
+
* @param localDB - Optional local PouchDB replica for reads. When provided,
|
|
204
|
+
* `this.db` uses the local replica and `this.remoteDB` stays remote.
|
|
205
|
+
* The UpdateQueue reads from remote and writes to remote (local `_rev`
|
|
206
|
+
* values may be stale, so read-modify-write cycles must go through
|
|
207
|
+
* the remote DB to avoid conflicts).
|
|
208
|
+
*/
|
|
209
|
+
constructor(id: string, userLookup: () => Promise<UserDBInterface>, localDB?: PouchDB.Database);
|
|
164
210
|
getCourseID(): string;
|
|
165
211
|
getCourseInfo(): Promise<CourseInfo>;
|
|
166
212
|
getInexperiencedCards(limit?: number): Promise<{
|
|
@@ -197,6 +243,7 @@ declare class CourseDB implements CourseDBInterface {
|
|
|
197
243
|
updateCardElo(cardId: string, elo: CourseElo): Promise<PouchDB.Core.Response>;
|
|
198
244
|
getAppliedTags(cardId: string): Promise<PouchDB.Query.Response<TagStub>>;
|
|
199
245
|
getAppliedTagsBatch(cardIds: string[]): Promise<Map<string, string[]>>;
|
|
246
|
+
getAllCardIds(): Promise<string[]>;
|
|
200
247
|
addTagToCard(cardId: string, tagId: string, updateELO?: boolean): Promise<PouchDB.Core.Response>;
|
|
201
248
|
removeTagFromCard(cardId: string, tagId: string): Promise<PouchDB.Core.Response>;
|
|
202
249
|
createTag(name: string, author: string): Promise<PouchDB.Core.Response>;
|
|
@@ -268,6 +315,111 @@ declare function getAppliedTags(id_course: string, id_card: string): Promise<Pou
|
|
|
268
315
|
declare function updateCardElo(courseID: string, cardID: string, elo: CourseElo): Promise<PouchDB.Core.Response | undefined>;
|
|
269
316
|
declare function updateCredentialledCourseConfig(courseID: string, config: CourseConfig$1): Promise<PouchDB.Core.Response>;
|
|
270
317
|
|
|
318
|
+
/**
|
|
319
|
+
* Sync state for a single course database.
|
|
320
|
+
*/
|
|
321
|
+
type CourseSyncState = 'not-started' | 'checking-config' | 'syncing' | 'warming-views' | 'ready' | 'disabled' | 'error';
|
|
322
|
+
/**
|
|
323
|
+
* Detailed sync status for observability.
|
|
324
|
+
*/
|
|
325
|
+
interface CourseSyncStatus {
|
|
326
|
+
state: CourseSyncState;
|
|
327
|
+
/** Number of documents replicated (set after sync completes) */
|
|
328
|
+
docsReplicated?: number;
|
|
329
|
+
/** Total replication time in ms */
|
|
330
|
+
syncTimeMs?: number;
|
|
331
|
+
/** View warming time in ms */
|
|
332
|
+
viewWarmTimeMs?: number;
|
|
333
|
+
/** Error message if state is 'error' */
|
|
334
|
+
error?: string;
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* Service that manages local PouchDB replicas of course databases.
|
|
338
|
+
*
|
|
339
|
+
* Usage:
|
|
340
|
+
* ```typescript
|
|
341
|
+
* const syncService = CourseSyncService.getInstance();
|
|
342
|
+
*
|
|
343
|
+
* // Trigger sync (typically on app load / pre-session)
|
|
344
|
+
* await syncService.ensureSynced(courseId);
|
|
345
|
+
*
|
|
346
|
+
* // Get local DB for reads (returns null if sync not ready/enabled)
|
|
347
|
+
* const localDB = syncService.getLocalDB(courseId);
|
|
348
|
+
* ```
|
|
349
|
+
*
|
|
350
|
+
* The service is a singleton — course sync state is shared across the app.
|
|
351
|
+
*/
|
|
352
|
+
declare class CourseSyncService {
|
|
353
|
+
private static instance;
|
|
354
|
+
private entries;
|
|
355
|
+
private constructor();
|
|
356
|
+
static getInstance(): CourseSyncService;
|
|
357
|
+
/**
|
|
358
|
+
* Reset the singleton (for testing).
|
|
359
|
+
*/
|
|
360
|
+
static resetInstance(): void;
|
|
361
|
+
/**
|
|
362
|
+
* Ensure a course's local replica is synced.
|
|
363
|
+
*
|
|
364
|
+
* On first call for a course:
|
|
365
|
+
* 1. Fetches CourseConfig from remote to check localSync.enabled
|
|
366
|
+
* 2. If enabled, performs one-shot replication remote → local
|
|
367
|
+
* 3. Pre-warms PouchDB view indices (elo, getTags)
|
|
368
|
+
*
|
|
369
|
+
* On subsequent calls: returns immediately if already synced, or awaits
|
|
370
|
+
* the in-flight sync if one is in progress.
|
|
371
|
+
*
|
|
372
|
+
* Safe to call multiple times — concurrent calls coalesce to one sync.
|
|
373
|
+
*
|
|
374
|
+
* @param courseId - The course to sync
|
|
375
|
+
* @param forceEnabled - Skip the CourseConfig check and sync regardless.
|
|
376
|
+
* Useful when the caller already knows local sync is desired (e.g.,
|
|
377
|
+
* LettersPractice hardcodes this).
|
|
378
|
+
*/
|
|
379
|
+
ensureSynced(courseId: string, forceEnabled?: boolean): Promise<void>;
|
|
380
|
+
/**
|
|
381
|
+
* Get the local PouchDB for a course, or null if not available.
|
|
382
|
+
*
|
|
383
|
+
* Returns null when:
|
|
384
|
+
* - Local sync is not enabled for this course
|
|
385
|
+
* - Sync has not been triggered yet
|
|
386
|
+
* - Sync is still in progress
|
|
387
|
+
* - Sync failed
|
|
388
|
+
*/
|
|
389
|
+
getLocalDB(courseId: string): PouchDB.Database | null;
|
|
390
|
+
/**
|
|
391
|
+
* Check whether a course has a ready local replica.
|
|
392
|
+
*/
|
|
393
|
+
isReady(courseId: string): boolean;
|
|
394
|
+
/**
|
|
395
|
+
* Get detailed sync status for a course.
|
|
396
|
+
*/
|
|
397
|
+
getStatus(courseId: string): CourseSyncStatus;
|
|
398
|
+
private performSync;
|
|
399
|
+
/**
|
|
400
|
+
* Check CourseConfig.localSync.enabled on the remote DB.
|
|
401
|
+
*/
|
|
402
|
+
private checkLocalSyncEnabled;
|
|
403
|
+
/**
|
|
404
|
+
* One-shot replication from remote to local.
|
|
405
|
+
*/
|
|
406
|
+
private replicate;
|
|
407
|
+
/**
|
|
408
|
+
* Pre-warm PouchDB view indices by running a minimal query against each
|
|
409
|
+
* design doc. This forces PouchDB to build the MapReduce index now
|
|
410
|
+
* (during a loading phase) rather than on first pipeline query.
|
|
411
|
+
*/
|
|
412
|
+
private warmViewIndices;
|
|
413
|
+
/**
|
|
414
|
+
* Get a remote PouchDB handle for a course.
|
|
415
|
+
*/
|
|
416
|
+
private getRemoteDB;
|
|
417
|
+
/**
|
|
418
|
+
* Local DB naming convention.
|
|
419
|
+
*/
|
|
420
|
+
private localDBName;
|
|
421
|
+
}
|
|
422
|
+
|
|
271
423
|
/**
|
|
272
424
|
* Sync strategy that implements full CouchDB remote synchronization
|
|
273
425
|
* Handles account creation, authentication, and live sync with remote CouchDB server
|
|
@@ -339,4 +491,4 @@ declare function getStartAndEndKeys(key: string): {
|
|
|
339
491
|
endkey: string;
|
|
340
492
|
};
|
|
341
493
|
|
|
342
|
-
export { AdminDB, CLASSROOM_CONFIG, ClassroomLookupDB, type ClassroomMessage, CouchDBSyncStrategy, CourseDB, CoursesDB, REVIEW_TIME_FORMAT, 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 };
|
|
494
|
+
export { AdminDB, CLASSROOM_CONFIG, ClassroomLookupDB, type ClassroomMessage, CouchDBSyncStrategy, CourseDB, CourseSyncService, type CourseSyncState, type CourseSyncStatus, CoursesDB, REVIEW_TIME_FORMAT, 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 };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { T as TagStub, a as Tag, S as SkuilderCourseData, Q as QualifiedCardID } from '../../types-legacy-JXDxinpU.js';
|
|
2
2
|
import { Moment } from 'moment';
|
|
3
|
-
import { A as AdminDBInterface, g as AssignedContent, h as StudyContentSource, i as StudentClassroomDBInterface, U as UserDBInterface, W as WeightedCard, T as TeacherClassroomDBInterface, b as CoursesDBInterface, C as CourseDBInterface, d as CourseInfo, D as DataLayerResult, e as ContentNavigationStrategyData, f as ContentNavigator, S as StudySessionItem, j as ScheduledCard } from '../../contentSource-
|
|
4
|
-
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-
|
|
3
|
+
import { A as AdminDBInterface, g as AssignedContent, h as StudyContentSource, i as StudentClassroomDBInterface, U as UserDBInterface, W as WeightedCard, T as TeacherClassroomDBInterface, b as CoursesDBInterface, C as CourseDBInterface, d as CourseInfo, D as DataLayerResult, e as ContentNavigationStrategyData, f as ContentNavigator, S as StudySessionItem, j as ScheduledCard } from '../../contentSource-Bdwkvqa8.js';
|
|
4
|
+
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-Bdwkvqa8.js';
|
|
5
5
|
import * as _vue_skuilder_common from '@vue-skuilder/common';
|
|
6
6
|
import { ClassroomConfig, DataShape, CourseElo, CourseConfig as CourseConfig$1 } from '@vue-skuilder/common';
|
|
7
7
|
import { S as SyncStrategy, A as AccountCreationResult, a as AuthenticationResult } from '../../SyncStrategy-CyATpyLQ.js';
|
|
@@ -45,6 +45,27 @@ interface CourseConfig {
|
|
|
45
45
|
questionTypes: QuestionType55[];
|
|
46
46
|
disambiguator?: string;
|
|
47
47
|
orchestration?: CourseOrchestrationConfig;
|
|
48
|
+
/**
|
|
49
|
+
* Opt-in client-side replication of the course database.
|
|
50
|
+
*
|
|
51
|
+
* When enabled, the client replicates the course DB to a local PouchDB
|
|
52
|
+
* instance on first visit (full one-shot sync) and performs incremental
|
|
53
|
+
* sync on subsequent visits. Pipeline scoring, tag hydration, and card
|
|
54
|
+
* lookup then run against the local replica — eliminating network round
|
|
55
|
+
* trips from the study-session hot path.
|
|
56
|
+
*
|
|
57
|
+
* **Read/write split:** The local DB is a read-only snapshot. All writes
|
|
58
|
+
* (card ELO updates, tag mutations, etc.) continue to target the remote
|
|
59
|
+
* CouchDB. This avoids propagating per-interaction ELO noise to every
|
|
60
|
+
* syncing client — the remote DB aggregates writes from all users, and
|
|
61
|
+
* each client's local snapshot is refreshed on the next page load.
|
|
62
|
+
*
|
|
63
|
+
* Defaults to `undefined` (disabled). Courses with small, relatively
|
|
64
|
+
* static content databases (e.g. < 50 MB) are good candidates.
|
|
65
|
+
*/
|
|
66
|
+
localSync?: {
|
|
67
|
+
enabled: boolean;
|
|
68
|
+
};
|
|
48
69
|
}
|
|
49
70
|
|
|
50
71
|
declare class AdminDB implements AdminDBInterface {
|
|
@@ -156,11 +177,36 @@ declare class CoursesDB implements CoursesDBInterface {
|
|
|
156
177
|
disambiguateCourse(courseId: string, disambiguator: string): Promise<void>;
|
|
157
178
|
}
|
|
158
179
|
declare class CourseDB implements CourseDBInterface {
|
|
180
|
+
/**
|
|
181
|
+
* Primary database handle used for all **read** operations (queries, gets).
|
|
182
|
+
*
|
|
183
|
+
* When local sync is active, this points to the local PouchDB replica for
|
|
184
|
+
* fast, network-free reads. Otherwise it points to the remote CouchDB.
|
|
185
|
+
*/
|
|
159
186
|
private db;
|
|
187
|
+
/**
|
|
188
|
+
* Remote database handle used for all **write** operations.
|
|
189
|
+
*
|
|
190
|
+
* Always points to the remote CouchDB so that writes (ELO updates, tag
|
|
191
|
+
* mutations, admin operations) aggregate on the server. The local replica
|
|
192
|
+
* is a read-only snapshot that refreshes on the next page load.
|
|
193
|
+
*
|
|
194
|
+
* When local sync is NOT active, this is the same instance as `this.db`.
|
|
195
|
+
*/
|
|
196
|
+
private remoteDB;
|
|
160
197
|
private id;
|
|
161
198
|
private _getCurrentUser;
|
|
162
199
|
private updateQueue;
|
|
163
|
-
|
|
200
|
+
/**
|
|
201
|
+
* @param id - Course ID
|
|
202
|
+
* @param userLookup - Async function returning the current user DB
|
|
203
|
+
* @param localDB - Optional local PouchDB replica for reads. When provided,
|
|
204
|
+
* `this.db` uses the local replica and `this.remoteDB` stays remote.
|
|
205
|
+
* The UpdateQueue reads from remote and writes to remote (local `_rev`
|
|
206
|
+
* values may be stale, so read-modify-write cycles must go through
|
|
207
|
+
* the remote DB to avoid conflicts).
|
|
208
|
+
*/
|
|
209
|
+
constructor(id: string, userLookup: () => Promise<UserDBInterface>, localDB?: PouchDB.Database);
|
|
164
210
|
getCourseID(): string;
|
|
165
211
|
getCourseInfo(): Promise<CourseInfo>;
|
|
166
212
|
getInexperiencedCards(limit?: number): Promise<{
|
|
@@ -197,6 +243,7 @@ declare class CourseDB implements CourseDBInterface {
|
|
|
197
243
|
updateCardElo(cardId: string, elo: CourseElo): Promise<PouchDB.Core.Response>;
|
|
198
244
|
getAppliedTags(cardId: string): Promise<PouchDB.Query.Response<TagStub>>;
|
|
199
245
|
getAppliedTagsBatch(cardIds: string[]): Promise<Map<string, string[]>>;
|
|
246
|
+
getAllCardIds(): Promise<string[]>;
|
|
200
247
|
addTagToCard(cardId: string, tagId: string, updateELO?: boolean): Promise<PouchDB.Core.Response>;
|
|
201
248
|
removeTagFromCard(cardId: string, tagId: string): Promise<PouchDB.Core.Response>;
|
|
202
249
|
createTag(name: string, author: string): Promise<PouchDB.Core.Response>;
|
|
@@ -268,6 +315,111 @@ declare function getAppliedTags(id_course: string, id_card: string): Promise<Pou
|
|
|
268
315
|
declare function updateCardElo(courseID: string, cardID: string, elo: CourseElo): Promise<PouchDB.Core.Response | undefined>;
|
|
269
316
|
declare function updateCredentialledCourseConfig(courseID: string, config: CourseConfig$1): Promise<PouchDB.Core.Response>;
|
|
270
317
|
|
|
318
|
+
/**
|
|
319
|
+
* Sync state for a single course database.
|
|
320
|
+
*/
|
|
321
|
+
type CourseSyncState = 'not-started' | 'checking-config' | 'syncing' | 'warming-views' | 'ready' | 'disabled' | 'error';
|
|
322
|
+
/**
|
|
323
|
+
* Detailed sync status for observability.
|
|
324
|
+
*/
|
|
325
|
+
interface CourseSyncStatus {
|
|
326
|
+
state: CourseSyncState;
|
|
327
|
+
/** Number of documents replicated (set after sync completes) */
|
|
328
|
+
docsReplicated?: number;
|
|
329
|
+
/** Total replication time in ms */
|
|
330
|
+
syncTimeMs?: number;
|
|
331
|
+
/** View warming time in ms */
|
|
332
|
+
viewWarmTimeMs?: number;
|
|
333
|
+
/** Error message if state is 'error' */
|
|
334
|
+
error?: string;
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* Service that manages local PouchDB replicas of course databases.
|
|
338
|
+
*
|
|
339
|
+
* Usage:
|
|
340
|
+
* ```typescript
|
|
341
|
+
* const syncService = CourseSyncService.getInstance();
|
|
342
|
+
*
|
|
343
|
+
* // Trigger sync (typically on app load / pre-session)
|
|
344
|
+
* await syncService.ensureSynced(courseId);
|
|
345
|
+
*
|
|
346
|
+
* // Get local DB for reads (returns null if sync not ready/enabled)
|
|
347
|
+
* const localDB = syncService.getLocalDB(courseId);
|
|
348
|
+
* ```
|
|
349
|
+
*
|
|
350
|
+
* The service is a singleton — course sync state is shared across the app.
|
|
351
|
+
*/
|
|
352
|
+
declare class CourseSyncService {
|
|
353
|
+
private static instance;
|
|
354
|
+
private entries;
|
|
355
|
+
private constructor();
|
|
356
|
+
static getInstance(): CourseSyncService;
|
|
357
|
+
/**
|
|
358
|
+
* Reset the singleton (for testing).
|
|
359
|
+
*/
|
|
360
|
+
static resetInstance(): void;
|
|
361
|
+
/**
|
|
362
|
+
* Ensure a course's local replica is synced.
|
|
363
|
+
*
|
|
364
|
+
* On first call for a course:
|
|
365
|
+
* 1. Fetches CourseConfig from remote to check localSync.enabled
|
|
366
|
+
* 2. If enabled, performs one-shot replication remote → local
|
|
367
|
+
* 3. Pre-warms PouchDB view indices (elo, getTags)
|
|
368
|
+
*
|
|
369
|
+
* On subsequent calls: returns immediately if already synced, or awaits
|
|
370
|
+
* the in-flight sync if one is in progress.
|
|
371
|
+
*
|
|
372
|
+
* Safe to call multiple times — concurrent calls coalesce to one sync.
|
|
373
|
+
*
|
|
374
|
+
* @param courseId - The course to sync
|
|
375
|
+
* @param forceEnabled - Skip the CourseConfig check and sync regardless.
|
|
376
|
+
* Useful when the caller already knows local sync is desired (e.g.,
|
|
377
|
+
* LettersPractice hardcodes this).
|
|
378
|
+
*/
|
|
379
|
+
ensureSynced(courseId: string, forceEnabled?: boolean): Promise<void>;
|
|
380
|
+
/**
|
|
381
|
+
* Get the local PouchDB for a course, or null if not available.
|
|
382
|
+
*
|
|
383
|
+
* Returns null when:
|
|
384
|
+
* - Local sync is not enabled for this course
|
|
385
|
+
* - Sync has not been triggered yet
|
|
386
|
+
* - Sync is still in progress
|
|
387
|
+
* - Sync failed
|
|
388
|
+
*/
|
|
389
|
+
getLocalDB(courseId: string): PouchDB.Database | null;
|
|
390
|
+
/**
|
|
391
|
+
* Check whether a course has a ready local replica.
|
|
392
|
+
*/
|
|
393
|
+
isReady(courseId: string): boolean;
|
|
394
|
+
/**
|
|
395
|
+
* Get detailed sync status for a course.
|
|
396
|
+
*/
|
|
397
|
+
getStatus(courseId: string): CourseSyncStatus;
|
|
398
|
+
private performSync;
|
|
399
|
+
/**
|
|
400
|
+
* Check CourseConfig.localSync.enabled on the remote DB.
|
|
401
|
+
*/
|
|
402
|
+
private checkLocalSyncEnabled;
|
|
403
|
+
/**
|
|
404
|
+
* One-shot replication from remote to local.
|
|
405
|
+
*/
|
|
406
|
+
private replicate;
|
|
407
|
+
/**
|
|
408
|
+
* Pre-warm PouchDB view indices by running a minimal query against each
|
|
409
|
+
* design doc. This forces PouchDB to build the MapReduce index now
|
|
410
|
+
* (during a loading phase) rather than on first pipeline query.
|
|
411
|
+
*/
|
|
412
|
+
private warmViewIndices;
|
|
413
|
+
/**
|
|
414
|
+
* Get a remote PouchDB handle for a course.
|
|
415
|
+
*/
|
|
416
|
+
private getRemoteDB;
|
|
417
|
+
/**
|
|
418
|
+
* Local DB naming convention.
|
|
419
|
+
*/
|
|
420
|
+
private localDBName;
|
|
421
|
+
}
|
|
422
|
+
|
|
271
423
|
/**
|
|
272
424
|
* Sync strategy that implements full CouchDB remote synchronization
|
|
273
425
|
* Handles account creation, authentication, and live sync with remote CouchDB server
|
|
@@ -339,4 +491,4 @@ declare function getStartAndEndKeys(key: string): {
|
|
|
339
491
|
endkey: string;
|
|
340
492
|
};
|
|
341
493
|
|
|
342
|
-
export { AdminDB, CLASSROOM_CONFIG, ClassroomLookupDB, type ClassroomMessage, CouchDBSyncStrategy, CourseDB, CoursesDB, REVIEW_TIME_FORMAT, 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 };
|
|
494
|
+
export { AdminDB, CLASSROOM_CONFIG, ClassroomLookupDB, type ClassroomMessage, CouchDBSyncStrategy, CourseDB, CourseSyncService, type CourseSyncState, type CourseSyncStatus, CoursesDB, REVIEW_TIME_FORMAT, 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 };
|