@voltro/database 0.30.2 → 0.31.0

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/CHANGELOG.md CHANGED
@@ -39,6 +39,121 @@ _Changes staged for the next release accumulate here (rolled up from
39
39
 
40
40
  ---
41
41
 
42
+ ## [0.31.0] — 2026-08-10
43
+
44
+ ### Added
45
+
46
+ - **@voltro/i18n, @voltro/cli** — **`LOCALE_COOKIE` and `THEME_COOKIE` are exported from `@voltro/i18n`.**
47
+
48
+ `voltro doctor` flags a hand-written `'voltro:locale'` and tells you to "import the constant … from `@voltro/ui-shadcn`" — which was the only package exporting one. An app on the framework's i18n and not on the shadcn kit could not follow that advice without adopting a UI kit for two strings. Reported by a consumer, who added that the rule "does not fire for us, and we think that is correct-by-accident".
49
+
50
+ The names live in `@voltro/i18n` now (the package that resolves the locale from a request already hardcoded the literal), doctor's remedy names it, and `cookieNameParity.test.ts` asserts every declaration in the repo agrees AND that the remedy names a package which actually exports what it names.
51
+
52
+ That second assertion is one `scripts/check-message-apis.mjs` structurally cannot make: it verifies a member EXISTS in the published surface, and `LOCALE_COOKIE` always did — just not anywhere the reader could import it from. A reachability claim needs its own check.
53
+ - **@voltro/cli** — **`voltro doctor` reports two things that were decidable and unreported: dead endpoints and never-fired events.**
54
+
55
+ **A procedure requiring a scope no declared role grants.** A consumer shipped `webhooks.test` guarded by `webhooks:test` while their roles granted `webhooks:read` and `webhooks:write` and nothing else — uncallable by every user in every team, with nothing failing at boot to say so — and asked us to build the rule. The rule already existed: `rbac/unknown-scope`, in `voltro check`. It was in the wrong PLACE for them. `check` is the CI gate; `doctor` is what someone runs when something feels wrong, and a rule that only fires where you already suspect the problem fires for the people who did not need it. Doctor now surfaces the same finding from the same function — reused, not re-derived, because two answers to "which scope is ungranted" would diverge on the day it mattered.
56
+
57
+ **A declared event with no emit call site.** Asked for as a dashboard column, with the number that motivated it: seven of eleven advertised events had no emit anywhere, a week of work to remove. From a partner's side that is the expensive failure — they write a subscriber, test it, see nothing, and cannot tell "not implemented" from "my endpoint is broken". It is source-decidable, so it lands before deploy for every app rather than for whoever opens a panel afterwards. ADVISORY, and it says so: an emit through a variable reads as missing here.
58
+
59
+ Both reach `--json` and the human report. `serverOnly` and `authz` were each in one and absent from the other, twice, and "absent" read as "nothing to report".
60
+ - **@voltro/cli** — **`voltro dev` and `voltro doctor` report `@voltro/*` packages that are not on the same release.**
61
+
62
+ A consumer bumped 34 packages to 0.30.2 and `@voltro/i18n` stayed on 0.30.0 — their bump script's pattern was `@voltro/[a-z-]+`, and `i18n` contains digits. Their point was not the typo:
63
+
64
+ > *"Der erwähnenswerte Teil ist, dass eine Versionsschiefe innerhalb einer > Release-Familie nirgends auffällt: tsc, Tests, doctor und drei Builds waren > mit dem 0.30.0-Paket im Baum alle grün. Wenn ihr eine Stelle habt, an der das > billig zu prüfen wäre, wäre eine Warnung dort mehr wert als eine perfekte > Fehlermeldung anderswo."*
65
+
66
+ `tsc` in four apps, 19 317 tests, `voltro doctor` exit 0, three production builds — all green with a package two releases behind. None of those checks is ABOUT version agreement, so none of them could have caught it.
67
+
68
+ The framework ships in LOCKSTEP (`prepare-publish.mjs` stamps one version across every package), which is what makes this exact rather than a compatibility guess: two versions in one tree is a state the release process cannot produce. Reported at dev boot — where they asked for it, because that is where it is cheap — and in doctor's human report and `--json`. WARN, never fatal: we have no evidence a skewed tree cannot work, only that the combination was never released as a set.
69
+
70
+ Distinct from the duplicate-instance check, which asks a different question ("is one package resolved at two versions?") and has a different fix.
71
+
72
+ ### Fixed
73
+
74
+ - **@voltro/cli** — **`voltro db scan-credentials` on 0.30.0–0.30.2 did not look at the column credentials are in, and exited 0.**
75
+
76
+ 0.30.0 fixed a crash — the scan asked every table for a column called `subject`, `_voltro_row_history` has none, and the command died on postgres with `code=42703`. The fix replaced `subject` with `subjectId` on BOTH tables. That is right for one table and wrong for the other: `_voltro_audit_log.subject` is a `json()` blob and is the exact column the 0.28.0 upgrade note named — the one a reporting team found 117 rows of `jiraToken` in. `subjectId` is a flat opaque id that cannot hold a credential.
77
+
78
+ So for three releases a security command ran cleanly, printed a scanned count beside a hit count, and had never looked where credentials are. **A clean answer from a scan that searched the wrong place is worse than the crash it replaced.**
79
+
80
+ The default targets are derived from where a credential can physically land now: every `json()` column on `_voltro_audit_log` (`subject`, `actor`, `scope`, `metadata`, plus `input` / `outcome` — a procedure's arguments and result, the likeliest accidental home for a token), and `data` on `_voltro_row_history` — the full-row snapshot, which carries a credential column from ANY user table and outlives deleting the source row. The test that signed off on the narrowing is derived from the table declarations now, so it fails in both directions: a target naming a column that does not exist, and a column that exists and goes unscanned.
81
+
82
+ A manual codemod under 0.31.0 tells anyone who ran the command on 0.30.x to run it again — a codemod note is delivered once at a version boundary and cannot be revised, so a correction has to be re-issued under a version nobody has reached.
83
+ - **@voltro/cli** — **`voltro dev` announced `ready` while a different process served its port.**
84
+
85
+ Reported as an aside — "it fails with `Port 5190 is already in use` and the OLD process keeps answering" — and measured to be worse than that. With a squatter on IPv4 `127.0.0.1:5299`, vite's `host: true` bound IPv6 `*:5299` SUCCESSFULLY and the boot printed `listening on http://localhost:5299` and `ready in 56 ms`. Two listeners, one port, different stacks; every request went to the old process, with the old module graph. Not a failed boot — a boot that reports ready while your edits do nothing.
86
+
87
+ `strictPort: true` was set and working: it asks "can I bind?", and the failure is "is somebody already serving this?", which on a dual-stack host is a different question with a different answer. Both dev boot paths (api and web) now CONNECT to `127.0.0.1` and `::1` before creating a server and refuse with a `bootRefusal` naming the port, the stack, and `lsof -nP -iTCP:<port> -sTCP:LISTEN`. Exit 1, no `ready` line. Deliberately dev-only: in production the ambiguous cases are real (a sidecar, a health proxy, a rolling restart sharing a namespace) and refusing a legitimate boot is the worse failure — `serve` keeps failing on the bind alone.
88
+ - **@voltro/cli** — **`voltro doctor`'s duplicate-version check could not see the tree the consumer who asked for it actually had.**
89
+
90
+ They shipped the app on `@voltro/i18n@0.30.1` while `packages/ui-admin` and `ui-admin-shared` — which depend on it themselves — were left on 0.30.0. Two physical copies of a package carrying a React context; nothing failed at boot, no test caught it, and the framework's own dev-SSR diagnosis names that condition as a cause. They asked us to build the check. **It existed, and it was blind to their case.**
91
+
92
+ It walked the app's `node_modules` plus every ancestor's, on the reasoning that the pnpm virtual store is reached through the symlinks those contain. True for one copy: an ancestor's `node_modules/@voltro/i18n` is ONE symlink to ONE version. A second version pulled in by a SIBLING workspace package is linked only from that sibling's own `node_modules`, which is neither the app root nor an ancestor of it — so it was structurally invisible from the directory doctor is run in.
93
+
94
+ The pnpm store is read directly now, from its directory NAMES (`@voltro+i18n@0.30.0`), which is one `readdir` rather than a descent into thousands of inner `node_modules`. The peer-hash suffix pnpm appends is not read as a version — one package linked twice for two peer sets is normal, and reporting it would train people to ignore the rule.
95
+ - **@voltro/database, @voltro/sql-mysql** — **`json().default([])` emitted MySQL-only DDL and died mid-plan on MariaDB.**
96
+
97
+ The emitter had one branch for both servers, under a comment asserting MariaDB 10.2.7+ accepts the MySQL form. It does not. Measured against the real servers:
98
+
99
+ | | statement | result | |---|---|---| | MariaDB 11.8.8 | `SET DEFAULT (CAST('[]' AS JSON))` | `ERROR 1064 … near 'JSON))'` | | MariaDB 11.8.8 | `SET DEFAULT ('[]')` | OK | | MySQL 8.4.10 | `SET DEFAULT ('[]')` | `ERROR 1101 … can't have a default value` | | MySQL 8.4.10 | `SET DEFAULT (CAST('[]' AS JSON))` | OK | | BOTH | `SET DEFAULT (CONVERT('[]' USING utf8mb4))` | OK |
100
+
101
+ MariaDB has no JSON *type* — the column is `LONGTEXT` with `CHECK (json_valid(...))` — so `AS JSON` is not a cast target it has; MySQL treats a parenthesised literal as a literal and demands a real expression. The failure was not graceful: the plan died mid-apply, leaving a boot migration part-applied.
102
+
103
+ `CONVERT(… USING utf8mb4)` is emitted for both, chosen over splitting the branch because the declarative applier cannot tell the two servers apart — `sql.onDialectOrElse` collapses the family to one token — so a per-server answer would have meant plumbing the real dialect through three call sites. Both forms introspect back to something `normalizeDefault` unwraps, so the next plan is EMPTY instead of re-emitting the same op forever.
104
+
105
+ `sql-mysql/src/jsonColumnDefault.integration.test.ts` boots BOTH servers, applies the schema and asserts the inserted row carries the default — verified red against the old emitter (`ER_PARSE_ERROR 1064`) before it was made green.
106
+ - **@voltro/cli** — **`locales:` declared the app's languages in `voltro dev` and demanded catalogs in `voltro build` — so an app on its own i18n stack got the right language in development and `lang="en"` in production.**
107
+
108
+ `voltro dev` already separates the two: the catalogs decide the CATALOG wiring, `locales:` decides the language facts (`<html lang>`, the resolved locale, `meta.locale`). The build did not, which is this repo's dev/build drift in its worse direction — the environment nobody watches, on the day of the first deploy.
109
+
110
+ Reported by a consumer on react-i18next, who left `locales:` out because it demanded `src/locales/<lang>.ts` they do not have, and got `<html lang="en">` plus `locale: 'en'` handed to every page's `meta` in a German app:
111
+
112
+ > *"Der Parameter `locale` ist damit für uns nicht nur nutzlos, sondern > gefährlich: hätten wir ihm vertraut, wäre nach der Hydration jeder deutsche > Tab englisch geworden."*
113
+
114
+ A value the framework hands you that is confidently wrong is worse than one it withholds.
115
+
116
+ The build now emits a resolver-only i18n runtime when `locales:` is declared with no catalogs: `resolveLocale` (cookie > `Accept-Language` > default, negotiated against the declared set) with an identity `outerWrap`. No provider, no React, no catalog imports — and `voltro start` sets `<html lang>` and `meta.locale` from the real negotiation instead of falling back to `'en'`.
117
+ - **@voltro/cli** — **A plugin's depth was addressed as a 1 KB README that says "read the website" — a dead end for an agent working inside a repo.**
118
+
119
+ We spent a round correcting a documented recommendation (`resolveSubjectId`) that the one team who had lived it had retracted. That consumer went looking for the correction in the channel our own `AGENTS.md` advertises as authoritative:
120
+
121
+ ```
122
+ resolveSubjectId → 0 Treffer
123
+ | notifications | node_modules/@voltro/plugin-notifications/README.md |
124
+ ```
125
+
126
+ > *"Diese Datei ist 1 039 Byte … Für einen Menschen ist das ein Klick. Für einen > Agenten, der im Repo arbeitet, ist es eine Sackgasse."*
127
+
128
+ Their measurement was slightly off — the correction WAS in `agent-docs/`, in `whats-new.md` — and the truth is worse than their reading. `whats-new.md` is regenerated from the LATEST release section only, so the lesson would have vanished at 0.31.0 regardless. Same structure as a codemod note: delivered once, at a boundary, unrevisable. **A durable lesson needs a durable address.**
129
+
130
+ Each plugin's docs page is now compiled into `agent-docs/plugins/<slug>.md` and the index points there first, README second — in the generated template AND in the per-project file the seeder writes, which is the one users actually read. The per-plugin modules stay out of the TOPIC table on purpose: 42 rows all titled "Plugins" would be a worse index than the dead end it replaces.
131
+ - **@voltro/database, @voltro/plugin-webhooks, @voltro/runtime** — **A `QueryDescriptor` without `order` took the SELECT compiler down, and the framework reported it as a database failure.**
132
+
133
+ {"msg":"mutation.webhooks.test failed: e.order is not iterable", "dbCauseChain":"TypeError: e.order is not iterable"}
134
+
135
+ `dbCauseChain` sent the reporter to the connection, the driver and the dialect. The database was never involved — the SELECT was never built. Nothing they could write in app code reached it either: the descriptor is constructed inside `@voltro/plugin-webhooks`, so `ctx.webhooks.testTarget(...)` was unusable and the outgoing-webhook path could not be exercised end to end.
136
+
137
+ The field is DECLARED required, which is why this looked impossible. A descriptor is hand-built at ~130 sites and nearly all of them cast (`as never`) past the generic row type — and a cast switches the required-field check off for the whole literal. Four sites shipped `order: undefined`.
138
+
139
+ Fixed as ONE read path (`orderClausesOf`) rather than a guard at the reported line: `order` was dereferenced bare in TWELVE places across two compilers (three in `sqlCompiler.ts`, nine in `jsonEagerCompiler.ts`), so patching the crash site would have moved it rather than removed it. The four lying call sites are corrected, and a source scan fails on a fifth.
140
+ - **@voltro/cli, @voltro/i18n** — **Dev SSR could build an `<I18nProvider>` the app's own `useT()` could not see, and the diagnosis we shipped pointed at a check that cannot observe the cause.**
141
+
142
+ `ssr.noExternal: ['react-intl']` without `@voltro/i18n` is itself an instance splitter: Vite externalizes a node_modules dependency in SSR, so Node — not Vite — resolves everything IT imports. An externalized `@voltro/i18n` therefore got Node's react-intl while every Vite-transformed module got the bundled one. Two instances, two contexts, and a provider invisible to a `useT()` with an error byte-identical to having no provider at all. Both are now bundled and deduped together in `voltro dev`, `voltro build` and `voltro start`, pinned by `i18nSingleInstance.test.ts` across all three configs.
143
+
144
+ The framework's diagnosis told readers to check with `pnpm ls -r --depth 10 @voltro/i18n react-intl`, and to report a framework bug if that showed one version of each. A consumer did exactly that — one version, one directory in the store, four fresh boots — and was still at a 500 on every SSR page. **The criterion was the defect: `pnpm ls` enumerates versions ON DISK and the failure is module INSTANCES in a process.** One file reached down two paths is two instances, and no package manager can see it. `@voltro/i18n` now registers each of its evaluations with the identity of the react-intl it is bound to, and the diagnosis reports the counts and the paths from the process where the render failed. When it counts one of each, it says the framework is at fault instead of making that conditional on a check that could not come back false.
145
+
146
+ The transferable half: an instruction to VERIFY has to be able to return false for the cause you are diagnosing. A check that cannot turns "I don't know" into "the framework is at fault", and the reader stops looking.
147
+ - **@voltro/plugin-webhooks** — **A webhook routing filter's operators were ANDed in the documentation and first-match-wins in the matcher, so the documented range OVER-matched.**
148
+
149
+ `{ gte: 100, lt: 1000 }` — the second example on the plugin's docs page — evaluated `gte` and returned, ignoring `lt`. A `total` of 5000 satisfied a filter declared as 100–1000. That is the worse direction of wrong: an under-matching filter delivers nothing and gets noticed, an over-matching one posts a partner data their own filter says they must not receive, and nothing anywhere reports it. Every present operator must now hold, and an operator object with no recognised key (`{ gtE: 5 }`, `{}`) matches nothing rather than everything.
150
+
151
+ `gt` / `gte` / `lt` / `lte` have always been TYPED `number | string` and compared only when both sides were numbers — so a string bound type-checked, subscribed, stored and matched nothing, which reads exactly like an event that never fired. Strings now compare lexicographically, which is what makes `{ 'payload.at': { lt: '2026-01-01' } }` work on an ISO timestamp. Mixed types still match nothing, deliberately: `'10' < 9` depends on which side JavaScript converts.
152
+
153
+ And the doc comment above the matcher called the equality form "v1" with operators as a future possibility, while `compareValue` implemented all six twenty lines below it. A consumer read that, refused a `resourceIds` filter for the whole life of the feature, and shipped a typed `ValidationError` telling their own users it was impossible. A comment describing an intention rather than the code under it is not a smaller doc — it is a wrong one, and more expensive than none.
154
+
155
+ ---
156
+
42
157
  ## [0.30.2] — 2026-08-09
43
158
 
44
159
  ### Changed
@@ -782,30 +782,30 @@ var r = (e) => {
782
782
  return t;
783
783
  }, Ue = () => [...W.values()], We = () => {
784
784
  W.clear();
785
- }, Ge = (e) => W.get(e)?.isReactive !== !1, Ke = (e) => ({
785
+ }, Ge = (e) => W.get(e)?.isReactive !== !1, Ke = (e) => e.order ?? [], qe = (e) => ({
786
786
  op: "count",
787
787
  alias: e ?? "count"
788
- }), qe = (e, t) => ({
788
+ }), Je = (e, t) => ({
789
789
  op: "count-distinct",
790
790
  column: e,
791
791
  alias: t ?? `count_distinct_${e}`
792
- }), Je = (e, t) => ({
792
+ }), Ye = (e, t) => ({
793
793
  op: "sum",
794
794
  column: e,
795
795
  alias: t ?? `sum_${e}`
796
- }), Ye = (e, t) => ({
796
+ }), Xe = (e, t) => ({
797
797
  op: "avg",
798
798
  column: e,
799
799
  alias: t ?? `avg_${e}`
800
- }), Xe = (e, t) => ({
800
+ }), Ze = (e, t) => ({
801
801
  op: "min",
802
802
  column: e,
803
803
  alias: t ?? `min_${e}`
804
- }), Ze = (e, t) => ({
804
+ }), Qe = (e, t) => ({
805
805
  op: "max",
806
806
  column: e,
807
807
  alias: t ?? `max_${e}`
808
- }), Qe = (e, t) => ({
808
+ }), $e = (e, t) => ({
809
809
  op: "column",
810
810
  column: e,
811
811
  alias: t ?? e
@@ -823,33 +823,33 @@ var r = (e) => {
823
823
  queries: t.map((e) => e.descriptor)
824
824
  }
825
825
  });
826
- }, $e = q("union"), et = q("union-all"), tt = q("intersect"), nt = q("except"), J = (e) => ({ over: (t) => ({
826
+ }, et = q("union"), tt = q("union-all"), nt = q("intersect"), rt = q("except"), J = (e) => ({ over: (t) => ({
827
827
  ...e,
828
828
  window: t
829
- }) }), rt = (e = "rowNumber") => J({
829
+ }) }), it = (e = "rowNumber") => J({
830
830
  op: "window-row-number",
831
831
  alias: e
832
- }), it = (e = "rank") => J({
832
+ }), at = (e = "rank") => J({
833
833
  op: "window-rank",
834
834
  alias: e
835
- }), at = (e = "denseRank") => J({
835
+ }), ot = (e = "denseRank") => J({
836
836
  op: "window-dense-rank",
837
837
  alias: e
838
- }), ot = (e, t = 1, n) => J({
838
+ }), st = (e, t = 1, n) => J({
839
839
  op: "window-lag",
840
840
  column: e,
841
841
  alias: n ?? `lag_${e}`,
842
842
  offset: t
843
- }), st = (e, t = 1, n) => J({
843
+ }), ct = (e, t = 1, n) => J({
844
844
  op: "window-lead",
845
845
  column: e,
846
846
  alias: n ?? `lead_${e}`,
847
847
  offset: t
848
- }), ct = (e, t) => J({
848
+ }), lt = (e, t) => J({
849
849
  op: "window-sum-over",
850
850
  column: e,
851
851
  alias: t ?? `sum_${e}_over`
852
- }), lt = (e, t) => J({
852
+ }), ut = (e, t) => J({
853
853
  op: "window-avg-over",
854
854
  column: e,
855
855
  alias: t ?? `avg_${e}_over`
@@ -1131,7 +1131,7 @@ function Z(e) {
1131
1131
  sourceTable: e
1132
1132
  });
1133
1133
  }
1134
- var ut = (e, t, n) => Q(e, "id", t, n), Q = (e, t, n, r, i = "asc") => {
1134
+ var dt = (e, t, n) => Q(e, "id", t, n), Q = (e, t, n, r, i = "asc") => {
1135
1135
  let a = [...e.order.filter((e) => e.column !== t), {
1136
1136
  column: t,
1137
1137
  direction: i
@@ -1146,11 +1146,11 @@ var ut = (e, t, n) => Q(e, "id", t, n), Q = (e, t, n, r, i = "asc") => {
1146
1146
  order: a,
1147
1147
  take: r
1148
1148
  };
1149
- }, dt = (e) => {
1149
+ }, ft = (e) => {
1150
1150
  let t = {};
1151
1151
  for (let [n, r] of Object.entries(e)) G(r), t[n] = Z(r);
1152
1152
  return t;
1153
- }, ft = (e) => e.isView === !0, pt = (e, t, n) => {
1153
+ }, pt = (e) => e.isView === !0, mt = (e, t, n) => {
1154
1154
  if (y(e), n.trim().length === 0) throw Error(`view('${e}', …): the SELECT body is empty — a view must define a query.`);
1155
1155
  let r = f(t);
1156
1156
  for (let t of Object.keys(r)) b(e, t);
@@ -1160,7 +1160,7 @@ var ut = (e, t, n) => Q(e, "id", t, n), Q = (e, t, n, r, i = "asc") => {
1160
1160
  isView: !0,
1161
1161
  viewSelect: n
1162
1162
  };
1163
- }, mt = (e) => Z({
1163
+ }, ht = (e) => Z({
1164
1164
  ...e,
1165
1165
  isReactive: !1,
1166
1166
  appliedMixins: [],
@@ -1168,7 +1168,7 @@ var ut = (e, t, n) => Q(e, "id", t, n), Q = (e, t, n, r, i = "asc") => {
1168
1168
  appliedUniques: [],
1169
1169
  appliedFullText: [],
1170
1170
  appliedChecks: []
1171
- }), ht = (e, t, n = null) => {
1171
+ }), gt = (e, t, n = null) => {
1172
1172
  let r = n === null ? V(e.tableName, t) : `${V(n, t)}.${V(e.tableName, t)}`, i = e.viewSelect.trim();
1173
1173
  switch (t) {
1174
1174
  case "postgres":
@@ -1178,16 +1178,16 @@ var ut = (e, t, n) => Q(e, "id", t, n), Q = (e, t, n, r, i = "asc") => {
1178
1178
  case "sqlite":
1179
1179
  case "turso": return `DROP VIEW IF EXISTS ${r};\nCREATE VIEW ${r} AS ${i};`;
1180
1180
  }
1181
- }, gt = /^[A-Za-z_][A-Za-z0-9_]*$/, _t = (e) => "$" + e.map((e) => typeof e == "number" ? `[${e}]` : gt.test(e) ? `.${e}` : `."${e.replace(/"/g, "\\\"")}"`).join(""), vt = (e) => "{" + e.map((e) => {
1181
+ }, _t = /^[A-Za-z_][A-Za-z0-9_]*$/, vt = (e) => "$" + e.map((e) => typeof e == "number" ? `[${e}]` : _t.test(e) ? `.${e}` : `."${e.replace(/"/g, "\\\"")}"`).join(""), yt = (e) => "{" + e.map((e) => {
1182
1182
  let t = String(e);
1183
1183
  return /^[A-Za-z0-9_]+$/.test(t) ? t : `"${t.replace(/(["\\])/g, "\\$1")}"`;
1184
- }).join(",") + "}", yt = (e, t, n, r = {}) => {
1184
+ }).join(",") + "}", bt = (e, t, n, r = {}) => {
1185
1185
  let i = V(e, n), a = r.numeric ?? !1;
1186
1186
  if (n === "postgres") {
1187
- let e = `(${i} #>> '${vt(t)}'::text[])`;
1187
+ let e = `(${i} #>> '${yt(t)}'::text[])`;
1188
1188
  return a ? `${e}::numeric` : e;
1189
1189
  }
1190
- let o = _t(t);
1190
+ let o = vt(t);
1191
1191
  switch (n) {
1192
1192
  case "mysql":
1193
1193
  case "mariadb": {
@@ -1204,10 +1204,10 @@ var ut = (e, t, n) => Q(e, "id", t, n), Q = (e, t, n, r, i = "asc") => {
1204
1204
  return a ? `CAST(${e} AS REAL)` : e;
1205
1205
  }
1206
1206
  }
1207
- }, bt = (e) => {
1207
+ }, xt = (e) => {
1208
1208
  if (!e.id || e.id.length === 0) throw Error("migration: `id` is required + must be non-empty");
1209
1209
  if (!e.up || !e.down) throw Error(`migration ${e.id}: both \`up\` and \`down\` must be functions`);
1210
1210
  return e;
1211
- }, xt = /^\d{8}_\d{6}_[a-z0-9_]+\.ts$/, St = (e) => typeof e == "object" && !!e && "id" in e && "up" in e && "down" in e && typeof e.id == "string" && typeof e.up == "function" && typeof e.down == "function", $ = /* @__PURE__ */ new Set(["voltro_isr_cache"]), Ct = (e) => $.has(e) || e.startsWith("_voltro_") || e.startsWith("_cloud_") || e.startsWith("cluster_"), wt = /__dropped_\d{14}$/, Tt = (e) => wt.test(e);
1211
+ }, St = /^\d{8}_\d{6}_[a-z0-9_]+\.ts$/, Ct = (e) => typeof e == "object" && !!e && "id" in e && "up" in e && "down" in e && typeof e.id == "string" && typeof e.up == "function" && typeof e.down == "function", $ = /* @__PURE__ */ new Set(["voltro_isr_cache"]), wt = (e) => $.has(e) || e.startsWith("_voltro_") || e.startsWith("_cloud_") || e.startsWith("cluster_"), Tt = /__dropped_\d{14}$/, Et = (e) => Tt.test(e);
1212
1212
  //#endregion
1213
- export { N as $, rt as A, me as At, G as B, se as Bt, Ze as C, ae as Ct, Z as D, ge as Dt, ut as E, he as Et, Ue as F, i as Ft, Fe as G, Be as H, We as I, ne as It, Pe as J, ze as K, Ve as L, d as Lt, ct as M, f as Mt, $e as N, re as Nt, it as O, ee as Ot, et as P, _e as Pt, Ne as Q, K as R, r as Rt, st as S, ce as St, Q as T, de as Tt, V as U, He as V, fe as Vt, Le as W, F as X, P as Y, M as Z, dt as _, ie as _t, St as a, b as at, tt as b, a as bt, ft as c, S as ct, ht as d, ye as dt, j as et, Ye as f, be as ft, qe as g, pe as gt, Ke as h, o as ht, xt as i, Te as it, Je as j, le as jt, Y as k, te as kt, mt as l, xe as lt, Qe as m, m as mt, Ct as n, Ce as nt, bt as o, x as ot, lt as p, ve as pt, Ie as q, Tt as r, we as rt, yt as s, y as st, $ as t, je as tt, pt as u, Se as ut, at as v, oe as vt, Xe as w, l as wt, ot as x, u as xt, nt as y, ue as yt, Ge as z, c as zt };
1213
+ export { Ne as $, Y as A, te as At, Ge as B, c as Bt, Qe as C, ce as Ct, dt as D, he as Dt, Q as E, de as Et, tt as F, _e as Ft, Le as G, He as H, fe as Ht, Ue as I, i as It, Ie as J, Fe as K, We as L, ne as Lt, Ye as M, le as Mt, lt as N, f as Nt, Z as O, ge as Ot, et as P, re as Pt, M as Q, Ve as R, d as Rt, ct as S, u as St, Ke as T, l as Tt, Be as U, G as V, se as Vt, V as W, P as X, Pe as Y, F as Z, ft as _, pe as _t, Ct as a, Te as at, nt as b, ue as bt, pt as c, y as ct, gt as d, Se as dt, N as et, Xe as f, ye as ft, Je as g, o as gt, qe as h, m as ht, St as i, we as it, it as j, me as jt, at as k, ee as kt, ht as l, S as lt, $e as m, ve as mt, wt as n, je as nt, xt as o, b as ot, ut as p, be as pt, ze as q, Et as r, Ce as rt, bt as s, x as st, $ as t, j as tt, mt as u, xe as ut, ot as v, ie as vt, Ze as w, ae as wt, st as x, a as xt, rt as y, oe as yt, K as z, r as zt };
package/dist/index.d.ts CHANGED
@@ -3559,6 +3559,29 @@ export declare interface OrderClause {
3559
3559
  readonly direction: 'asc' | 'desc';
3560
3560
  }
3561
3561
 
3562
+ /**
3563
+ * The descriptor's ORDER BY clauses, tolerating an absent `order`.
3564
+ *
3565
+ * The field is declared REQUIRED above and that is not what the compilers can
3566
+ * rely on. A `QueryDescriptor` is hand-built at ~130 call sites across the
3567
+ * framework and its plugins, and nearly every one of them casts (`as never`) to
3568
+ * get past the generic row type — which switches the required-field check off
3569
+ * for the whole object literal. Four of those sites passed `order: undefined`.
3570
+ *
3571
+ * What that produced is the reason this is a function rather than eleven local
3572
+ * `?? []`s. `for (const c of descriptor.order)` threw `e.order is not iterable`
3573
+ * from inside the SELECT compiler, and the framework wrapped it as a
3574
+ * `dbCauseChain` — so a plugin bug arrived at the consumer looking like their
3575
+ * DATABASE had failed. They spent a round on the connection.
3576
+ *
3577
+ * Every other optional clause on the descriptor (`groupBy`, `having`,
3578
+ * `spatialClause.order`) is already read through a guard. This one was iterated
3579
+ * bare in TWELVE places across two compilers, so a guard at the crash site
3580
+ * would have moved the crash rather than removed it — the same one-of-N shape
3581
+ * as the four dialect stores.
3582
+ */
3583
+ export declare const orderClausesOf: (descriptor: Pick<QueryDescriptor, "order">) => ReadonlyArray<OrderClause>;
3584
+
3562
3585
  export declare interface OrPredicate {
3563
3586
  readonly or: ReadonlyArray<Predicate>;
3564
3587
  }