@vue-skuilder/db 0.2.19 → 0.2.21

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.
Files changed (35) hide show
  1. package/dist/{SyncStrategy-BJMZq3WO.d.cts → SyncStrategy-CCU1H81I.d.cts} +8 -2
  2. package/dist/{SyncStrategy-BJMZq3WO.d.ts → SyncStrategy-CCU1H81I.d.ts} +8 -2
  3. package/dist/{contentSource-D-LQYFyx.d.cts → contentSource-BMxsujRu.d.cts} +5 -0
  4. package/dist/{contentSource-BDRX_YYJ.d.ts → contentSource-Bazgkf4C.d.ts} +5 -0
  5. package/dist/core/index.d.cts +3 -3
  6. package/dist/core/index.d.ts +3 -3
  7. package/dist/core/index.js +12 -2
  8. package/dist/core/index.js.map +1 -1
  9. package/dist/core/index.mjs +12 -2
  10. package/dist/core/index.mjs.map +1 -1
  11. package/dist/{dataLayerProvider-Dpshuql4.d.ts → dataLayerProvider-Bt-D6xas.d.ts} +1 -1
  12. package/dist/{dataLayerProvider-Dd2UcCif.d.cts → dataLayerProvider-DifVCcIS.d.cts} +1 -1
  13. package/dist/impl/couch/index.d.cts +20 -4
  14. package/dist/impl/couch/index.d.ts +20 -4
  15. package/dist/impl/couch/index.js +54 -6
  16. package/dist/impl/couch/index.js.map +1 -1
  17. package/dist/impl/couch/index.mjs +54 -6
  18. package/dist/impl/couch/index.mjs.map +1 -1
  19. package/dist/impl/static/index.d.cts +3 -3
  20. package/dist/impl/static/index.d.ts +3 -3
  21. package/dist/impl/static/index.js +12 -2
  22. package/dist/impl/static/index.js.map +1 -1
  23. package/dist/impl/static/index.mjs +12 -2
  24. package/dist/impl/static/index.mjs.map +1 -1
  25. package/dist/index.d.cts +3 -3
  26. package/dist/index.d.ts +3 -3
  27. package/dist/index.js +54 -6
  28. package/dist/index.js.map +1 -1
  29. package/dist/index.mjs +54 -6
  30. package/dist/index.mjs.map +1 -1
  31. package/package.json +3 -3
  32. package/src/core/types/user.ts +6 -1
  33. package/src/impl/common/BaseUserDB.ts +19 -2
  34. package/src/impl/common/SyncStrategy.ts +17 -4
  35. package/src/impl/couch/CouchDBSyncStrategy.ts +52 -4
@@ -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-BDRX_YYJ.js';
1
+ import { U as UserDBInterface, a as UserDBReader, C as CourseDBInterface, b as CoursesDBInterface, c as ClassroomDBInterface, A as AdminDBInterface } from './contentSource-Bazgkf4C.js';
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-D-LQYFyx.cjs';
1
+ import { U as UserDBInterface, a as UserDBReader, C as CourseDBInterface, b as CoursesDBInterface, c as ClassroomDBInterface, A as AdminDBInterface } from './contentSource-BMxsujRu.cjs';
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-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';
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-BMxsujRu.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-BMxsujRu.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-BJMZq3WO.cjs';
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-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';
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-Bazgkf4C.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-Bazgkf4C.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-BJMZq3WO.js';
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;
@@ -7563,6 +7563,14 @@ var init_BaseUserDB = __esm({
7563
7563
  _hydration = { state: "not-required" };
7564
7564
  /** In-flight hydration, so concurrent callers can wait for a real answer. */
7565
7565
  _hydrationPromise = null;
7566
+ /**
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;
7566
7574
  /**
7567
7575
  * How far the local mirror can be trusted, RIGHT NOW. See
7568
7576
  * {@link UserHydrationStatus}.
@@ -8049,7 +8057,7 @@ Currently logged-in as ${this._username}.`
8049
8057
  this.setDBandQ();
8050
8058
  this._hydrationPromise = this.hydrateLocalMirror();
8051
8059
  await this._hydrationPromise;
8052
- this.syncStrategy.startSync(this.localDB, this.remoteDB);
8060
+ this.syncStrategy.startSync(this.localDB, this.remoteDB, this._hydratedSeq);
8053
8061
  this.applyDesignDocs().catch((error) => {
8054
8062
  log3(`Error in applyDesignDocs background task: ${error}`);
8055
8063
  if (error && typeof error === "object") {
@@ -8077,6 +8085,7 @@ Currently logged-in as ${this._username}.`
8077
8085
  * ceiling). Callers decide what a failure means for them.
8078
8086
  */
8079
8087
  async hydrateLocalMirror() {
8088
+ this._hydratedSeq = void 0;
8080
8089
  if (this.localDB.name === this.remoteDB.name || !this.syncStrategy.hydrate) {
8081
8090
  this._hydration = { state: "not-required" };
8082
8091
  return;
@@ -8088,11 +8097,12 @@ Currently logged-in as ${this._username}.`
8088
8097
  this._hydration = { state: "hydrating" };
8089
8098
  const start = Date.now();
8090
8099
  try {
8091
- const { docsWritten } = await withTimeout(
8100
+ const { docsWritten, lastSeq } = await withTimeout(
8092
8101
  this.syncStrategy.hydrate(this.localDB, this.remoteDB),
8093
8102
  HYDRATION_TIMEOUT_MS,
8094
8103
  `Hydration of local mirror for ${this._username}`
8095
8104
  );
8105
+ this._hydratedSeq = lastSeq;
8096
8106
  await this.writeHydrationMarker();
8097
8107
  this._hydration = {
8098
8108
  state: "hydrated",
@@ -9031,22 +9041,60 @@ var init_CouchDBSyncStrategy = __esm({
9031
9041
  * before we know what the remote already has, which is the conflict-leaf
9032
9042
  * problem hydration exists to avoid. Local changes go up when startSync()
9033
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.
9034
9059
  */
9035
9060
  async hydrate(localDB, remoteDB) {
9036
- const replication = pouchdb_setup_default.replicate(remoteDB, localDB, {});
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
+ );
9037
9067
  this.hydrationHandle = replication;
9038
9068
  try {
9039
9069
  const info = await replication;
9040
- return { docsWritten: info.docs_written };
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
+ };
9041
9076
  } finally {
9042
9077
  this.hydrationHandle = void 0;
9043
9078
  }
9044
9079
  }
9045
- startSync(localDB, remoteDB) {
9080
+ startSync(localDB, remoteDB, since) {
9046
9081
  if (localDB.name !== remoteDB.name) {
9047
9082
  this.syncHandle = pouchdb_setup_default.sync(localDB, remoteDB, {
9048
9083
  live: true,
9049
- 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 } } : {}
9050
9098
  });
9051
9099
  }
9052
9100
  }