@vue-skuilder/db 0.2.18 → 0.2.20
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/{SyncStrategy-BJMZq3WO.d.cts → SyncStrategy-CCU1H81I.d.cts} +8 -2
- package/dist/{SyncStrategy-BJMZq3WO.d.ts → SyncStrategy-CCU1H81I.d.ts} +8 -2
- package/dist/{contentSource-CudEz5Tm.d.ts → contentSource-BDRX_YYJ.d.ts} +13 -1
- package/dist/{contentSource-CBqZCoGU.d.cts → contentSource-D-LQYFyx.d.cts} +13 -1
- package/dist/core/index.d.cts +3 -3
- package/dist/core/index.d.ts +3 -3
- package/dist/core/index.js +38 -7
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.mjs +38 -7
- package/dist/core/index.mjs.map +1 -1
- package/dist/{dataLayerProvider-BBA8tJNx.d.cts → dataLayerProvider-Dd2UcCif.d.cts} +1 -1
- package/dist/{dataLayerProvider-C9WgkBzR.d.ts → dataLayerProvider-Dpshuql4.d.ts} +1 -1
- package/dist/impl/couch/index.d.cts +20 -4
- package/dist/impl/couch/index.d.ts +20 -4
- package/dist/impl/couch/index.js +80 -11
- package/dist/impl/couch/index.js.map +1 -1
- package/dist/impl/couch/index.mjs +80 -11
- package/dist/impl/couch/index.mjs.map +1 -1
- package/dist/impl/static/index.d.cts +3 -3
- package/dist/impl/static/index.d.ts +3 -3
- package/dist/impl/static/index.js +38 -7
- package/dist/impl/static/index.js.map +1 -1
- package/dist/impl/static/index.mjs +38 -7
- package/dist/impl/static/index.mjs.map +1 -1
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +80 -11
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +80 -11
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
- package/src/core/interfaces/userDB.ts +14 -1
- package/src/impl/common/BaseUserDB.ts +56 -8
- package/src/impl/common/SyncStrategy.ts +17 -4
- package/src/impl/couch/CouchDBSyncStrategy.ts +52 -4
- package/tests/impl/hydration.test.ts +49 -0
|
@@ -48,17 +48,23 @@ interface SyncStrategy {
|
|
|
48
48
|
*
|
|
49
49
|
* @param localDB The local PouchDB instance
|
|
50
50
|
* @param remoteDB The remote PouchDB instance
|
|
51
|
-
* @returns Count of documents written locally
|
|
51
|
+
* @returns Count of documents written locally, and — only when the pull is
|
|
52
|
+
* known to have landed the remote's complete current state — the sequence
|
|
53
|
+
* it corresponds to, for startSync() to resume from. Omitted means "no
|
|
54
|
+
* safe shortcut, walk from the checkpoint".
|
|
52
55
|
*/
|
|
53
56
|
hydrate?(localDB: PouchDB.Database, remoteDB: PouchDB.Database): Promise<{
|
|
54
57
|
docsWritten: number;
|
|
58
|
+
lastSeq?: string | number;
|
|
55
59
|
}>;
|
|
56
60
|
/**
|
|
57
61
|
* Start synchronization between local and remote databases
|
|
58
62
|
* @param localDB The local PouchDB instance
|
|
59
63
|
* @param remoteDB The remote PouchDB instance
|
|
64
|
+
* @param since Sequence to start the pull from, as returned by a hydrate()
|
|
65
|
+
* in the same session. Omit to resume from the stored checkpoint.
|
|
60
66
|
*/
|
|
61
|
-
startSync(localDB: PouchDB.Database, remoteDB: PouchDB.Database): void;
|
|
67
|
+
startSync(localDB: PouchDB.Database, remoteDB: PouchDB.Database, since?: string | number): void;
|
|
62
68
|
/**
|
|
63
69
|
* Stop synchronization (optional - for cleanup)
|
|
64
70
|
*/
|
|
@@ -48,17 +48,23 @@ interface SyncStrategy {
|
|
|
48
48
|
*
|
|
49
49
|
* @param localDB The local PouchDB instance
|
|
50
50
|
* @param remoteDB The remote PouchDB instance
|
|
51
|
-
* @returns Count of documents written locally
|
|
51
|
+
* @returns Count of documents written locally, and — only when the pull is
|
|
52
|
+
* known to have landed the remote's complete current state — the sequence
|
|
53
|
+
* it corresponds to, for startSync() to resume from. Omitted means "no
|
|
54
|
+
* safe shortcut, walk from the checkpoint".
|
|
52
55
|
*/
|
|
53
56
|
hydrate?(localDB: PouchDB.Database, remoteDB: PouchDB.Database): Promise<{
|
|
54
57
|
docsWritten: number;
|
|
58
|
+
lastSeq?: string | number;
|
|
55
59
|
}>;
|
|
56
60
|
/**
|
|
57
61
|
* Start synchronization between local and remote databases
|
|
58
62
|
* @param localDB The local PouchDB instance
|
|
59
63
|
* @param remoteDB The remote PouchDB instance
|
|
64
|
+
* @param since Sequence to start the pull from, as returned by a hydrate()
|
|
65
|
+
* in the same session. Omit to resume from the stored checkpoint.
|
|
60
66
|
*/
|
|
61
|
-
startSync(localDB: PouchDB.Database, remoteDB: PouchDB.Database): void;
|
|
67
|
+
startSync(localDB: PouchDB.Database, remoteDB: PouchDB.Database, since?: string | number): void;
|
|
62
68
|
/**
|
|
63
69
|
* Stop synchronization (optional - for cleanup)
|
|
64
70
|
*/
|
|
@@ -514,14 +514,26 @@ interface UserDBReader {
|
|
|
514
514
|
getUsername(): string;
|
|
515
515
|
isLoggedIn(): boolean;
|
|
516
516
|
/**
|
|
517
|
-
* How far this device's local mirror of the user's data can be trusted
|
|
517
|
+
* How far this device's local mirror of the user's data can be trusted,
|
|
518
|
+
* as of right now.
|
|
518
519
|
*
|
|
519
520
|
* Reads answer from the local mirror, so on a device that has never synced
|
|
520
521
|
* this account "document not found" does not mean "no such data". Anything
|
|
521
522
|
* that would otherwise interpret an empty read as a new user should check
|
|
522
523
|
* for `failed` first. See {@link UserHydrationStatus}.
|
|
524
|
+
*
|
|
525
|
+
* Can return `hydrating`; use {@link awaitHydration} to decide on a settled
|
|
526
|
+
* answer instead of a snapshot.
|
|
523
527
|
*/
|
|
524
528
|
hydrationStatus(): UserHydrationStatus;
|
|
529
|
+
/**
|
|
530
|
+
* The hydration status once settled — never `hydrating`.
|
|
531
|
+
*
|
|
532
|
+
* Resolves immediately unless a pull is in flight. Prefer this over
|
|
533
|
+
* hydrationStatus() wherever the answer gates behaviour, such as a route
|
|
534
|
+
* guard that may run while a login is still completing.
|
|
535
|
+
*/
|
|
536
|
+
awaitHydration(): Promise<UserHydrationStatus>;
|
|
525
537
|
/**
|
|
526
538
|
* Get user configuration
|
|
527
539
|
*/
|
|
@@ -514,14 +514,26 @@ interface UserDBReader {
|
|
|
514
514
|
getUsername(): string;
|
|
515
515
|
isLoggedIn(): boolean;
|
|
516
516
|
/**
|
|
517
|
-
* How far this device's local mirror of the user's data can be trusted
|
|
517
|
+
* How far this device's local mirror of the user's data can be trusted,
|
|
518
|
+
* as of right now.
|
|
518
519
|
*
|
|
519
520
|
* Reads answer from the local mirror, so on a device that has never synced
|
|
520
521
|
* this account "document not found" does not mean "no such data". Anything
|
|
521
522
|
* that would otherwise interpret an empty read as a new user should check
|
|
522
523
|
* for `failed` first. See {@link UserHydrationStatus}.
|
|
524
|
+
*
|
|
525
|
+
* Can return `hydrating`; use {@link awaitHydration} to decide on a settled
|
|
526
|
+
* answer instead of a snapshot.
|
|
523
527
|
*/
|
|
524
528
|
hydrationStatus(): UserHydrationStatus;
|
|
529
|
+
/**
|
|
530
|
+
* The hydration status once settled — never `hydrating`.
|
|
531
|
+
*
|
|
532
|
+
* Resolves immediately unless a pull is in flight. Prefer this over
|
|
533
|
+
* hydrationStatus() wherever the answer gates behaviour, such as a route
|
|
534
|
+
* guard that may run while a login is still completing.
|
|
535
|
+
*/
|
|
536
|
+
awaitHydration(): Promise<UserHydrationStatus>;
|
|
525
537
|
/**
|
|
526
538
|
* Get user configuration
|
|
527
539
|
*/
|
package/dist/core/index.d.cts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { ab as LearnableWeight, M as UserOutcomeRecord, ac as OrchestrationContext, W as WeightedCard, U as UserDBInterface, C as CourseDBInterface, R as ReplanHints, a1 as StrategyContribution } from '../contentSource-
|
|
2
|
-
export { K as ActivityRecord, A as AdminDBInterface, v as AssignedCard, g as AssignedContent, u as AssignedCourse, t as AssignedTag, a8 as CardGenerator, aa as CardGeneratorFactory, c as ClassroomDBInterface, F as ClassroomRegistration, E as ClassroomRegistrationDesignation, H as ClassroomRegistrationDoc, e as ContentNavigationStrategyData, f as ContentNavigator, q as ContentSourceID, d as CourseInfo, L as CourseRegistration, s as CourseRegistrationDoc, b as CoursesDBInterface, a9 as GeneratorContext, G as GeneratorResult, P as HYDRATION_MARKER_ID, Q as HydrationMarker, V as NavigatorConstructor, a4 as NavigatorRole, a5 as NavigatorRoles, a3 as Navigators, j as ScheduledCard, I as SessionTrackingData, i as StudentClassroomDBInterface, h as StudyContentSource, k as StudySessionFailedItem, l as StudySessionFailedNewItem, m as StudySessionFailedReviewItem, S as StudySessionItem, n as StudySessionNewItem, o as StudySessionReviewItem, T as TeacherClassroomDBInterface, J as UserConfig, z as UserCourseSetting, y as UserCourseSettings, x as UserDBAuthenticator, a as UserDBReader, w as UserDBWriter, N as UserHydrationState, O as UserHydrationStatus, B as UsrCrsDataInterface, ad as computeDeviation, af as computeEffectiveWeight, ae as computeSpread, ag as createOrchestrationContext, a2 as getCardOrigin, Y as getRegisteredNavigator, $ as getRegisteredNavigatorNames, _ as getRegisteredNavigatorRole, r as getStudySource, Z as hasRegisteredNavigator, a0 as initializeNavigatorRegistry, a7 as isFilter, a6 as isGenerator, p as isReview, X as registerNavigator } from '../contentSource-
|
|
3
|
-
export { D as DataLayerProvider } from '../dataLayerProvider-
|
|
1
|
+
import { ab as LearnableWeight, M as UserOutcomeRecord, ac as OrchestrationContext, W as WeightedCard, U as UserDBInterface, C as CourseDBInterface, R as ReplanHints, a1 as StrategyContribution } from '../contentSource-D-LQYFyx.cjs';
|
|
2
|
+
export { K as ActivityRecord, A as AdminDBInterface, v as AssignedCard, g as AssignedContent, u as AssignedCourse, t as AssignedTag, a8 as CardGenerator, aa as CardGeneratorFactory, c as ClassroomDBInterface, F as ClassroomRegistration, E as ClassroomRegistrationDesignation, H as ClassroomRegistrationDoc, e as ContentNavigationStrategyData, f as ContentNavigator, q as ContentSourceID, d as CourseInfo, L as CourseRegistration, s as CourseRegistrationDoc, b as CoursesDBInterface, a9 as GeneratorContext, G as GeneratorResult, P as HYDRATION_MARKER_ID, Q as HydrationMarker, V as NavigatorConstructor, a4 as NavigatorRole, a5 as NavigatorRoles, a3 as Navigators, j as ScheduledCard, I as SessionTrackingData, i as StudentClassroomDBInterface, h as StudyContentSource, k as StudySessionFailedItem, l as StudySessionFailedNewItem, m as StudySessionFailedReviewItem, S as StudySessionItem, n as StudySessionNewItem, o as StudySessionReviewItem, T as TeacherClassroomDBInterface, J as UserConfig, z as UserCourseSetting, y as UserCourseSettings, x as UserDBAuthenticator, a as UserDBReader, w as UserDBWriter, N as UserHydrationState, O as UserHydrationStatus, B as UsrCrsDataInterface, ad as computeDeviation, af as computeEffectiveWeight, ae as computeSpread, ag as createOrchestrationContext, a2 as getCardOrigin, Y as getRegisteredNavigator, $ as getRegisteredNavigatorNames, _ as getRegisteredNavigatorRole, r as getStudySource, Z as hasRegisteredNavigator, a0 as initializeNavigatorRegistry, a7 as isFilter, a6 as isGenerator, p as isReview, X as registerNavigator } from '../contentSource-D-LQYFyx.cjs';
|
|
3
|
+
export { D as DataLayerProvider } from '../dataLayerProvider-Dd2UcCif.cjs';
|
|
4
4
|
import { D as DocType, d as QuestionRecord, b as DocTypePrefixes, C as CardHistory, c as CardRecord } from '../types-legacy-4tlwHnXo.cjs';
|
|
5
5
|
export { e as CardData, f as CourseListData, h as DataShapeData, g as DisplayableData, F as Field, G as GuestUsername, Q as QualifiedCardID, i as QuestionData, S as SkuilderCourseData, a as Tag, T as TagStub, l as log } from '../types-legacy-4tlwHnXo.cjs';
|
|
6
6
|
import { DataShape, ParsedCard } from '@vue-skuilder/common';
|
package/dist/core/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { ab as LearnableWeight, M as UserOutcomeRecord, ac as OrchestrationContext, W as WeightedCard, U as UserDBInterface, C as CourseDBInterface, R as ReplanHints, a1 as StrategyContribution } from '../contentSource-
|
|
2
|
-
export { K as ActivityRecord, A as AdminDBInterface, v as AssignedCard, g as AssignedContent, u as AssignedCourse, t as AssignedTag, a8 as CardGenerator, aa as CardGeneratorFactory, c as ClassroomDBInterface, F as ClassroomRegistration, E as ClassroomRegistrationDesignation, H as ClassroomRegistrationDoc, e as ContentNavigationStrategyData, f as ContentNavigator, q as ContentSourceID, d as CourseInfo, L as CourseRegistration, s as CourseRegistrationDoc, b as CoursesDBInterface, a9 as GeneratorContext, G as GeneratorResult, P as HYDRATION_MARKER_ID, Q as HydrationMarker, V as NavigatorConstructor, a4 as NavigatorRole, a5 as NavigatorRoles, a3 as Navigators, j as ScheduledCard, I as SessionTrackingData, i as StudentClassroomDBInterface, h as StudyContentSource, k as StudySessionFailedItem, l as StudySessionFailedNewItem, m as StudySessionFailedReviewItem, S as StudySessionItem, n as StudySessionNewItem, o as StudySessionReviewItem, T as TeacherClassroomDBInterface, J as UserConfig, z as UserCourseSetting, y as UserCourseSettings, x as UserDBAuthenticator, a as UserDBReader, w as UserDBWriter, N as UserHydrationState, O as UserHydrationStatus, B as UsrCrsDataInterface, ad as computeDeviation, af as computeEffectiveWeight, ae as computeSpread, ag as createOrchestrationContext, a2 as getCardOrigin, Y as getRegisteredNavigator, $ as getRegisteredNavigatorNames, _ as getRegisteredNavigatorRole, r as getStudySource, Z as hasRegisteredNavigator, a0 as initializeNavigatorRegistry, a7 as isFilter, a6 as isGenerator, p as isReview, X as registerNavigator } from '../contentSource-
|
|
3
|
-
export { D as DataLayerProvider } from '../dataLayerProvider-
|
|
1
|
+
import { ab as LearnableWeight, M as UserOutcomeRecord, ac as OrchestrationContext, W as WeightedCard, U as UserDBInterface, C as CourseDBInterface, R as ReplanHints, a1 as StrategyContribution } from '../contentSource-BDRX_YYJ.js';
|
|
2
|
+
export { K as ActivityRecord, A as AdminDBInterface, v as AssignedCard, g as AssignedContent, u as AssignedCourse, t as AssignedTag, a8 as CardGenerator, aa as CardGeneratorFactory, c as ClassroomDBInterface, F as ClassroomRegistration, E as ClassroomRegistrationDesignation, H as ClassroomRegistrationDoc, e as ContentNavigationStrategyData, f as ContentNavigator, q as ContentSourceID, d as CourseInfo, L as CourseRegistration, s as CourseRegistrationDoc, b as CoursesDBInterface, a9 as GeneratorContext, G as GeneratorResult, P as HYDRATION_MARKER_ID, Q as HydrationMarker, V as NavigatorConstructor, a4 as NavigatorRole, a5 as NavigatorRoles, a3 as Navigators, j as ScheduledCard, I as SessionTrackingData, i as StudentClassroomDBInterface, h as StudyContentSource, k as StudySessionFailedItem, l as StudySessionFailedNewItem, m as StudySessionFailedReviewItem, S as StudySessionItem, n as StudySessionNewItem, o as StudySessionReviewItem, T as TeacherClassroomDBInterface, J as UserConfig, z as UserCourseSetting, y as UserCourseSettings, x as UserDBAuthenticator, a as UserDBReader, w as UserDBWriter, N as UserHydrationState, O as UserHydrationStatus, B as UsrCrsDataInterface, ad as computeDeviation, af as computeEffectiveWeight, ae as computeSpread, ag as createOrchestrationContext, a2 as getCardOrigin, Y as getRegisteredNavigator, $ as getRegisteredNavigatorNames, _ as getRegisteredNavigatorRole, r as getStudySource, Z as hasRegisteredNavigator, a0 as initializeNavigatorRegistry, a7 as isFilter, a6 as isGenerator, p as isReview, X as registerNavigator } from '../contentSource-BDRX_YYJ.js';
|
|
3
|
+
export { D as DataLayerProvider } from '../dataLayerProvider-Dpshuql4.js';
|
|
4
4
|
import { D as DocType, d as QuestionRecord, b as DocTypePrefixes, C as CardHistory, c as CardRecord } from '../types-legacy-4tlwHnXo.js';
|
|
5
5
|
export { e as CardData, f as CourseListData, h as DataShapeData, g as DisplayableData, F as Field, G as GuestUsername, Q as QualifiedCardID, i as QuestionData, S as SkuilderCourseData, a as Tag, T as TagStub, l as log } from '../types-legacy-4tlwHnXo.js';
|
|
6
6
|
import { DataShape, ParsedCard } from '@vue-skuilder/common';
|
package/dist/core/index.js
CHANGED
|
@@ -6919,16 +6919,44 @@ var init_BaseUserDB = __esm({
|
|
|
6919
6919
|
// Database to use for write operations (local-first approach)
|
|
6920
6920
|
updateQueue;
|
|
6921
6921
|
_hydration = { state: "not-required" };
|
|
6922
|
+
/** In-flight hydration, so concurrent callers can wait for a real answer. */
|
|
6923
|
+
_hydrationPromise = null;
|
|
6922
6924
|
/**
|
|
6923
|
-
*
|
|
6925
|
+
* Sequence the local mirror was filled to by a hydration that succeeded in
|
|
6926
|
+
* THIS init(), handed to startSync() so the live pull can skip history it
|
|
6927
|
+
* would otherwise re-walk. Undefined whenever there is no such guarantee —
|
|
6928
|
+
* hydration skipped, not required, or failed. Never persisted; see the
|
|
6929
|
+
* `since` note in CouchDBSyncStrategy.startSync().
|
|
6930
|
+
*/
|
|
6931
|
+
_hydratedSeq;
|
|
6932
|
+
/**
|
|
6933
|
+
* How far the local mirror can be trusted, RIGHT NOW. See
|
|
6934
|
+
* {@link UserHydrationStatus}.
|
|
6924
6935
|
*
|
|
6925
|
-
*
|
|
6926
|
-
*
|
|
6927
|
-
*
|
|
6936
|
+
* This is a snapshot, and during login it can legitimately read
|
|
6937
|
+
* `hydrating` — prefer awaitHydration() anywhere a decision hangs on the
|
|
6938
|
+
* answer.
|
|
6928
6939
|
*/
|
|
6929
6940
|
hydrationStatus() {
|
|
6930
6941
|
return { ...this._hydration };
|
|
6931
6942
|
}
|
|
6943
|
+
/**
|
|
6944
|
+
* The hydration status once it has settled — never `hydrating`.
|
|
6945
|
+
*
|
|
6946
|
+
* Resolves immediately unless a pull is in flight. Exists for callers that
|
|
6947
|
+
* must decide something (a route guard, a first-run branch) and would
|
|
6948
|
+
* otherwise sample `hydrating` and guess. Since init() is awaited by both
|
|
6949
|
+
* app startup and login(), that only happens when something navigates
|
|
6950
|
+
* concurrently with a login still in progress — a window that stretches to
|
|
6951
|
+
* HYDRATION_TIMEOUT_MS on a slow connection.
|
|
6952
|
+
*/
|
|
6953
|
+
async awaitHydration() {
|
|
6954
|
+
try {
|
|
6955
|
+
await this._hydrationPromise;
|
|
6956
|
+
} catch {
|
|
6957
|
+
}
|
|
6958
|
+
return this.hydrationStatus();
|
|
6959
|
+
}
|
|
6932
6960
|
/**
|
|
6933
6961
|
* Whether a missing document may be treated as genuinely absent, allowing
|
|
6934
6962
|
* callers to create it with defaults.
|
|
@@ -7385,8 +7413,9 @@ Currently logged-in as ${this._username}.`
|
|
|
7385
7413
|
}
|
|
7386
7414
|
this.syncStrategy.stopSync?.();
|
|
7387
7415
|
this.setDBandQ();
|
|
7388
|
-
|
|
7389
|
-
|
|
7416
|
+
this._hydrationPromise = this.hydrateLocalMirror();
|
|
7417
|
+
await this._hydrationPromise;
|
|
7418
|
+
this.syncStrategy.startSync(this.localDB, this.remoteDB, this._hydratedSeq);
|
|
7390
7419
|
this.applyDesignDocs().catch((error) => {
|
|
7391
7420
|
log3(`Error in applyDesignDocs background task: ${error}`);
|
|
7392
7421
|
if (error && typeof error === "object") {
|
|
@@ -7414,6 +7443,7 @@ Currently logged-in as ${this._username}.`
|
|
|
7414
7443
|
* ceiling). Callers decide what a failure means for them.
|
|
7415
7444
|
*/
|
|
7416
7445
|
async hydrateLocalMirror() {
|
|
7446
|
+
this._hydratedSeq = void 0;
|
|
7417
7447
|
if (this.localDB.name === this.remoteDB.name || !this.syncStrategy.hydrate) {
|
|
7418
7448
|
this._hydration = { state: "not-required" };
|
|
7419
7449
|
return;
|
|
@@ -7425,11 +7455,12 @@ Currently logged-in as ${this._username}.`
|
|
|
7425
7455
|
this._hydration = { state: "hydrating" };
|
|
7426
7456
|
const start = Date.now();
|
|
7427
7457
|
try {
|
|
7428
|
-
const { docsWritten } = await withTimeout(
|
|
7458
|
+
const { docsWritten, lastSeq } = await withTimeout(
|
|
7429
7459
|
this.syncStrategy.hydrate(this.localDB, this.remoteDB),
|
|
7430
7460
|
HYDRATION_TIMEOUT_MS,
|
|
7431
7461
|
`Hydration of local mirror for ${this._username}`
|
|
7432
7462
|
);
|
|
7463
|
+
this._hydratedSeq = lastSeq;
|
|
7433
7464
|
await this.writeHydrationMarker();
|
|
7434
7465
|
this._hydration = {
|
|
7435
7466
|
state: "hydrated",
|