@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
package/dist/index.mjs
CHANGED
|
@@ -7361,22 +7361,60 @@ var init_CouchDBSyncStrategy = __esm({
|
|
|
7361
7361
|
* before we know what the remote already has, which is the conflict-leaf
|
|
7362
7362
|
* problem hydration exists to avoid. Local changes go up when startSync()
|
|
7363
7363
|
* takes over.
|
|
7364
|
+
*
|
|
7365
|
+
* Into an EMPTY local DB this skips deleted documents, because a mirror that
|
|
7366
|
+
* never held a document does not need to be told it was removed. That
|
|
7367
|
+
* matters more than it sounds: scheduled reviews (`card_review_*`) are
|
|
7368
|
+
* created and deleted once per review completed, so the changes feed is
|
|
7369
|
+
* dominated by tombstones and grows without bound, while the durable record
|
|
7370
|
+
* lives in card history. One real account measured 385 live documents behind
|
|
7371
|
+
* 8,370 deletions — an unfiltered pull moved 9,520 documents and took ~20s,
|
|
7372
|
+
* blowing the hydration timeout on a phone; filtered, the same pull moves
|
|
7373
|
+
* 404 and takes ~2s, reaching a byte-identical local state.
|
|
7374
|
+
*
|
|
7375
|
+
* The filter runs server-side, so `last_seq` still reports the source's true
|
|
7376
|
+
* sequence. Returning it lets startSync() begin the live pull from there
|
|
7377
|
+
* instead of re-walking the same history in the background — without that,
|
|
7378
|
+
* the cost is merely deferred, not removed.
|
|
7364
7379
|
*/
|
|
7365
7380
|
async hydrate(localDB, remoteDB) {
|
|
7366
|
-
const
|
|
7381
|
+
const pristine = (await localDB.info()).doc_count === 0;
|
|
7382
|
+
const replication = pouchdb_setup_default.replicate(
|
|
7383
|
+
remoteDB,
|
|
7384
|
+
localDB,
|
|
7385
|
+
pristine ? { selector: { _deleted: { $exists: false } } } : {}
|
|
7386
|
+
);
|
|
7367
7387
|
this.hydrationHandle = replication;
|
|
7368
7388
|
try {
|
|
7369
7389
|
const info = await replication;
|
|
7370
|
-
return {
|
|
7390
|
+
return {
|
|
7391
|
+
docsWritten: info.docs_written,
|
|
7392
|
+
// Only a pristine pull is a trustworthy starting point for the live
|
|
7393
|
+
// sync; otherwise leave startSync() to its own checkpoint.
|
|
7394
|
+
lastSeq: pristine ? info.last_seq : void 0
|
|
7395
|
+
};
|
|
7371
7396
|
} finally {
|
|
7372
7397
|
this.hydrationHandle = void 0;
|
|
7373
7398
|
}
|
|
7374
7399
|
}
|
|
7375
|
-
startSync(localDB, remoteDB) {
|
|
7400
|
+
startSync(localDB, remoteDB, since) {
|
|
7376
7401
|
if (localDB.name !== remoteDB.name) {
|
|
7377
7402
|
this.syncHandle = pouchdb_setup_default.sync(localDB, remoteDB, {
|
|
7378
7403
|
live: true,
|
|
7379
|
-
retry: true
|
|
7404
|
+
retry: true,
|
|
7405
|
+
// `since` applies to the pull only, and only when hydrate() just
|
|
7406
|
+
// established that local matches remote at that sequence. Without it,
|
|
7407
|
+
// a filtered hydration leaves the pull with no usable checkpoint (the
|
|
7408
|
+
// filter changes the replication id), so it restarts from zero and
|
|
7409
|
+
// re-walks in the background exactly the tombstone history hydration
|
|
7410
|
+
// just skipped — same work, now competing with the study session.
|
|
7411
|
+
//
|
|
7412
|
+
// Deliberately NOT persisted across launches: on later boots hydration
|
|
7413
|
+
// is skipped and the pull's own checkpoint is the correct resume
|
|
7414
|
+
// point, so a stored sequence could only be stale. If the app dies
|
|
7415
|
+
// before that first checkpoint is written, the next launch simply
|
|
7416
|
+
// walks the full feed once.
|
|
7417
|
+
...since !== void 0 ? { pull: { since } } : {}
|
|
7380
7418
|
});
|
|
7381
7419
|
}
|
|
7382
7420
|
}
|
|
@@ -7896,16 +7934,44 @@ var init_BaseUserDB = __esm({
|
|
|
7896
7934
|
// Database to use for write operations (local-first approach)
|
|
7897
7935
|
updateQueue;
|
|
7898
7936
|
_hydration = { state: "not-required" };
|
|
7937
|
+
/** In-flight hydration, so concurrent callers can wait for a real answer. */
|
|
7938
|
+
_hydrationPromise = null;
|
|
7939
|
+
/**
|
|
7940
|
+
* Sequence the local mirror was filled to by a hydration that succeeded in
|
|
7941
|
+
* THIS init(), handed to startSync() so the live pull can skip history it
|
|
7942
|
+
* would otherwise re-walk. Undefined whenever there is no such guarantee —
|
|
7943
|
+
* hydration skipped, not required, or failed. Never persisted; see the
|
|
7944
|
+
* `since` note in CouchDBSyncStrategy.startSync().
|
|
7945
|
+
*/
|
|
7946
|
+
_hydratedSeq;
|
|
7899
7947
|
/**
|
|
7900
|
-
* How far the local mirror can be trusted. See
|
|
7948
|
+
* How far the local mirror can be trusted, RIGHT NOW. See
|
|
7949
|
+
* {@link UserHydrationStatus}.
|
|
7901
7950
|
*
|
|
7902
|
-
*
|
|
7903
|
-
*
|
|
7904
|
-
*
|
|
7951
|
+
* This is a snapshot, and during login it can legitimately read
|
|
7952
|
+
* `hydrating` — prefer awaitHydration() anywhere a decision hangs on the
|
|
7953
|
+
* answer.
|
|
7905
7954
|
*/
|
|
7906
7955
|
hydrationStatus() {
|
|
7907
7956
|
return { ...this._hydration };
|
|
7908
7957
|
}
|
|
7958
|
+
/**
|
|
7959
|
+
* The hydration status once it has settled — never `hydrating`.
|
|
7960
|
+
*
|
|
7961
|
+
* Resolves immediately unless a pull is in flight. Exists for callers that
|
|
7962
|
+
* must decide something (a route guard, a first-run branch) and would
|
|
7963
|
+
* otherwise sample `hydrating` and guess. Since init() is awaited by both
|
|
7964
|
+
* app startup and login(), that only happens when something navigates
|
|
7965
|
+
* concurrently with a login still in progress — a window that stretches to
|
|
7966
|
+
* HYDRATION_TIMEOUT_MS on a slow connection.
|
|
7967
|
+
*/
|
|
7968
|
+
async awaitHydration() {
|
|
7969
|
+
try {
|
|
7970
|
+
await this._hydrationPromise;
|
|
7971
|
+
} catch {
|
|
7972
|
+
}
|
|
7973
|
+
return this.hydrationStatus();
|
|
7974
|
+
}
|
|
7909
7975
|
/**
|
|
7910
7976
|
* Whether a missing document may be treated as genuinely absent, allowing
|
|
7911
7977
|
* callers to create it with defaults.
|
|
@@ -8362,8 +8428,9 @@ Currently logged-in as ${this._username}.`
|
|
|
8362
8428
|
}
|
|
8363
8429
|
this.syncStrategy.stopSync?.();
|
|
8364
8430
|
this.setDBandQ();
|
|
8365
|
-
|
|
8366
|
-
|
|
8431
|
+
this._hydrationPromise = this.hydrateLocalMirror();
|
|
8432
|
+
await this._hydrationPromise;
|
|
8433
|
+
this.syncStrategy.startSync(this.localDB, this.remoteDB, this._hydratedSeq);
|
|
8367
8434
|
this.applyDesignDocs().catch((error) => {
|
|
8368
8435
|
log4(`Error in applyDesignDocs background task: ${error}`);
|
|
8369
8436
|
if (error && typeof error === "object") {
|
|
@@ -8391,6 +8458,7 @@ Currently logged-in as ${this._username}.`
|
|
|
8391
8458
|
* ceiling). Callers decide what a failure means for them.
|
|
8392
8459
|
*/
|
|
8393
8460
|
async hydrateLocalMirror() {
|
|
8461
|
+
this._hydratedSeq = void 0;
|
|
8394
8462
|
if (this.localDB.name === this.remoteDB.name || !this.syncStrategy.hydrate) {
|
|
8395
8463
|
this._hydration = { state: "not-required" };
|
|
8396
8464
|
return;
|
|
@@ -8402,11 +8470,12 @@ Currently logged-in as ${this._username}.`
|
|
|
8402
8470
|
this._hydration = { state: "hydrating" };
|
|
8403
8471
|
const start = Date.now();
|
|
8404
8472
|
try {
|
|
8405
|
-
const { docsWritten } = await withTimeout(
|
|
8473
|
+
const { docsWritten, lastSeq } = await withTimeout(
|
|
8406
8474
|
this.syncStrategy.hydrate(this.localDB, this.remoteDB),
|
|
8407
8475
|
HYDRATION_TIMEOUT_MS,
|
|
8408
8476
|
`Hydration of local mirror for ${this._username}`
|
|
8409
8477
|
);
|
|
8478
|
+
this._hydratedSeq = lastSeq;
|
|
8410
8479
|
await this.writeHydrationMarker();
|
|
8411
8480
|
this._hydration = {
|
|
8412
8481
|
state: "hydrated",
|