@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
|
@@ -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-D-LQYFyx.cjs';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Main factory interface for data access
|
|
@@ -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-BDRX_YYJ.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Main factory interface for data access
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { T as TagStub, a as Tag, S as SkuilderCourseData, Q as QualifiedCardID } from '../../types-legacy-4tlwHnXo.cjs';
|
|
2
2
|
import { Moment } from 'moment';
|
|
3
|
-
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-
|
|
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, 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-D-LQYFyx.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-D-LQYFyx.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
|
-
import { S as SyncStrategy, A as AccountCreationResult, a as AuthenticationResult } from '../../SyncStrategy-
|
|
7
|
+
import { S as SyncStrategy, A as AccountCreationResult, a as AuthenticationResult } from '../../SyncStrategy-CCU1H81I.cjs';
|
|
8
8
|
|
|
9
9
|
type NamespacedDatashape = string;
|
|
10
10
|
interface DataShape55 {
|
|
@@ -508,11 +508,27 @@ declare class CouchDBSyncStrategy implements SyncStrategy {
|
|
|
508
508
|
* before we know what the remote already has, which is the conflict-leaf
|
|
509
509
|
* problem hydration exists to avoid. Local changes go up when startSync()
|
|
510
510
|
* takes over.
|
|
511
|
+
*
|
|
512
|
+
* Into an EMPTY local DB this skips deleted documents, because a mirror that
|
|
513
|
+
* never held a document does not need to be told it was removed. That
|
|
514
|
+
* matters more than it sounds: scheduled reviews (`card_review_*`) are
|
|
515
|
+
* created and deleted once per review completed, so the changes feed is
|
|
516
|
+
* dominated by tombstones and grows without bound, while the durable record
|
|
517
|
+
* lives in card history. One real account measured 385 live documents behind
|
|
518
|
+
* 8,370 deletions — an unfiltered pull moved 9,520 documents and took ~20s,
|
|
519
|
+
* blowing the hydration timeout on a phone; filtered, the same pull moves
|
|
520
|
+
* 404 and takes ~2s, reaching a byte-identical local state.
|
|
521
|
+
*
|
|
522
|
+
* The filter runs server-side, so `last_seq` still reports the source's true
|
|
523
|
+
* sequence. Returning it lets startSync() begin the live pull from there
|
|
524
|
+
* instead of re-walking the same history in the background — without that,
|
|
525
|
+
* the cost is merely deferred, not removed.
|
|
511
526
|
*/
|
|
512
527
|
hydrate(localDB: PouchDB.Database, remoteDB: PouchDB.Database): Promise<{
|
|
513
528
|
docsWritten: number;
|
|
529
|
+
lastSeq?: string | number;
|
|
514
530
|
}>;
|
|
515
|
-
startSync(localDB: PouchDB.Database, remoteDB: PouchDB.Database): void;
|
|
531
|
+
startSync(localDB: PouchDB.Database, remoteDB: PouchDB.Database, since?: string | number): void;
|
|
516
532
|
stopSync?(): void;
|
|
517
533
|
canCreateAccount(): boolean;
|
|
518
534
|
canAuthenticate(): boolean;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { T as TagStub, a as Tag, S as SkuilderCourseData, Q as QualifiedCardID } from '../../types-legacy-4tlwHnXo.js';
|
|
2
2
|
import { Moment } from 'moment';
|
|
3
|
-
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-
|
|
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, 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-BDRX_YYJ.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-BDRX_YYJ.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
|
-
import { S as SyncStrategy, A as AccountCreationResult, a as AuthenticationResult } from '../../SyncStrategy-
|
|
7
|
+
import { S as SyncStrategy, A as AccountCreationResult, a as AuthenticationResult } from '../../SyncStrategy-CCU1H81I.js';
|
|
8
8
|
|
|
9
9
|
type NamespacedDatashape = string;
|
|
10
10
|
interface DataShape55 {
|
|
@@ -508,11 +508,27 @@ declare class CouchDBSyncStrategy implements SyncStrategy {
|
|
|
508
508
|
* before we know what the remote already has, which is the conflict-leaf
|
|
509
509
|
* problem hydration exists to avoid. Local changes go up when startSync()
|
|
510
510
|
* takes over.
|
|
511
|
+
*
|
|
512
|
+
* Into an EMPTY local DB this skips deleted documents, because a mirror that
|
|
513
|
+
* never held a document does not need to be told it was removed. That
|
|
514
|
+
* matters more than it sounds: scheduled reviews (`card_review_*`) are
|
|
515
|
+
* created and deleted once per review completed, so the changes feed is
|
|
516
|
+
* dominated by tombstones and grows without bound, while the durable record
|
|
517
|
+
* lives in card history. One real account measured 385 live documents behind
|
|
518
|
+
* 8,370 deletions — an unfiltered pull moved 9,520 documents and took ~20s,
|
|
519
|
+
* blowing the hydration timeout on a phone; filtered, the same pull moves
|
|
520
|
+
* 404 and takes ~2s, reaching a byte-identical local state.
|
|
521
|
+
*
|
|
522
|
+
* The filter runs server-side, so `last_seq` still reports the source's true
|
|
523
|
+
* sequence. Returning it lets startSync() begin the live pull from there
|
|
524
|
+
* instead of re-walking the same history in the background — without that,
|
|
525
|
+
* the cost is merely deferred, not removed.
|
|
511
526
|
*/
|
|
512
527
|
hydrate(localDB: PouchDB.Database, remoteDB: PouchDB.Database): Promise<{
|
|
513
528
|
docsWritten: number;
|
|
529
|
+
lastSeq?: string | number;
|
|
514
530
|
}>;
|
|
515
|
-
startSync(localDB: PouchDB.Database, remoteDB: PouchDB.Database): void;
|
|
531
|
+
startSync(localDB: PouchDB.Database, remoteDB: PouchDB.Database, since?: string | number): void;
|
|
516
532
|
stopSync?(): void;
|
|
517
533
|
canCreateAccount(): boolean;
|
|
518
534
|
canAuthenticate(): boolean;
|
package/dist/impl/couch/index.js
CHANGED
|
@@ -7561,16 +7561,44 @@ var init_BaseUserDB = __esm({
|
|
|
7561
7561
|
// Database to use for write operations (local-first approach)
|
|
7562
7562
|
updateQueue;
|
|
7563
7563
|
_hydration = { state: "not-required" };
|
|
7564
|
+
/** In-flight hydration, so concurrent callers can wait for a real answer. */
|
|
7565
|
+
_hydrationPromise = null;
|
|
7564
7566
|
/**
|
|
7565
|
-
*
|
|
7567
|
+
* Sequence the local mirror was filled to by a hydration that succeeded in
|
|
7568
|
+
* THIS init(), handed to startSync() so the live pull can skip history it
|
|
7569
|
+
* would otherwise re-walk. Undefined whenever there is no such guarantee —
|
|
7570
|
+
* hydration skipped, not required, or failed. Never persisted; see the
|
|
7571
|
+
* `since` note in CouchDBSyncStrategy.startSync().
|
|
7572
|
+
*/
|
|
7573
|
+
_hydratedSeq;
|
|
7574
|
+
/**
|
|
7575
|
+
* How far the local mirror can be trusted, RIGHT NOW. See
|
|
7576
|
+
* {@link UserHydrationStatus}.
|
|
7566
7577
|
*
|
|
7567
|
-
*
|
|
7568
|
-
*
|
|
7569
|
-
*
|
|
7578
|
+
* This is a snapshot, and during login it can legitimately read
|
|
7579
|
+
* `hydrating` — prefer awaitHydration() anywhere a decision hangs on the
|
|
7580
|
+
* answer.
|
|
7570
7581
|
*/
|
|
7571
7582
|
hydrationStatus() {
|
|
7572
7583
|
return { ...this._hydration };
|
|
7573
7584
|
}
|
|
7585
|
+
/**
|
|
7586
|
+
* The hydration status once it has settled — never `hydrating`.
|
|
7587
|
+
*
|
|
7588
|
+
* Resolves immediately unless a pull is in flight. Exists for callers that
|
|
7589
|
+
* must decide something (a route guard, a first-run branch) and would
|
|
7590
|
+
* otherwise sample `hydrating` and guess. Since init() is awaited by both
|
|
7591
|
+
* app startup and login(), that only happens when something navigates
|
|
7592
|
+
* concurrently with a login still in progress — a window that stretches to
|
|
7593
|
+
* HYDRATION_TIMEOUT_MS on a slow connection.
|
|
7594
|
+
*/
|
|
7595
|
+
async awaitHydration() {
|
|
7596
|
+
try {
|
|
7597
|
+
await this._hydrationPromise;
|
|
7598
|
+
} catch {
|
|
7599
|
+
}
|
|
7600
|
+
return this.hydrationStatus();
|
|
7601
|
+
}
|
|
7574
7602
|
/**
|
|
7575
7603
|
* Whether a missing document may be treated as genuinely absent, allowing
|
|
7576
7604
|
* callers to create it with defaults.
|
|
@@ -8027,8 +8055,9 @@ Currently logged-in as ${this._username}.`
|
|
|
8027
8055
|
}
|
|
8028
8056
|
this.syncStrategy.stopSync?.();
|
|
8029
8057
|
this.setDBandQ();
|
|
8030
|
-
|
|
8031
|
-
|
|
8058
|
+
this._hydrationPromise = this.hydrateLocalMirror();
|
|
8059
|
+
await this._hydrationPromise;
|
|
8060
|
+
this.syncStrategy.startSync(this.localDB, this.remoteDB, this._hydratedSeq);
|
|
8032
8061
|
this.applyDesignDocs().catch((error) => {
|
|
8033
8062
|
log3(`Error in applyDesignDocs background task: ${error}`);
|
|
8034
8063
|
if (error && typeof error === "object") {
|
|
@@ -8056,6 +8085,7 @@ Currently logged-in as ${this._username}.`
|
|
|
8056
8085
|
* ceiling). Callers decide what a failure means for them.
|
|
8057
8086
|
*/
|
|
8058
8087
|
async hydrateLocalMirror() {
|
|
8088
|
+
this._hydratedSeq = void 0;
|
|
8059
8089
|
if (this.localDB.name === this.remoteDB.name || !this.syncStrategy.hydrate) {
|
|
8060
8090
|
this._hydration = { state: "not-required" };
|
|
8061
8091
|
return;
|
|
@@ -8067,11 +8097,12 @@ Currently logged-in as ${this._username}.`
|
|
|
8067
8097
|
this._hydration = { state: "hydrating" };
|
|
8068
8098
|
const start = Date.now();
|
|
8069
8099
|
try {
|
|
8070
|
-
const { docsWritten } = await withTimeout(
|
|
8100
|
+
const { docsWritten, lastSeq } = await withTimeout(
|
|
8071
8101
|
this.syncStrategy.hydrate(this.localDB, this.remoteDB),
|
|
8072
8102
|
HYDRATION_TIMEOUT_MS,
|
|
8073
8103
|
`Hydration of local mirror for ${this._username}`
|
|
8074
8104
|
);
|
|
8105
|
+
this._hydratedSeq = lastSeq;
|
|
8075
8106
|
await this.writeHydrationMarker();
|
|
8076
8107
|
this._hydration = {
|
|
8077
8108
|
state: "hydrated",
|
|
@@ -9010,22 +9041,60 @@ var init_CouchDBSyncStrategy = __esm({
|
|
|
9010
9041
|
* before we know what the remote already has, which is the conflict-leaf
|
|
9011
9042
|
* problem hydration exists to avoid. Local changes go up when startSync()
|
|
9012
9043
|
* takes over.
|
|
9044
|
+
*
|
|
9045
|
+
* Into an EMPTY local DB this skips deleted documents, because a mirror that
|
|
9046
|
+
* never held a document does not need to be told it was removed. That
|
|
9047
|
+
* matters more than it sounds: scheduled reviews (`card_review_*`) are
|
|
9048
|
+
* created and deleted once per review completed, so the changes feed is
|
|
9049
|
+
* dominated by tombstones and grows without bound, while the durable record
|
|
9050
|
+
* lives in card history. One real account measured 385 live documents behind
|
|
9051
|
+
* 8,370 deletions — an unfiltered pull moved 9,520 documents and took ~20s,
|
|
9052
|
+
* blowing the hydration timeout on a phone; filtered, the same pull moves
|
|
9053
|
+
* 404 and takes ~2s, reaching a byte-identical local state.
|
|
9054
|
+
*
|
|
9055
|
+
* The filter runs server-side, so `last_seq` still reports the source's true
|
|
9056
|
+
* sequence. Returning it lets startSync() begin the live pull from there
|
|
9057
|
+
* instead of re-walking the same history in the background — without that,
|
|
9058
|
+
* the cost is merely deferred, not removed.
|
|
9013
9059
|
*/
|
|
9014
9060
|
async hydrate(localDB, remoteDB) {
|
|
9015
|
-
const
|
|
9061
|
+
const pristine = (await localDB.info()).doc_count === 0;
|
|
9062
|
+
const replication = pouchdb_setup_default.replicate(
|
|
9063
|
+
remoteDB,
|
|
9064
|
+
localDB,
|
|
9065
|
+
pristine ? { selector: { _deleted: { $exists: false } } } : {}
|
|
9066
|
+
);
|
|
9016
9067
|
this.hydrationHandle = replication;
|
|
9017
9068
|
try {
|
|
9018
9069
|
const info = await replication;
|
|
9019
|
-
return {
|
|
9070
|
+
return {
|
|
9071
|
+
docsWritten: info.docs_written,
|
|
9072
|
+
// Only a pristine pull is a trustworthy starting point for the live
|
|
9073
|
+
// sync; otherwise leave startSync() to its own checkpoint.
|
|
9074
|
+
lastSeq: pristine ? info.last_seq : void 0
|
|
9075
|
+
};
|
|
9020
9076
|
} finally {
|
|
9021
9077
|
this.hydrationHandle = void 0;
|
|
9022
9078
|
}
|
|
9023
9079
|
}
|
|
9024
|
-
startSync(localDB, remoteDB) {
|
|
9080
|
+
startSync(localDB, remoteDB, since) {
|
|
9025
9081
|
if (localDB.name !== remoteDB.name) {
|
|
9026
9082
|
this.syncHandle = pouchdb_setup_default.sync(localDB, remoteDB, {
|
|
9027
9083
|
live: true,
|
|
9028
|
-
retry: true
|
|
9084
|
+
retry: true,
|
|
9085
|
+
// `since` applies to the pull only, and only when hydrate() just
|
|
9086
|
+
// established that local matches remote at that sequence. Without it,
|
|
9087
|
+
// a filtered hydration leaves the pull with no usable checkpoint (the
|
|
9088
|
+
// filter changes the replication id), so it restarts from zero and
|
|
9089
|
+
// re-walks in the background exactly the tombstone history hydration
|
|
9090
|
+
// just skipped — same work, now competing with the study session.
|
|
9091
|
+
//
|
|
9092
|
+
// Deliberately NOT persisted across launches: on later boots hydration
|
|
9093
|
+
// is skipped and the pull's own checkpoint is the correct resume
|
|
9094
|
+
// point, so a stored sequence could only be stale. If the app dies
|
|
9095
|
+
// before that first checkpoint is written, the next launch simply
|
|
9096
|
+
// walks the full feed once.
|
|
9097
|
+
...since !== void 0 ? { pull: { since } } : {}
|
|
9029
9098
|
});
|
|
9030
9099
|
}
|
|
9031
9100
|
}
|