@vue-skuilder/db 0.2.17 → 0.2.19
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-CyATpyLQ.d.cts → SyncStrategy-BJMZq3WO.d.cts} +17 -0
- package/dist/{SyncStrategy-CyATpyLQ.d.ts → SyncStrategy-BJMZq3WO.d.ts} +17 -0
- package/dist/{contentSource-B1p-vdz7.d.ts → contentSource-BDRX_YYJ.d.ts} +97 -1
- package/dist/{contentSource-Brz42x7n.d.cts → contentSource-D-LQYFyx.d.cts} +97 -1
- package/dist/core/index.d.cts +3 -3
- package/dist/core/index.d.ts +3 -3
- package/dist/core/index.js +167 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.mjs +166 -1
- package/dist/core/index.mjs.map +1 -1
- package/dist/{dataLayerProvider-CpwpT1rM.d.cts → dataLayerProvider-Dd2UcCif.d.cts} +1 -1
- package/dist/{dataLayerProvider-BWayUIoK.d.ts → dataLayerProvider-Dpshuql4.d.ts} +1 -1
- package/dist/impl/couch/index.d.cts +16 -3
- package/dist/impl/couch/index.d.ts +16 -3
- package/dist/impl/couch/index.js +199 -3
- package/dist/impl/couch/index.js.map +1 -1
- package/dist/impl/couch/index.mjs +199 -3
- 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 +165 -1
- package/dist/impl/static/index.js.map +1 -1
- package/dist/impl/static/index.mjs +165 -1
- 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 +201 -3
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +200 -3
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -3
- package/src/core/index.ts +1 -0
- package/src/core/interfaces/userDB.ts +24 -0
- package/src/core/types/hydration.ts +78 -0
- package/src/impl/common/BaseUserDB.ts +233 -2
- package/src/impl/common/SyncStrategy.ts +19 -0
- package/src/impl/couch/CouchDBSyncStrategy.ts +54 -4
- package/tests/impl/hydration.test.ts +330 -0
package/dist/index.d.cts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { U as UserDBInterface, s as CourseRegistrationDoc, S as StudySessionItem, W as WeightedCard, R as ReplanHints, h as StudyContentSource, G as GeneratorResult, C as CourseDBInterface } from './contentSource-
|
|
2
|
-
export { K as ActivityRecord, A as AdminDBInterface, v as AssignedCard, g as AssignedContent, u as AssignedCourse, t as AssignedTag,
|
|
3
|
-
import { D as DataLayerProvider } from './dataLayerProvider-
|
|
1
|
+
import { U as UserDBInterface, s as CourseRegistrationDoc, S as StudySessionItem, W as WeightedCard, R as ReplanHints, h as StudyContentSource, G as GeneratorResult, C as CourseDBInterface } 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, b as CoursesDBInterface, ah as DocumentUpdater, a9 as GeneratorContext, P as HYDRATION_MARKER_ID, Q as HydrationMarker, ab as LearnableWeight, V as NavigatorConstructor, a4 as NavigatorRole, a5 as NavigatorRoles, a3 as Navigators, ac as OrchestrationContext, j as ScheduledCard, I as SessionTrackingData, a1 as StrategyContribution, i as StudentClassroomDBInterface, k as StudySessionFailedItem, l as StudySessionFailedNewItem, m as StudySessionFailedReviewItem, 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, M as UserOutcomeRecord, 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, ai as newInterval, X as registerNavigator } from './contentSource-D-LQYFyx.cjs';
|
|
3
|
+
import { D as DataLayerProvider } from './dataLayerProvider-Dd2UcCif.cjs';
|
|
4
4
|
import { C as CardHistory, c as CardRecord, d as QuestionRecord } from './types-legacy-4tlwHnXo.cjs';
|
|
5
5
|
export { e as CardData, f as CourseListData, h as DataShapeData, g as DisplayableData, D as DocType, b as DocTypePrefixes, 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 { SrsBacklogDebug, StrategyPressureDebug, Loggable } from './core/index.cjs';
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { U as UserDBInterface, s as CourseRegistrationDoc, S as StudySessionItem, W as WeightedCard, R as ReplanHints, h as StudyContentSource, G as GeneratorResult, C as CourseDBInterface } from './contentSource-
|
|
2
|
-
export { K as ActivityRecord, A as AdminDBInterface, v as AssignedCard, g as AssignedContent, u as AssignedCourse, t as AssignedTag,
|
|
3
|
-
import { D as DataLayerProvider } from './dataLayerProvider-
|
|
1
|
+
import { U as UserDBInterface, s as CourseRegistrationDoc, S as StudySessionItem, W as WeightedCard, R as ReplanHints, h as StudyContentSource, G as GeneratorResult, C as CourseDBInterface } 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, b as CoursesDBInterface, ah as DocumentUpdater, a9 as GeneratorContext, P as HYDRATION_MARKER_ID, Q as HydrationMarker, ab as LearnableWeight, V as NavigatorConstructor, a4 as NavigatorRole, a5 as NavigatorRoles, a3 as Navigators, ac as OrchestrationContext, j as ScheduledCard, I as SessionTrackingData, a1 as StrategyContribution, i as StudentClassroomDBInterface, k as StudySessionFailedItem, l as StudySessionFailedNewItem, m as StudySessionFailedReviewItem, 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, M as UserOutcomeRecord, 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, ai as newInterval, X as registerNavigator } from './contentSource-BDRX_YYJ.js';
|
|
3
|
+
import { D as DataLayerProvider } from './dataLayerProvider-Dpshuql4.js';
|
|
4
4
|
import { C as CardHistory, c as CardRecord, d as QuestionRecord } from './types-legacy-4tlwHnXo.js';
|
|
5
5
|
export { e as CardData, f as CourseListData, h as DataShapeData, g as DisplayableData, D as DocType, b as DocTypePrefixes, 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 { SrsBacklogDebug, StrategyPressureDebug, Loggable } from './core/index.js';
|
package/dist/index.js
CHANGED
|
@@ -7357,6 +7357,8 @@ var init_CouchDBSyncStrategy = __esm({
|
|
|
7357
7357
|
CouchDBSyncStrategy = class {
|
|
7358
7358
|
syncHandle;
|
|
7359
7359
|
// Handle to cancel sync if needed
|
|
7360
|
+
/** In-flight one-shot hydration pull, so a timed-out pull can be abandoned. */
|
|
7361
|
+
hydrationHandle;
|
|
7360
7362
|
setupRemoteDB(username) {
|
|
7361
7363
|
if (username === GuestUsername || username.startsWith(GuestUsername)) {
|
|
7362
7364
|
return getLocalUserDB(username);
|
|
@@ -7371,8 +7373,26 @@ var init_CouchDBSyncStrategy = __esm({
|
|
|
7371
7373
|
return this.getUserDB(username);
|
|
7372
7374
|
}
|
|
7373
7375
|
}
|
|
7376
|
+
/**
|
|
7377
|
+
* One-shot remote → local pull. Resolves once the local mirror is caught up.
|
|
7378
|
+
*
|
|
7379
|
+
* Pull only: pushing here would upload whatever the local DB happens to hold
|
|
7380
|
+
* before we know what the remote already has, which is the conflict-leaf
|
|
7381
|
+
* problem hydration exists to avoid. Local changes go up when startSync()
|
|
7382
|
+
* takes over.
|
|
7383
|
+
*/
|
|
7384
|
+
async hydrate(localDB, remoteDB) {
|
|
7385
|
+
const replication = pouchdb_setup_default.replicate(remoteDB, localDB, {});
|
|
7386
|
+
this.hydrationHandle = replication;
|
|
7387
|
+
try {
|
|
7388
|
+
const info = await replication;
|
|
7389
|
+
return { docsWritten: info.docs_written };
|
|
7390
|
+
} finally {
|
|
7391
|
+
this.hydrationHandle = void 0;
|
|
7392
|
+
}
|
|
7393
|
+
}
|
|
7374
7394
|
startSync(localDB, remoteDB) {
|
|
7375
|
-
if (localDB !== remoteDB) {
|
|
7395
|
+
if (localDB.name !== remoteDB.name) {
|
|
7376
7396
|
this.syncHandle = pouchdb_setup_default.sync(localDB, remoteDB, {
|
|
7377
7397
|
live: true,
|
|
7378
7398
|
retry: true
|
|
@@ -7380,8 +7400,20 @@ var init_CouchDBSyncStrategy = __esm({
|
|
|
7380
7400
|
}
|
|
7381
7401
|
}
|
|
7382
7402
|
stopSync() {
|
|
7403
|
+
if (this.hydrationHandle) {
|
|
7404
|
+
try {
|
|
7405
|
+
this.hydrationHandle.cancel();
|
|
7406
|
+
} catch (e) {
|
|
7407
|
+
logger.warn(`Failed to cancel hydration pull (continuing anyway): ${e}`);
|
|
7408
|
+
}
|
|
7409
|
+
this.hydrationHandle = void 0;
|
|
7410
|
+
}
|
|
7383
7411
|
if (this.syncHandle) {
|
|
7384
|
-
|
|
7412
|
+
try {
|
|
7413
|
+
this.syncHandle.cancel();
|
|
7414
|
+
} catch (e) {
|
|
7415
|
+
logger.warn(`Failed to cancel user sync (continuing anyway): ${e}`);
|
|
7416
|
+
}
|
|
7385
7417
|
this.syncHandle = void 0;
|
|
7386
7418
|
}
|
|
7387
7419
|
}
|
|
@@ -7650,6 +7682,19 @@ var init_couch = __esm({
|
|
|
7650
7682
|
});
|
|
7651
7683
|
|
|
7652
7684
|
// src/impl/common/BaseUserDB.ts
|
|
7685
|
+
function withTimeout(p, ms, label) {
|
|
7686
|
+
let timer;
|
|
7687
|
+
return Promise.race([
|
|
7688
|
+
p,
|
|
7689
|
+
new Promise((_resolve, reject) => {
|
|
7690
|
+
timer = setTimeout(() => reject(new Error(`${label} timed out after ${ms}ms`)), ms);
|
|
7691
|
+
})
|
|
7692
|
+
]).finally(() => {
|
|
7693
|
+
if (timer !== void 0) {
|
|
7694
|
+
clearTimeout(timer);
|
|
7695
|
+
}
|
|
7696
|
+
});
|
|
7697
|
+
}
|
|
7653
7698
|
function accomodateGuest() {
|
|
7654
7699
|
logger.log("[funnel] accomodateGuest() called");
|
|
7655
7700
|
if (typeof localStorage === "undefined") {
|
|
@@ -7823,7 +7868,7 @@ async function dropUserFromClassroom(user, classID) {
|
|
|
7823
7868
|
async function getUserClassrooms(user) {
|
|
7824
7869
|
return getOrCreateClassroomRegistrationsDoc(user);
|
|
7825
7870
|
}
|
|
7826
|
-
var import_common12, import_moment6, log4, BaseUser, userCoursesDoc, userClassroomsDoc;
|
|
7871
|
+
var import_common12, import_moment6, log4, HYDRATION_TIMEOUT_MS, BaseUser, userCoursesDoc, userClassroomsDoc;
|
|
7827
7872
|
var init_BaseUserDB = __esm({
|
|
7828
7873
|
"src/impl/common/BaseUserDB.ts"() {
|
|
7829
7874
|
"use strict";
|
|
@@ -7840,6 +7885,7 @@ var init_BaseUserDB = __esm({
|
|
|
7840
7885
|
log4 = (s) => {
|
|
7841
7886
|
logger.info(s);
|
|
7842
7887
|
};
|
|
7888
|
+
HYDRATION_TIMEOUT_MS = 15e3;
|
|
7843
7889
|
BaseUser = class _BaseUser {
|
|
7844
7890
|
static _instance;
|
|
7845
7891
|
static _initialized = false;
|
|
@@ -7868,6 +7914,49 @@ var init_BaseUserDB = __esm({
|
|
|
7868
7914
|
writeDB;
|
|
7869
7915
|
// Database to use for write operations (local-first approach)
|
|
7870
7916
|
updateQueue;
|
|
7917
|
+
_hydration = { state: "not-required" };
|
|
7918
|
+
/** In-flight hydration, so concurrent callers can wait for a real answer. */
|
|
7919
|
+
_hydrationPromise = null;
|
|
7920
|
+
/**
|
|
7921
|
+
* How far the local mirror can be trusted, RIGHT NOW. See
|
|
7922
|
+
* {@link UserHydrationStatus}.
|
|
7923
|
+
*
|
|
7924
|
+
* This is a snapshot, and during login it can legitimately read
|
|
7925
|
+
* `hydrating` — prefer awaitHydration() anywhere a decision hangs on the
|
|
7926
|
+
* answer.
|
|
7927
|
+
*/
|
|
7928
|
+
hydrationStatus() {
|
|
7929
|
+
return { ...this._hydration };
|
|
7930
|
+
}
|
|
7931
|
+
/**
|
|
7932
|
+
* The hydration status once it has settled — never `hydrating`.
|
|
7933
|
+
*
|
|
7934
|
+
* Resolves immediately unless a pull is in flight. Exists for callers that
|
|
7935
|
+
* must decide something (a route guard, a first-run branch) and would
|
|
7936
|
+
* otherwise sample `hydrating` and guess. Since init() is awaited by both
|
|
7937
|
+
* app startup and login(), that only happens when something navigates
|
|
7938
|
+
* concurrently with a login still in progress — a window that stretches to
|
|
7939
|
+
* HYDRATION_TIMEOUT_MS on a slow connection.
|
|
7940
|
+
*/
|
|
7941
|
+
async awaitHydration() {
|
|
7942
|
+
try {
|
|
7943
|
+
await this._hydrationPromise;
|
|
7944
|
+
} catch {
|
|
7945
|
+
}
|
|
7946
|
+
return this.hydrationStatus();
|
|
7947
|
+
}
|
|
7948
|
+
/**
|
|
7949
|
+
* Whether a missing document may be treated as genuinely absent, allowing
|
|
7950
|
+
* callers to create it with defaults.
|
|
7951
|
+
*
|
|
7952
|
+
* False only when hydration failed or is still running: a 404 then may just
|
|
7953
|
+
* mean "not pulled down yet", and materializing a default would both lie to
|
|
7954
|
+
* the user and, once live sync starts, push a rev-1 document that loses to
|
|
7955
|
+
* the real one — silently discarding it.
|
|
7956
|
+
*/
|
|
7957
|
+
canMaterializeDefaults() {
|
|
7958
|
+
return this._hydration.state !== "failed" && this._hydration.state !== "hydrating";
|
|
7959
|
+
}
|
|
7871
7960
|
async createAccount(username, password) {
|
|
7872
7961
|
if (!this.syncStrategy.canCreateAccount()) {
|
|
7873
7962
|
throw new Error("Account creation not supported by current sync strategy");
|
|
@@ -7985,6 +8074,11 @@ Currently logged-in as ${this._username}.`
|
|
|
7985
8074
|
} catch (e) {
|
|
7986
8075
|
const err = e;
|
|
7987
8076
|
if (err.status === 404) {
|
|
8077
|
+
if (!this.canMaterializeDefaults()) {
|
|
8078
|
+
throw new Error(
|
|
8079
|
+
`Cannot read course registrations for ${this._username}: the local mirror is unavailable (hydration state '${this._hydration.state}'). Refusing to create an empty registration doc \u2014 that would report an existing user as unregistered, and then lose to their real document once sync resumes.`
|
|
8080
|
+
);
|
|
8081
|
+
}
|
|
7988
8082
|
await this.localDB.put({
|
|
7989
8083
|
_id: _BaseUser.DOC_IDS.COURSE_REGISTRATIONS,
|
|
7990
8084
|
courses: [],
|
|
@@ -8227,6 +8321,11 @@ Currently logged-in as ${this._username}.`
|
|
|
8227
8321
|
} catch (e) {
|
|
8228
8322
|
const err = e;
|
|
8229
8323
|
if (err.name && err.name === "not_found") {
|
|
8324
|
+
if (!this.canMaterializeDefaults()) {
|
|
8325
|
+
throw new Error(
|
|
8326
|
+
`Cannot read config for ${this._username}: the local mirror is unavailable (hydration state '${this._hydration.state}'). Refusing to write a default config over what may be an existing one.`
|
|
8327
|
+
);
|
|
8328
|
+
}
|
|
8230
8329
|
await this.localDB.put(defaultConfig);
|
|
8231
8330
|
return this.getConfig();
|
|
8232
8331
|
} else {
|
|
@@ -8300,7 +8399,10 @@ Currently logged-in as ${this._username}.`
|
|
|
8300
8399
|
_BaseUser._initialized = true;
|
|
8301
8400
|
return;
|
|
8302
8401
|
}
|
|
8402
|
+
this.syncStrategy.stopSync?.();
|
|
8303
8403
|
this.setDBandQ();
|
|
8404
|
+
this._hydrationPromise = this.hydrateLocalMirror();
|
|
8405
|
+
await this._hydrationPromise;
|
|
8304
8406
|
this.syncStrategy.startSync(this.localDB, this.remoteDB);
|
|
8305
8407
|
this.applyDesignDocs().catch((error) => {
|
|
8306
8408
|
log4(`Error in applyDesignDocs background task: ${error}`);
|
|
@@ -8316,6 +8418,85 @@ Currently logged-in as ${this._username}.`
|
|
|
8316
8418
|
});
|
|
8317
8419
|
_BaseUser._initialized = true;
|
|
8318
8420
|
}
|
|
8421
|
+
/**
|
|
8422
|
+
* Pull this account's documents into the local mirror, if that hasn't
|
|
8423
|
+
* happened on this device before.
|
|
8424
|
+
*
|
|
8425
|
+
* Runs between setDBandQ() and startSync() — after the handles exist, before
|
|
8426
|
+
* anything can observe an empty local DB or replicate one upward.
|
|
8427
|
+
*
|
|
8428
|
+
* Never throws: a failure is recorded as `failed` state and reported through
|
|
8429
|
+
* hydrationStatus(), because throwing here would abort init() and leave
|
|
8430
|
+
* BaseUser._initialized false forever (BaseUser.instance() polls it with no
|
|
8431
|
+
* ceiling). Callers decide what a failure means for them.
|
|
8432
|
+
*/
|
|
8433
|
+
async hydrateLocalMirror() {
|
|
8434
|
+
if (this.localDB.name === this.remoteDB.name || !this.syncStrategy.hydrate) {
|
|
8435
|
+
this._hydration = { state: "not-required" };
|
|
8436
|
+
return;
|
|
8437
|
+
}
|
|
8438
|
+
if (await this.hasHydrationMarker()) {
|
|
8439
|
+
this._hydration = { state: "stale" };
|
|
8440
|
+
return;
|
|
8441
|
+
}
|
|
8442
|
+
this._hydration = { state: "hydrating" };
|
|
8443
|
+
const start = Date.now();
|
|
8444
|
+
try {
|
|
8445
|
+
const { docsWritten } = await withTimeout(
|
|
8446
|
+
this.syncStrategy.hydrate(this.localDB, this.remoteDB),
|
|
8447
|
+
HYDRATION_TIMEOUT_MS,
|
|
8448
|
+
`Hydration of local mirror for ${this._username}`
|
|
8449
|
+
);
|
|
8450
|
+
await this.writeHydrationMarker();
|
|
8451
|
+
this._hydration = {
|
|
8452
|
+
state: "hydrated",
|
|
8453
|
+
docsWritten,
|
|
8454
|
+
durationMs: Date.now() - start
|
|
8455
|
+
};
|
|
8456
|
+
log4(
|
|
8457
|
+
`Hydrated local mirror for ${this._username}: ${docsWritten} docs in ${this._hydration.durationMs}ms`
|
|
8458
|
+
);
|
|
8459
|
+
} catch (e) {
|
|
8460
|
+
this.syncStrategy.stopSync?.();
|
|
8461
|
+
this._hydration = {
|
|
8462
|
+
state: "failed",
|
|
8463
|
+
durationMs: Date.now() - start,
|
|
8464
|
+
error: e instanceof Error ? e.message : String(e)
|
|
8465
|
+
};
|
|
8466
|
+
logger.error(`Failed to hydrate local mirror for ${this._username}:`, e);
|
|
8467
|
+
}
|
|
8468
|
+
}
|
|
8469
|
+
/**
|
|
8470
|
+
* Has a full pull completed on this device for the CURRENT account?
|
|
8471
|
+
*
|
|
8472
|
+
* The marker is a `_local/` document, which never replicates — so its
|
|
8473
|
+
* presence describes this device's mirror and cannot arrive from elsewhere.
|
|
8474
|
+
*/
|
|
8475
|
+
async hasHydrationMarker() {
|
|
8476
|
+
try {
|
|
8477
|
+
const marker = await this.localDB.get(HYDRATION_MARKER_ID);
|
|
8478
|
+
return marker.username === this._username;
|
|
8479
|
+
} catch {
|
|
8480
|
+
return false;
|
|
8481
|
+
}
|
|
8482
|
+
}
|
|
8483
|
+
async writeHydrationMarker() {
|
|
8484
|
+
try {
|
|
8485
|
+
let existingRev;
|
|
8486
|
+
try {
|
|
8487
|
+
existingRev = (await this.localDB.get(HYDRATION_MARKER_ID))._rev;
|
|
8488
|
+
} catch {
|
|
8489
|
+
}
|
|
8490
|
+
await this.localDB.put({
|
|
8491
|
+
_id: HYDRATION_MARKER_ID,
|
|
8492
|
+
_rev: existingRev,
|
|
8493
|
+
username: this._username,
|
|
8494
|
+
hydratedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
8495
|
+
});
|
|
8496
|
+
} catch (e) {
|
|
8497
|
+
logger.warn(`Could not write hydration marker for ${this._username}: ${e}`);
|
|
8498
|
+
}
|
|
8499
|
+
}
|
|
8319
8500
|
static designDocs = [
|
|
8320
8501
|
{
|
|
8321
8502
|
_id: "_design/reviewCards",
|
|
@@ -8652,6 +8833,11 @@ Currently logged-in as ${this._username}.`
|
|
|
8652
8833
|
} catch (e) {
|
|
8653
8834
|
const err = e;
|
|
8654
8835
|
if (err.status === 404) {
|
|
8836
|
+
if (!this.canMaterializeDefaults()) {
|
|
8837
|
+
throw new Error(
|
|
8838
|
+
`Cannot read strategy state '${strategyKey}' for ${this._username}: the local mirror is unavailable (hydration state '${this._hydration.state}').`
|
|
8839
|
+
);
|
|
8840
|
+
}
|
|
8655
8841
|
return null;
|
|
8656
8842
|
}
|
|
8657
8843
|
throw e;
|
|
@@ -10172,6 +10358,15 @@ var init_userOutcome = __esm({
|
|
|
10172
10358
|
}
|
|
10173
10359
|
});
|
|
10174
10360
|
|
|
10361
|
+
// src/core/types/hydration.ts
|
|
10362
|
+
var HYDRATION_MARKER_ID;
|
|
10363
|
+
var init_hydration = __esm({
|
|
10364
|
+
"src/core/types/hydration.ts"() {
|
|
10365
|
+
"use strict";
|
|
10366
|
+
HYDRATION_MARKER_ID = "_local/hydration";
|
|
10367
|
+
}
|
|
10368
|
+
});
|
|
10369
|
+
|
|
10175
10370
|
// src/core/bulkImport/cardProcessor.ts
|
|
10176
10371
|
async function importParsedCards(parsedCards, courseDB, config) {
|
|
10177
10372
|
const results = [];
|
|
@@ -10652,6 +10847,7 @@ var init_core = __esm({
|
|
|
10652
10847
|
init_user();
|
|
10653
10848
|
init_strategyState();
|
|
10654
10849
|
init_userOutcome();
|
|
10850
|
+
init_hydration();
|
|
10655
10851
|
init_Loggable();
|
|
10656
10852
|
init_util();
|
|
10657
10853
|
init_navigators();
|
|
@@ -10674,6 +10870,7 @@ __export(index_exports, {
|
|
|
10674
10870
|
ENV: () => ENV,
|
|
10675
10871
|
FileSystemError: () => FileSystemError,
|
|
10676
10872
|
GuestUsername: () => GuestUsername,
|
|
10873
|
+
HYDRATION_MARKER_ID: () => HYDRATION_MARKER_ID,
|
|
10677
10874
|
Loggable: () => Loggable,
|
|
10678
10875
|
NOT_SET: () => NOT_SET,
|
|
10679
10876
|
NavigatorRole: () => NavigatorRole,
|
|
@@ -15454,6 +15651,7 @@ init_userDBHelpers();
|
|
|
15454
15651
|
ENV,
|
|
15455
15652
|
FileSystemError,
|
|
15456
15653
|
GuestUsername,
|
|
15654
|
+
HYDRATION_MARKER_ID,
|
|
15457
15655
|
Loggable,
|
|
15458
15656
|
NOT_SET,
|
|
15459
15657
|
NavigatorRole,
|