@voltro/cli 0.14.0 → 0.15.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.
Files changed (65) hide show
  1. package/CHANGELOG.md +127 -0
  2. package/dist/{apiBuild-370ITNRk.js → apiBuild-CHItlwPO.js} +2 -2
  3. package/dist/apiBuild-D8juhYsS.js +2 -0
  4. package/dist/bin.js +3 -3
  5. package/dist/{commands-D-teL_TM.js → commands-CPQuQRXh.js} +1779 -1563
  6. package/dist/{dbCommand-Dmi-vHfA.js → dbCommand-B8F29zV0.js} +2 -2
  7. package/dist/dbCommand-Dt7BtdMd.js +2 -0
  8. package/dist/{dev-CPKbckaN.js → dev-BHLM3RPX.js} +1 -1
  9. package/dist/{dev-CKyW_LAv.js → dev-C2lCUhYI.js} +1101 -1081
  10. package/dist/{fileConventions-COg0ZytD.js → fileConventions-Cu0tM-Bl.js} +22 -2
  11. package/dist/{frameworkTableAssembly-DCLhpHsF.js → frameworkTableAssembly-BA2bAmYN.js} +1 -1
  12. package/dist/frameworkTableAssembly-D1zJSgOU.js +2 -0
  13. package/dist/index.js +1 -1
  14. package/dist/{inspectMetrics-CyVMeofr.js → inspectMetrics-CYDSO3za.js} +1 -1
  15. package/dist/{serveCommand-CU3Sw4ZM.js → serveCommand-DBzQcCuv.js} +5 -5
  16. package/dist/serveEntry.js +2 -2
  17. package/dist/{start-D8ATvTzo.js → start-BtgatJN3.js} +1 -1
  18. package/dist/startEntry.js +2 -2
  19. package/package.json +17 -17
  20. package/templates/AGENTS.md +1 -1
  21. package/templates/agent-docs/_index.md +1 -1
  22. package/templates/agent-docs/authentication.md +29 -0
  23. package/templates/agent-docs/cli.md +80 -0
  24. package/templates/agent-docs/data.md +2 -2
  25. package/templates/agent-docs/database/migrations.md +21 -0
  26. package/templates/agent-docs/schema-driven-ui.md +2 -1
  27. package/templates/agent-docs/whats-new.md +240 -48
  28. package/templates/apps/api-ai/package.json +7 -7
  29. package/templates/apps/api-auth/package.json +8 -8
  30. package/templates/apps/api-backend/package.json +7 -7
  31. package/templates/apps/api-backend-deactivation/package.json +7 -7
  32. package/templates/apps/api-backend-mail/package.json +8 -8
  33. package/templates/apps/api-backend-mariadb/package.json +9 -9
  34. package/templates/apps/api-backend-storage/package.json +8 -8
  35. package/templates/apps/api-data-advanced/package.json +8 -8
  36. package/templates/apps/api-durable/package.json +8 -8
  37. package/templates/apps/api-feature-flags/package.json +9 -9
  38. package/templates/apps/api-governance/package.json +8 -8
  39. package/templates/apps/api-kv/package.json +8 -8
  40. package/templates/apps/api-moderation/package.json +8 -8
  41. package/templates/apps/api-observability/package.json +8 -8
  42. package/templates/apps/api-ratelimit/package.json +8 -8
  43. package/templates/apps/api-rbac/package.json +8 -8
  44. package/templates/apps/api-rest/package.json +7 -7
  45. package/templates/apps/api-saas/package.json +11 -11
  46. package/templates/apps/api-search/package.json +8 -8
  47. package/templates/apps/api-versioning/package.json +8 -8
  48. package/templates/apps/api-webhooks/package.json +9 -9
  49. package/templates/apps/changelog/package.json +6 -6
  50. package/templates/apps/edge-functions/package.json +2 -2
  51. package/templates/apps/frontend-admin/package.json +8 -8
  52. package/templates/apps/frontend-app/package.json +8 -8
  53. package/templates/apps/frontend-blank/package.json +7 -7
  54. package/templates/apps/frontend-contact/package.json +7 -7
  55. package/templates/apps/frontend-dashboard/package.json +7 -7
  56. package/templates/apps/frontend-docs/package.json +7 -7
  57. package/templates/apps/frontend-i18n/package.json +6 -6
  58. package/templates/apps/frontend-landing/package.json +7 -7
  59. package/templates/apps/frontend-spa/package.json +7 -7
  60. package/templates/apps/frontend-ssr/package.json +7 -7
  61. package/templates/apps/frontend-ssr-api/package.json +8 -8
  62. package/templates/apps/frontend-static-blog/package.json +6 -6
  63. package/dist/apiBuild-DatWP5hh.js +0 -2
  64. package/dist/dbCommand-B3CeMU3u.js +0 -2
  65. package/dist/frameworkTableAssembly-DYzb2elg.js +0 -2
