@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
|
@@ -7541,16 +7541,44 @@ var init_BaseUserDB = __esm({
|
|
|
7541
7541
|
// Database to use for write operations (local-first approach)
|
|
7542
7542
|
updateQueue;
|
|
7543
7543
|
_hydration = { state: "not-required" };
|
|
7544
|
+
/** In-flight hydration, so concurrent callers can wait for a real answer. */
|
|
7545
|
+
_hydrationPromise = null;
|
|
7544
7546
|
/**
|
|
7545
|
-
*
|
|
7547
|
+
* Sequence the local mirror was filled to by a hydration that succeeded in
|
|
7548
|
+
* THIS init(), handed to startSync() so the live pull can skip history it
|
|
7549
|
+
* would otherwise re-walk. Undefined whenever there is no such guarantee —
|
|
7550
|
+
* hydration skipped, not required, or failed. Never persisted; see the
|
|
7551
|
+
* `since` note in CouchDBSyncStrategy.startSync().
|
|
7552
|
+
*/
|
|
7553
|
+
_hydratedSeq;
|
|
7554
|
+
/**
|
|
7555
|
+
* How far the local mirror can be trusted, RIGHT NOW. See
|
|
7556
|
+
* {@link UserHydrationStatus}.
|
|
7546
7557
|
*
|
|
7547
|
-
*
|
|
7548
|
-
*
|
|
7549
|
-
*
|
|
7558
|
+
* This is a snapshot, and during login it can legitimately read
|
|
7559
|
+
* `hydrating` — prefer awaitHydration() anywhere a decision hangs on the
|
|
7560
|
+
* answer.
|
|
7550
7561
|
*/
|
|
7551
7562
|
hydrationStatus() {
|
|
7552
7563
|
return { ...this._hydration };
|
|
7553
7564
|
}
|
|
7565
|
+
/**
|
|
7566
|
+
* The hydration status once it has settled — never `hydrating`.
|
|
7567
|
+
*
|
|
7568
|
+
* Resolves immediately unless a pull is in flight. Exists for callers that
|
|
7569
|
+
* must decide something (a route guard, a first-run branch) and would
|
|
7570
|
+
* otherwise sample `hydrating` and guess. Since init() is awaited by both
|
|
7571
|
+
* app startup and login(), that only happens when something navigates
|
|
7572
|
+
* concurrently with a login still in progress — a window that stretches to
|
|
7573
|
+
* HYDRATION_TIMEOUT_MS on a slow connection.
|
|
7574
|
+
*/
|
|
7575
|
+
async awaitHydration() {
|
|
7576
|
+
try {
|
|
7577
|
+
await this._hydrationPromise;
|
|
7578
|
+
} catch {
|
|
7579
|
+
}
|
|
7580
|
+
return this.hydrationStatus();
|
|
7581
|
+
}
|
|
7554
7582
|
/**
|
|
7555
7583
|
* Whether a missing document may be treated as genuinely absent, allowing
|
|
7556
7584
|
* callers to create it with defaults.
|
|
@@ -8007,8 +8035,9 @@ Currently logged-in as ${this._username}.`
|
|
|
8007
8035
|
}
|
|
8008
8036
|
this.syncStrategy.stopSync?.();
|
|
8009
8037
|
this.setDBandQ();
|
|
8010
|
-
|
|
8011
|
-
|
|
8038
|
+
this._hydrationPromise = this.hydrateLocalMirror();
|
|
8039
|
+
await this._hydrationPromise;
|
|
8040
|
+
this.syncStrategy.startSync(this.localDB, this.remoteDB, this._hydratedSeq);
|
|
8012
8041
|
this.applyDesignDocs().catch((error) => {
|
|
8013
8042
|
log3(`Error in applyDesignDocs background task: ${error}`);
|
|
8014
8043
|
if (error && typeof error === "object") {
|
|
@@ -8036,6 +8065,7 @@ Currently logged-in as ${this._username}.`
|
|
|
8036
8065
|
* ceiling). Callers decide what a failure means for them.
|
|
8037
8066
|
*/
|
|
8038
8067
|
async hydrateLocalMirror() {
|
|
8068
|
+
this._hydratedSeq = void 0;
|
|
8039
8069
|
if (this.localDB.name === this.remoteDB.name || !this.syncStrategy.hydrate) {
|
|
8040
8070
|
this._hydration = { state: "not-required" };
|
|
8041
8071
|
return;
|
|
@@ -8047,11 +8077,12 @@ Currently logged-in as ${this._username}.`
|
|
|
8047
8077
|
this._hydration = { state: "hydrating" };
|
|
8048
8078
|
const start = Date.now();
|
|
8049
8079
|
try {
|
|
8050
|
-
const { docsWritten } = await withTimeout(
|
|
8080
|
+
const { docsWritten, lastSeq } = await withTimeout(
|
|
8051
8081
|
this.syncStrategy.hydrate(this.localDB, this.remoteDB),
|
|
8052
8082
|
HYDRATION_TIMEOUT_MS,
|
|
8053
8083
|
`Hydration of local mirror for ${this._username}`
|
|
8054
8084
|
);
|
|
8085
|
+
this._hydratedSeq = lastSeq;
|
|
8055
8086
|
await this.writeHydrationMarker();
|
|
8056
8087
|
this._hydration = {
|
|
8057
8088
|
state: "hydrated",
|
|
@@ -8989,22 +9020,60 @@ var init_CouchDBSyncStrategy = __esm({
|
|
|
8989
9020
|
* before we know what the remote already has, which is the conflict-leaf
|
|
8990
9021
|
* problem hydration exists to avoid. Local changes go up when startSync()
|
|
8991
9022
|
* takes over.
|
|
9023
|
+
*
|
|
9024
|
+
* Into an EMPTY local DB this skips deleted documents, because a mirror that
|
|
9025
|
+
* never held a document does not need to be told it was removed. That
|
|
9026
|
+
* matters more than it sounds: scheduled reviews (`card_review_*`) are
|
|
9027
|
+
* created and deleted once per review completed, so the changes feed is
|
|
9028
|
+
* dominated by tombstones and grows without bound, while the durable record
|
|
9029
|
+
* lives in card history. One real account measured 385 live documents behind
|
|
9030
|
+
* 8,370 deletions — an unfiltered pull moved 9,520 documents and took ~20s,
|
|
9031
|
+
* blowing the hydration timeout on a phone; filtered, the same pull moves
|
|
9032
|
+
* 404 and takes ~2s, reaching a byte-identical local state.
|
|
9033
|
+
*
|
|
9034
|
+
* The filter runs server-side, so `last_seq` still reports the source's true
|
|
9035
|
+
* sequence. Returning it lets startSync() begin the live pull from there
|
|
9036
|
+
* instead of re-walking the same history in the background — without that,
|
|
9037
|
+
* the cost is merely deferred, not removed.
|
|
8992
9038
|
*/
|
|
8993
9039
|
async hydrate(localDB, remoteDB) {
|
|
8994
|
-
const
|
|
9040
|
+
const pristine = (await localDB.info()).doc_count === 0;
|
|
9041
|
+
const replication = pouchdb_setup_default.replicate(
|
|
9042
|
+
remoteDB,
|
|
9043
|
+
localDB,
|
|
9044
|
+
pristine ? { selector: { _deleted: { $exists: false } } } : {}
|
|
9045
|
+
);
|
|
8995
9046
|
this.hydrationHandle = replication;
|
|
8996
9047
|
try {
|
|
8997
9048
|
const info = await replication;
|
|
8998
|
-
return {
|
|
9049
|
+
return {
|
|
9050
|
+
docsWritten: info.docs_written,
|
|
9051
|
+
// Only a pristine pull is a trustworthy starting point for the live
|
|
9052
|
+
// sync; otherwise leave startSync() to its own checkpoint.
|
|
9053
|
+
lastSeq: pristine ? info.last_seq : void 0
|
|
9054
|
+
};
|
|
8999
9055
|
} finally {
|
|
9000
9056
|
this.hydrationHandle = void 0;
|
|
9001
9057
|
}
|
|
9002
9058
|
}
|
|
9003
|
-
startSync(localDB, remoteDB) {
|
|
9059
|
+
startSync(localDB, remoteDB, since) {
|
|
9004
9060
|
if (localDB.name !== remoteDB.name) {
|
|
9005
9061
|
this.syncHandle = pouchdb_setup_default.sync(localDB, remoteDB, {
|
|
9006
9062
|
live: true,
|
|
9007
|
-
retry: true
|
|
9063
|
+
retry: true,
|
|
9064
|
+
// `since` applies to the pull only, and only when hydrate() just
|
|
9065
|
+
// established that local matches remote at that sequence. Without it,
|
|
9066
|
+
// a filtered hydration leaves the pull with no usable checkpoint (the
|
|
9067
|
+
// filter changes the replication id), so it restarts from zero and
|
|
9068
|
+
// re-walks in the background exactly the tombstone history hydration
|
|
9069
|
+
// just skipped — same work, now competing with the study session.
|
|
9070
|
+
//
|
|
9071
|
+
// Deliberately NOT persisted across launches: on later boots hydration
|
|
9072
|
+
// is skipped and the pull's own checkpoint is the correct resume
|
|
9073
|
+
// point, so a stored sequence could only be stale. If the app dies
|
|
9074
|
+
// before that first checkpoint is written, the next launch simply
|
|
9075
|
+
// walks the full feed once.
|
|
9076
|
+
...since !== void 0 ? { pull: { since } } : {}
|
|
9008
9077
|
});
|
|
9009
9078
|
}
|
|
9010
9079
|
}
|