@fuzdev/fuz_app 0.101.0 → 0.101.1

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.
@@ -4,10 +4,41 @@
4
4
  * Queries migration state and table info without a running server.
5
5
  * Returns structured data that consumer scripts can print however they like.
6
6
  *
7
+ * The migration check is **name-divergence aware**: it name-prefix-verifies the
8
+ * applied migrations against the code's list (mirroring `run_migrations`), so a
9
+ * divergent history (same count, different names) reports a `divergence` and
10
+ * renders `DIVERGED` rather than a false `up_to_date`.
11
+ *
7
12
  * @module
8
13
  */
9
14
  import type { Db } from './db.js';
10
15
  import type { MigrationNamespace } from './migrate.js';
16
+ /**
17
+ * A divergence between the recorded migration tracker and the code's list.
18
+ *
19
+ * Either variant is a state the migration runner refuses to boot against — a
20
+ * re-bootstrap (drop + migrate) is needed. Structured (not a pre-formatted
21
+ * string) so programmatic consumers can branch on `kind`; `format_db_status`
22
+ * renders the operator-facing line. The discriminated-union twin of the Rust
23
+ * `fuz_db` `Divergence` enum.
24
+ */
25
+ export type Divergence = {
26
+ /** An applied name doesn't match the code's name at `position`. */
27
+ kind: 'name_mismatch';
28
+ /** Sequence position of the first mismatch. */
29
+ position: number;
30
+ /** The name recorded in the tracker at `position`. */
31
+ applied: string;
32
+ /** The name the code declares at `position`. */
33
+ expected: string;
34
+ } | {
35
+ /** The tracker records more migrations than the code declares. */
36
+ kind: 'binary_older';
37
+ /** Count recorded in the tracker. */
38
+ applied: number;
39
+ /** Count the code declares. */
40
+ declared: number;
41
+ };
11
42
  /**
12
43
  * Migration status for a single namespace.
13
44
  */
@@ -17,8 +48,18 @@ export interface MigrationStatus {
17
48
  applied_names: Array<string>;
18
49
  /** Names of code migrations not yet applied (suffix of the code array). */
19
50
  pending_names: Array<string>;
20
- /** Whether `applied_names` is the full code array (no pending work). */
51
+ /**
52
+ * Whether `applied_names` is the full code array with no name divergence
53
+ * (no pending work, no diverged history).
54
+ */
21
55
  up_to_date: boolean;
56
+ /**
57
+ * The first applied/code divergence, if any. Absent when the applied names
58
+ * are a clean prefix of the code's list (the only state the runner boots
59
+ * against). Present means a divergent bootstrap history — a re-bootstrap
60
+ * (drop + migrate) is needed.
61
+ */
62
+ divergence?: Divergence;
22
63
  }
23
64
  /**
24
65
  * Table info with row count.
@@ -1 +1 @@
1
- {"version":3,"file":"status.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/db/status.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,OAAO,KAAK,EAAC,EAAE,EAAC,MAAM,SAAS,CAAC;AAChC,OAAO,KAAK,EAAC,kBAAkB,EAAC,MAAM,cAAc,CAAC;AAErD;;GAEG;AACH,MAAM,WAAW,eAAe;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,uEAAuE;IACvE,aAAa,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAC7B,2EAA2E;IAC3E,aAAa,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAC7B,wEAAwE;IACxE,UAAU,EAAE,OAAO,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,QAAQ;IACxB,yCAAyC;IACzC,SAAS,EAAE,OAAO,CAAC;IACnB,0CAA0C;IAC1C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,+BAA+B;IAC/B,WAAW,EAAE,MAAM,CAAC;IACpB,4BAA4B;IAC5B,MAAM,EAAE,KAAK,CAAC,WAAW,CAAC,CAAC;IAC3B,sCAAsC;IACtC,UAAU,EAAE,KAAK,CAAC,eAAe,CAAC,CAAC;IACnC;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC5B;AA8BD;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,eAAe,GAC3B,IAAI,EAAE,EACN,aAAa,KAAK,CAAC,kBAAkB,CAAC,KACpC,OAAO,CAAC,QAAQ,CAmFlB,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,GAAI,QAAQ,QAAQ,KAAG,MA8CnD,CAAC"}
1
+ {"version":3,"file":"status.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/db/status.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAIH,OAAO,KAAK,EAAC,EAAE,EAAC,MAAM,SAAS,CAAC;AAChC,OAAO,KAAK,EAAC,kBAAkB,EAAC,MAAM,cAAc,CAAC;AAErD;;;;;;;;GAQG;AACH,MAAM,MAAM,UAAU,GACnB;IACA,mEAAmE;IACnE,IAAI,EAAE,eAAe,CAAC;IACtB,+CAA+C;IAC/C,QAAQ,EAAE,MAAM,CAAC;IACjB,sDAAsD;IACtD,OAAO,EAAE,MAAM,CAAC;IAChB,gDAAgD;IAChD,QAAQ,EAAE,MAAM,CAAC;CAChB,GACD;IACA,kEAAkE;IAClE,IAAI,EAAE,cAAc,CAAC;IACrB,qCAAqC;IACrC,OAAO,EAAE,MAAM,CAAC;IAChB,+BAA+B;IAC/B,QAAQ,EAAE,MAAM,CAAC;CAChB,CAAC;AAEL;;GAEG;AACH,MAAM,WAAW,eAAe;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,uEAAuE;IACvE,aAAa,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAC7B,2EAA2E;IAC3E,aAAa,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAC7B;;;OAGG;IACH,UAAU,EAAE,OAAO,CAAC;IACpB;;;;;OAKG;IACH,UAAU,CAAC,EAAE,UAAU,CAAC;CACxB;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,QAAQ;IACxB,yCAAyC;IACzC,SAAS,EAAE,OAAO,CAAC;IACnB,0CAA0C;IAC1C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,+BAA+B;IAC/B,WAAW,EAAE,MAAM,CAAC;IACpB,4BAA4B;IAC5B,MAAM,EAAE,KAAK,CAAC,WAAW,CAAC,CAAC;IAC3B,sCAAsC;IACtC,UAAU,EAAE,KAAK,CAAC,eAAe,CAAC,CAAC;IACnC;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC5B;AA8BD;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,eAAe,GAC3B,IAAI,EAAE,EACN,aAAa,KAAK,CAAC,kBAAkB,CAAC,KACpC,OAAO,CAAC,QAAQ,CA2GlB,CAAC;AAeF;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,GAAI,QAAQ,QAAQ,KAAG,MAoDnD,CAAC"}
package/dist/db/status.js CHANGED
@@ -4,6 +4,11 @@
4
4
  * Queries migration state and table info without a running server.
5
5
  * Returns structured data that consumer scripts can print however they like.
6
6
  *
7
+ * The migration check is **name-divergence aware**: it name-prefix-verifies the
8
+ * applied migrations against the code's list (mirroring `run_migrations`), so a
9
+ * divergent history (same count, different names) reports a `divergence` and
10
+ * renders `DIVERGED` rather than a false `up_to_date`.
11
+ *
7
12
  * @module
8
13
  */