@@ -1,9 +1,29 @@
1
1
  //#region src/fileConventions.ts
2
- var e = /\.(query|mutation|action|stream)\.ts$/, t = /\.(query|mutation|action|stream)\.server\.ts$/, n = /\.workflow\.tsx$/, r = /\.workflow\.server\.tsx$/, i = /\.workflow\.tsx?$/, a = /\.trigger\.tsx?$/, o = /\.cron\.tsx?$/, s = /\.webhook\.tsx?$/, c = /\.outbox\.tsx?$/, l = /\.subscribe\.tsx?$/, u = /\.reaction\.tsx?$/, d = /\.aggregate\.tsx?$/, f = /\.startup\.tsx?$/, p = /\.seed\.ts$/, m = /\.relations\.ts$/, h = /\.entity\.ts$|\.schema\.ts$|^schema\.ts$/, g = (e) => i.test(e) && !n.test(e) && !r.test(e), _ = /\.page\.tsx$/, v = /\.island\.tsx?$/, y = /\.component\.tsx$/, b = /\.component\.ui\.tsx$/, x = /\.hook\.tsx?$/, S = /\.internal\.tsx?$/, C = /\.types\.tsx?$/, w = /\.fixture\.tsx?$/, T = /\.tracking\.tsx?$/, E = /\.store\.tsx?$/, D = /\.(test|spec)\.tsx?$/, O = /* @__PURE__ */ new Set([
2
+ var e = /\.(query|mutation|action|stream)\.ts$/, t = /\.(query|mutation|action|stream)\.server\.ts$/, n = /\.workflow\.tsx$/, r = /\.workflow\.server\.tsx$/, i = /\.workflow\.tsx?$/, a = /\.trigger\.tsx?$/, o = /\.cron\.tsx?$/, s = /\.webhook\.tsx?$/, c = /\.outbox\.tsx?$/, l = /\.subscribe\.tsx?$/, u = /\.reaction\.tsx?$/, d = /\.aggregate\.tsx?$/, f = /\.startup\.tsx?$/, p = /\.seed\.ts$/, m = /\.relations\.ts$/, h = /\.entity\.ts$|\.schema\.ts$|^schema\.ts$/, g = (e) => i.test(e) && !n.test(e) && !r.test(e), _ = /\.page\.tsx$/, v = /\.island\.tsx?$/, y = /\.component\.tsx$/, b = /\.component\.ui\.tsx$/, x = /\.hook\.tsx?$/, S = /\.internal\.tsx?$/, C = /\.types\.tsx?$/, w = /\.fixture\.tsx?$/, T = /\.tracking\.tsx?$/, E = /\.store\.tsx?$/, D = /\.client\.tsx?$/, O = /\.(test|spec)\.tsx?$/, k = [
3
+ e,
4
+ t,
5
+ i,
6
+ r,
7
+ a,
8
+ o,
9
+ s,
10
+ c,
11
+ l,
12
+ u,
13
+ d,
14
+ f,
15
+ p,
16
+ m,
17
+ h,
18
+ /\.route\.tsx?$/,
19
+ E,
20
+ D,
21
+ /\.(agent|tool|email|connection|serverless|server|config|generated)\.tsx?$/
22
+ ], A = (e) => k.some((t) => t.test(e)), j = /* @__PURE__ */ new Set([
3
23
  "layout.tsx",
4
24
  "error.tsx",
5
25
  "loading.tsx",
6
26
  "not-found.tsx"
7
27
  ]);
8
28
  //#endregion
9
- export { a as C, i as D, n as E, r as O, T as S, s as T, p as _, w as a, l as b, v as c, O as d, e as f, h as g, m as h, o as i, g as k, c as l, u as m, y as n, x as o, t as p, b as r, S as s, d as t, _ as u, f as v, C as w, D as x, E as y };
29
+ export { g as A, a as C, i as D, n as E, r as O, T as S, s as T, p as _, w as a, l as b, v as c, j as d, e as f, h as g, m as h, o as i, A as k, c as l, u as m, y as n, x as o, t as p, b as r, S as s, d as t, _ as u, f as v, C as w, O as x, E as y };
@@ -1,5 +1,5 @@
1
1
  import { n as e } from "./appModuleLoader-C9r9mxZt.js";
2
- import { C as t, D as n, T as r, i, l as a } from "./fileConventions-COg0ZytD.js";
2
+ import { C as t, D as n, T as r, i, l as a } from "./fileConventions-Cu0tM-Bl.js";
3
3
  import { basename as o, join as s } from "node:path";
4
4
  import { pathToFileURL as c } from "node:url";
5
5
  import { Effect as l } from "effect";
@@ -0,0 +1,2 @@
1
+ import { t as e } from "./frameworkTableAssembly-BA2bAmYN.js";
2
+ export { e as assembleFrameworkTables };
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { a as e, i as t, n, o as r, r as i, t as a } from "./commands-D-teL_TM.js";
1
+ import { a as e, i as t, n, o as r, r as i, t as a } from "./commands-CPQuQRXh.js";
2
2
  //#region src/index.ts
3
3
  var o = "framework";
4
4
  //#endregion
@@ -1,4 +1,4 @@
1
- import { d as e, u as t, x as n } from "./fileConventions-COg0ZytD.js";
1
+ import { d as e, u as t, x as n } from "./fileConventions-Cu0tM-Bl.js";
2
2
  import { D as r, E as i, T as a, a as o, p as s, w as c } from "./inspect-Dwx0_tUj.js";
3
3
  import { basename as l, dirname as u, isAbsolute as d, join as f, relative as p, resolve as m } from "node:path";
4
4
  import { fileURLToPath as h, pathToFileURL as g } from "node:url";
@@ -1,10 +1,10 @@
1
- import { ct as e, t } from "./inspectMetrics-CyVMeofr.js";
1
+ import { ct as e, t } from "./inspectMetrics-CYDSO3za.js";
2
2
  import { s as n } from "./seedRunner-D6eu-u5U.js";
3
3
  import { r, t as i } from "./appModuleLoader-C9r9mxZt.js";
4
- import { f as a, m as o, n as s, p as c, u as l } from "./frameworkTableAssembly-DCLhpHsF.js";
5
- import { l as u } from "./fileConventions-COg0ZytD.js";
6
- import { $ as d, A as ee, B as f, C as te, D as ne, E as re, F as p, H as ie, I as m, J as h, K as ae, L as oe, M as g, N as _, O as se, P as v, Q as y, S as ce, T as le, V as ue, X as de, Y as b, Z as x, _ as fe, a as S, b as C, c as pe, et as me, f as w, g as he, h as ge, j as _e, k as ve, n as ye, p as T, q as be, r as xe, s as Se, v as Ce, w as we, x as Te, y as E, z as Ee } from "./dev-CKyW_LAv.js";
7
- import { c as D, r as De } from "./apiBuild-370ITNRk.js";
4
+ import { f as a, m as o, n as s, p as c, u as l } from "./frameworkTableAssembly-BA2bAmYN.js";
5
+ import { l as u } from "./fileConventions-Cu0tM-Bl.js";
6
+ import { $ as d, A as ee, B as f, C as te, D as ne, E as re, F as p, H as ie, I as m, J as h, K as ae, L as oe, M as g, N as _, O as se, P as v, Q as y, S as ce, T as le, V as ue, X as de, Y as b, Z as x, _ as fe, a as S, b as C, c as pe, et as me, f as w, g as he, h as ge, j as _e, k as ve, n as ye, p as T, q as be, r as xe, s as Se, v as Ce, w as we, x as Te, y as E, z as Ee } from "./dev-C2lCUhYI.js";
7
+ import { c as D, r as De } from "./apiBuild-CHItlwPO.js";
8
8
  import { t as Oe } from "./bootTiming-BdyP9nYw.js";
9
9
  import { n as ke } from "./cliArgs-qdZSElM3.js";
10
10
  import { join as Ae } from "node:path";
@@ -1,5 +1,5 @@
1
- import { $ as e } from "./inspectMetrics-CyVMeofr.js";
1
+ import { $ as e } from "./inspectMetrics-CYDSO3za.js";
2
2
  import { c as t } from "./seedRunner-D6eu-u5U.js";
3
3
  import { r as n } from "./appModuleLoader-C9r9mxZt.js";
4
- import { t as r } from "./serveCommand-CU3Sw4ZM.js";
4
+ import { t as r } from "./serveCommand-DBzQcCuv.js";
5
5
  export { e as loadDotEnv, n as registerAppModules, t as registerDriver, r as runServe };
@@ -1,4 +1,4 @@
1
- import { A as e, C as t, E as n, G as r, I as i, J as a, K as o, N as s, O as c, V as l, W as u, X as d, Z as f, _ as ee, a as p, at as m, b as h, c as g, ct as _, dt as v, f as te, g as y, h as b, i as x, it as ne, j as S, k as C, m as w, nt as T, o as re, p as ie, q as E, r as D, rt as O, s as k, t as A, tt as j, v as M, w as ae, y as N } from "./inspectMetrics-CyVMeofr.js";
1
+ import { A as e, C as t, E as n, G as r, I as i, J as a, K as o, N as s, O as c, V as l, W as u, X as d, Z as f, _ as ee, a as p, at as m, b as h, c as g, ct as _, dt as v, f as te, g as y, h as b, i as x, it as ne, j as S, k as C, m as w, nt as T, o as re, p as ie, q as E, r as D, rt as O, s as k, t as A, tt as j, v as M, w as ae, y as N } from "./inspectMetrics-CYDSO3za.js";
2
2
  import { D as oe, E as se, T as ce, a as P, p as le, w as ue } from "./inspect-Dwx0_tUj.js";
3
3
  import { t as de } from "./bootTiming-BdyP9nYw.js";
4
4
  import { dirname as fe, extname as F, join as I, resolve as L } from "node:path";
@@ -1,3 +1,3 @@
1
- import { $ as e } from "./inspectMetrics-CyVMeofr.js";
2
- import { t } from "./start-D8ATvTzo.js";
1
+ import { $ as e } from "./inspectMetrics-CYDSO3za.js";
2
+ import { t } from "./start-BtgatJN3.js";
3
3
  export { e as loadDotEnv, t as runStartCommand };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voltro/cli",
3
- "version": "0.14.0",
3
+ "version": "0.15.0",
4
4
  "description": "The `voltro` CLI — dev server, codegen, migrations, project scaffolding, agent-docs seeding, and production serve.",
5
5
  "keywords": [
6
6
  "voltro",
@@ -62,22 +62,22 @@
62
62
  "@effect/platform-node": "^0.107.0",
63
63
  "@effect/sql": "^0.51.1",
64
64
  "@effect/workflow": "^0.18.2",
65
- "@voltro/ai": "0.14.0",
66
- "@voltro/cache": "0.14.0",
67
- "@voltro/data-transfer": "0.14.0",
68
- "@voltro/database": "0.14.0",
69
- "@voltro/env": "0.14.0",
70
- "@voltro/kv": "0.14.0",
71
- "@voltro/logger": "0.14.0",
72
- "@voltro/plugin-auth": "0.14.0",
73
- "@voltro/plugin-broadcast": "0.14.0",
74
- "@voltro/plugin-mail": "0.14.0",
75
- "@voltro/plugin-storage": "0.14.0",
76
- "@voltro/plugin-webhooks": "0.14.0",
77
- "@voltro/protocol": "0.14.0",
78
- "@voltro/runtime": "0.14.0",
79
- "@voltro/serverless": "0.14.0",
80
- "@voltro/workflow": "0.14.0",
65
+ "@voltro/ai": "0.15.0",
66
+ "@voltro/cache": "0.15.0",
67
+ "@voltro/data-transfer": "0.15.0",
68
+ "@voltro/database": "0.15.0",
69
+ "@voltro/env": "0.15.0",
70
+ "@voltro/kv": "0.15.0",
71
+ "@voltro/logger": "0.15.0",
72
+ "@voltro/plugin-auth": "0.15.0",
73
+ "@voltro/plugin-broadcast": "0.15.0",
74
+ "@voltro/plugin-mail": "0.15.0",
75
+ "@voltro/plugin-storage": "0.15.0",
76
+ "@voltro/plugin-webhooks": "0.15.0",
77
+ "@voltro/protocol": "0.15.0",
78
+ "@voltro/runtime": "0.15.0",
79
+ "@voltro/serverless": "0.15.0",
80
+ "@voltro/workflow": "0.15.0",
81
81
  "chokidar": "^5.0.0",
82
82
  "ioredis": "^5.11.1",
83
83
  "tinyglobby": "^0.2.17",
@@ -526,7 +526,7 @@ each plugin's own README.
526
526
 
527
527
  | Topic | Open | Summary |
528
528
  |---|---|---|
529
- | **What's new in 0.13.0** | `node_modules/@voltro/cli/templates/agent-docs/whats-new.md` | Everything that changed in this version. Read it before hand-rolling something the framework may now ship. |
529
+ | **What's new in 0.14.0** | `node_modules/@voltro/cli/templates/agent-docs/whats-new.md` | Everything that changed in this version. Read it before hand-rolling something the framework may now ship. |
530
530
  | AI | `node_modules/@voltro/cli/templates/agent-docs/ai.md` | How Voltro treats AI — agents, tools, streaming, RAG — all primitives over the same WebSocket as the rest of the framework. |
531
531
  | Authentication | `node_modules/@voltro/cli/templates/agent-docs/authentication.md` | How @voltro/plugin-auth wires password + session-cookie auth across api + web, plus the pluggable identity-strategy protocol. |
532
532
  | Caching | `node_modules/@voltro/cli/templates/agent-docs/caching.md` | Voltro's caching layer (@voltro/cache) — an always-on memory default, swappable Redis-compatible backends, a low-level wrap primitive, and automatic query-result invalidation. |
@@ -9,7 +9,7 @@ each plugin's own README.
9
9
 
10
10
  | Topic | Open | Summary |
11
11
  |---|---|---|
12
- | **What's new in 0.13.0** | `node_modules/@voltro/cli/templates/agent-docs/whats-new.md` | Everything that changed in this version. Read it before hand-rolling something the framework may now ship. |
12
+ | **What's new in 0.14.0** | `node_modules/@voltro/cli/templates/agent-docs/whats-new.md` | Everything that changed in this version. Read it before hand-rolling something the framework may now ship. |
13
13
  | AI | `node_modules/@voltro/cli/templates/agent-docs/ai.md` | How Voltro treats AI — agents, tools, streaming, RAG — all primitives over the same WebSocket as the rest of the framework. |
14
14
  | Authentication | `node_modules/@voltro/cli/templates/agent-docs/authentication.md` | How @voltro/plugin-auth wires password + session-cookie auth across api + web, plus the pluggable identity-strategy protocol. |
15
15
  | Caching | `node_modules/@voltro/cli/templates/agent-docs/caching.md` | Voltro's caching layer (@voltro/cache) — an always-on memory default, swappable Redis-compatible backends, a low-level wrap primitive, and automatic query-result invalidation. |
@@ -525,6 +525,7 @@ interface AuthStrategy {
525
525
  interface AuthStrategyInput {
526
526
  readonly headers: Readonly<Record<string, string | undefined>>
527
527
  readonly clientId: number // per-connection id (for soft re-auth)
528
+ readonly store?: DataStore // the app's store, for a DB-backed strategy
528
529
  }
529
530
  ```
530
531
 
@@ -540,6 +541,34 @@ The `failed`-stops-the-chain rule is a **security** decision, not an ergonomic o
540
541
 
541
542
  > Strategies must be **fast on the no-match path** — a cookie-name substring check, no IO — because every strategy runs on every request until one matches. Do JWKS fetches / DB lookups only *after* you've confirmed the request is yours, and cache them.
542
543
 
544
+ ### Reading your database — `input.store`
545
+
546
+ A strategy that identifies the caller from a row — a session, an API key, a PAT
547
+ — gets the app's `DataStore` on its input:
548
+
549
+ ```ts
550
+ const dbSession: AuthStrategy = {
551
+ id: 'db-session',
552
+ resolve: async ({ headers, store }) => {
553
+ const token = headers.authorization?.slice('Bearer '.length)
554
+ if (token === undefined) return { kind: 'skip' }
555
+ if (store === undefined) return { kind: 'skip' }
556
+ const [row] = await store.query(sessions.byToken(token))
557
+ return row ? { kind: 'matched', subject: toSubject(row) } : { kind: 'failed', reason: 'unknown token' }
558
+ },
559
+ }
560
+ ```
561
+
562
+ It is the **boot** store, not a request-scoped one — strategies resolve before a
563
+ request store exists — and the same value `auth.resolveScopes` receives.
564
+ `undefined` only while the store is still being built (`voltro dev` builds it
565
+ after the auth chain) and on an app with no store, so a strategy should `skip`
566
+ rather than throw.
567
+
568
+ Read users, sessions, keys. A strategy that runs domain writes while deciding
569
+ who the caller is has the two jobs the wrong way round; nothing in the type
570
+ stops you, and it is still wrong.
571
+
543
572
  ## Composing the chain
544
573
 
545
574
  `composeAuthStrategies` turns an ordered list of strategies into a single resolver. First `matched` wins; first `failed` short-circuits to anonymous.
@@ -718,6 +718,45 @@ The restart is a full re-exec — there is no in-process hot-reload of a
718
718
  handler body; editing a query's executor respawns the child (debounced
719
719
  80ms, so a burst of saves collapses into one restart).
720
720
 
721
+ ## When the dev server stops
722
+
723
+ A restart replaces the child; the supervisor keeps watching. When the dev
724
+ server exits **on its own** — an aborted boot, or you stopping it — what
725
+ happens next depends on whether anyone is there to react.
726
+
727
+ **In a terminal**, a crashed boot is something you are about to fix, so
728
+ the supervisor keeps watching and tells you:
729
+
730
+ ```text
731
+ dev server crashed — waiting for a file change exitCode=1
732
+ ```
733
+
734
+ Fix the cause and save. The watcher restarts the server exactly as it
735
+ would for any other edit; you don't retype the command.
736
+
737
+ **Piped, backgrounded or in CI** — anywhere stdout is not a TTY — nobody
738
+ is going to fix anything, so `voltro dev` exits with the child's code:
739
+
740
+ ```text
741
+ dev server exited — supervisor stopping exitCode=1
742
+ ```
743
+
744
+ That half matters because the supervisor used to keep watching in *both*
745
+ cases: a shell that had long since closed still had a `voltro dev` behind
746
+ it holding a watcher, and a CI job that had "finished" kept its runner
747
+ busy. A failed boot is now a failed command wherever no one is looking.
748
+
749
+ A **clean** exit always stops the supervisor, watched or not — a dev
750
+ server ending on purpose is not something to wait out.
751
+
752
+ | Env | Effect |
753
+ |---|---|
754
+ | `VOLTRO_DEV_KEEP_ALIVE=1` | Wait for a fix even without a TTY — a CI runner with a TTY allocated, or a wrapper that pipes output while you watch it. |
755
+ | `VOLTRO_DEV_KEEP_ALIVE=0` | Exit on a crash even in a terminal. |
756
+
757
+ Neither can keep a clean exit alive; that would turn a deliberate
758
+ shutdown into a hang.
759
+
721
760
  The `.env` trigger applies to **both api and web** apps — `process.env` is
722
761
  parsed once at boot, so editing a `.env` (or re-pulling secrets, e.g.
723
762
  `doppler secrets download > .env`) needs a full re-exec. `voltro dev` watches
@@ -2255,6 +2294,7 @@ voltro update # bump to the latest published version, install, r
2255
2294
  voltro update --to 0.4.0 # pin an explicit target version
2256
2295
  voltro update --dry-run # preview the bump + which codemods would run — writes nothing
2257
2296
  voltro update --force # allow a dirty working tree (not recommended)
2297
+ voltro update --only 0.14.0/03_pages-suffix # run just these codemod(s), comma-separated
2258
2298
  voltro update --exact # pin exact versions (drop the ^ / ~ range prefix)
2259
2299
  voltro update --help # every flag — always answered, even on a dirty tree
2260
2300
 
@@ -2263,6 +2303,26 @@ voltro update --codemods-only --from 0.3.0 # re-run codemods 0.3.0
2263
2303
  voltro update --codemods-only --from 0.3.0 --to 0.4.0 # explicit delta
2264
2304
  ```
2265
2305
 
2306
+ ### Taking only part of the jump — `--only`
2307
+
2308
+ Ids are what `--dry-run` prints:
2309
+
2310
+ ```bash
2311
+ voltro update --dry-run
2312
+ voltro update --codemods-only --from 0.13.0 --only 0.14.0/03_pages-suffix,0.14.0/02_reactive-by-default
2313
+ ```
2314
+
2315
+ Useful when part of a jump is load-bearing (without it the app does not build or
2316
+ its routes 404) and part is elective: take the necessary ones, get back to a
2317
+ committable tree, then run the rest. An id that matches nothing in the jump is an
2318
+ error listing the ids that do — "it did nothing" and "you typed it wrong" would
2319
+ otherwise look identical.
2320
+
2321
+ There is no `--required` flag, deliberately. "Required" would have to mean *this
2322
+ app does not run without it*, and that is a property of your app rather than of
2323
+ the codemod: the pages rename is unavoidable for a project with pages and
2324
+ irrelevant to an api-only one. You know which ones you need; we would be guessing.
2325
+
2266
2326
  ## In a workspace, the whole workspace moves
2267
2327
 
2268
2328
  Run `voltro update` anywhere inside a workspace — a `pnpm-workspace.yaml`, or a
@@ -2333,6 +2393,26 @@ voltro update --codemods-only --from 0.5.0 --to 0.6.0
2333
2393
 
2334
2394
  Every `@voltro/*` entry in `dependencies` and `devDependencies` — in every workspace member, see above — with the range style preserved (`^0.3.0` stays caret, `~0.3.0` stays tilde) unless you pass `--exact`. Non-registry specs (`workspace:*`, `catalog:`, `link:`, …) are left untouched — they're already resolved by your monorepo or catalog.
2335
2395
 
2396
+ ## When the install cannot run on this host
2397
+
2398
+ Some projects install in a container with their own store, from an offline
2399
+ mirror, or in a locked-down CI image. `voltro update` runs your package manager
2400
+ on the machine you invoke it from, so on those hosts the install step fails —
2401
+ and it fails *after* the version bump is written, which leaves the tree
2402
+ half-upgraded.
2403
+
2404
+ `--no-install` splits the command where those projects need it split:
2405
+
2406
+ ```bash
2407
+ voltro update --no-install --to 0.14.0 # writes the bump, stops, says what is left
2408
+ # ...install however this project installs...
2409
+ voltro update --codemods-only --from 0.13.0 --to 0.14.0
2410
+ ```
2411
+
2412
+ Step two is not optional and the command says so: the codemods for a jump ship
2413
+ **inside** the target version, so nothing can run them until the install has put
2414
+ that version on disk.
2415
+
2336
2416
  ## Which package manager
2337
2417
 
2338
2418
  `voltro update` never assumes npm. It resolves your project's package manager in this order, starting in the app directory and walking **up to the repo root**:
@@ -911,10 +911,10 @@ Throw a matching error from the server file; the client can narrow on `_tag`.
911
911
 
912
912
  Tagged errors **round-trip structurally** over the wire — the caught value carries `_tag` plus every declared field as real properties (and `instanceof` works, same Schema class both ends). You do **not** need to parse the error message string.
913
913
 
914
- Inside Effect, use `Effect.catchTag('NoteQuotaExceeded', …)`. In a React `try/catch` (outside Effect, where `catchTag` isn't available and the decoded value may be a plain object, not a class instance), match with **`errorTag(err)`** — the dependency-free tag reader `@voltro/client` ships:
914
+ Inside Effect, use `Effect.catchTag('NoteQuotaExceeded', …)`. In a React `try/catch` (outside Effect, where `catchTag` isn't available and the decoded value may be a plain object, not a class instance), match with **`errorTag(err)`** — the dependency-free tag reader `@voltro/protocol` ships (it reads what `toRpc` writes):
915
915
 
916
916
  ```ts
917
- import { errorTag } from '@voltro/client'
917
+ import { errorTag } from '@voltro/protocol'
918
918
 
919
919
  try {
920
920
  await createNote(input)
@@ -435,6 +435,27 @@ VOLTRO_DESTRUCTIVE_OK=1 voltro db apply --note 'retiring users table after migra
435
435
 
436
436
  For complex multi-table retirements (move data out, then drop), use a [file-based migration](./file-based.md) — explicit ordering + a transaction wrapped around the data move.
437
437
 
438
+ ### Name the tables, not the whole run
439
+
440
+ `VOLTRO_DESTRUCTIVE_OK=1` acknowledges **every** lossy op in the plan. That is
441
+ rarely what you mean — a plan with one intended drop and three other lossy ops
442
+ would have all four approved by a single `1`. Give it a comma-separated list
443
+ instead:
444
+
445
+ ```sh
446
+ # Only these tables — every other lossy op in the plan stays refused.
447
+ VOLTRO_DESTRUCTIVE_OK=users,legacy_notes voltro db apply --note 'retiring the pre-migration tables'
448
+ ```
449
+
450
+ An op the list does not name stays blocked, and a plan with anything still
451
+ blocked is refused as a whole. Half a plan applied is how a schema ends up in a
452
+ state neither the declaration nor the database describes.
453
+
454
+ There is deliberately **no `.dropped()` marker for a table**, unlike for a
455
+ column. A dropped column leaves a slot worth documenting in the declaration; a
456
+ dropped table leaves nothing, so the marker would be a dead entry you have to
457
+ remember to delete.
458
+
438
459
  ## `.narrowedFrom()` for type changes
439
460
 
440
461
  A bare column type change is **refuse-to-plan** — the planner blocks it
@@ -1017,7 +1017,8 @@ be on. Rather than repeat that check in every `useMutation` call site, each api'
1017
1017
  runtime carries an error bus; this hook subscribes to it with React lifecycle.
1018
1018
 
1019
1019
  ```tsx
1020
- import { useOnRpcError, errorTag } from '@voltro/client'
1020
+ import { useOnRpcError } from '@voltro/client'
1021
+ import { errorTag } from '@voltro/protocol'
1021
1022
 
1022
1023
  useOnRpcError('app', useCallback((e) => {
1023
1024
  if (errorTag(e.error) === 'Unauthenticated') redirectToSignIn()