9
14
  import { to_error_message } from '@fuzdev/fuz_util/error.ts';
@@ -78,14 +83,37 @@ export const query_db_status = async (db, namespaces) => {
78
83
  ORDER BY sequence ASC`, [namespace]);
79
84
  const applied_names = rows.map((r) => r.name);
80
85
  const code_names = ns_migrations.map((m) => m.name);
81
- // pending = the suffix of code names beyond applied.length (callers
82
- // see the boot algorithm's tail without paying for verify here)
83
- const pending_names = code_names.slice(applied_names.length);
86
+ const total = code_names.length;
87
+ const applied = applied_names.length;
88
+ // Name-prefix verify, mirroring the migration runner: the applied
89
+ // names must equal the first `applied` code names by position. A
90
+ // divergent history (a renamed/reordered migration, same count or not)
91
+ // is the exact state `run_migrations` refuses to boot against, so a
92
+ // count-only check would report a DB the runner rejects as up-to-date.
93
+ let divergence;
94
+ if (applied > total) {
95
+ divergence = { kind: 'binary_older', applied, declared: total };
96
+ }
97
+ else {
98
+ const position = applied_names.findIndex((name, i) => name !== code_names[i]);
99
+ if (position !== -1) {
100
+ divergence = {
101
+ kind: 'name_mismatch',
102
+ position,
103
+ applied: applied_names[position],
104
+ expected: code_names[position],
105
+ };
106
+ }
107
+ }
108
+ // pending = the suffix of code names past the applied count (clamped:
109
+ // a binary-older history has no pending tail)
110
+ const pending_names = code_names.slice(Math.min(applied, total));
84
111
  migrations.push({
85
112
  namespace,
86
113
  applied_names,
87
114
  pending_names,
88
- up_to_date: applied_names.length === code_names.length && pending_names.length === 0,
115
+ up_to_date: applied === total && pending_names.length === 0 && divergence === undefined,
116
+ ...(divergence ? { divergence } : {}),
89
117
  });
90
118
  }
91
119
  }
@@ -110,6 +138,18 @@ export const query_db_status = async (db, namespaces) => {
110
138
  ...(old_tracker_shape ? { old_tracker_shape: true } : {}),
111
139
  };
112
140
  };
141
+ /**
142
+ * Render a `Divergence` as the operator-facing detail line. Twin of the Rust
143
+ * `Divergence` `Display`.
144
+ */
145
+ const format_divergence = (divergence) => {
146
+ switch (divergence.kind) {
147
+ case 'name_mismatch':
148
+ return `position ${divergence.position}: database has '${divergence.applied}', code has '${divergence.expected}'`;
149
+ case 'binary_older':
150
+ return `database has ${divergence.applied} applied but code declares ${divergence.declared} (binary older than database)`;
151
+ }
152
+ };
113
153
  /**
114
154
  * Format a `DbStatus` as a human-readable string for CLI output.
115
155
  *
@@ -141,7 +181,10 @@ export const format_db_status = (status) => {
141
181
  lines.push(' Migrations:');
142
182
  for (const m of status.migrations) {
143
183
  const total = m.applied_names.length + m.pending_names.length;
144
- if (m.up_to_date) {
184
+ if (m.divergence) {
185
+ lines.push(` ${m.namespace}: DIVERGED (${m.applied_names.length}/${total}) — ${format_divergence(m.divergence)}`);
186
+ }
187
+ else if (m.up_to_date) {
145
188
  lines.push(` ${m.namespace}: up to date (${m.applied_names.length}/${total})`);
146
189
  }
147
190
  else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fuzdev/fuz_app",
3
- "version": "0.101.0",
3
+ "version": "0.101.1",
4
4
  "description": "fullstack app library",
5
5
  "glyph": "🗝",
6
6
  "logo": "logo.svg",