@voltro/cli 0.2.2 → 0.3.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 (61) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/THIRD-PARTY-NOTICES.md +234 -1
  3. package/bin/voltro.mjs +71 -1
  4. package/dist/apiBuild-DdgYydVJ.js +193 -0
  5. package/dist/apiBuild-cadmH8ca.js +2 -0
  6. package/dist/bin.js +2 -2
  7. package/dist/{commands-DQy4812j.js → commands-CXESev-z.js} +2182 -1780
  8. package/dist/dev-DemiMSSl.js +2 -0
  9. package/dist/{dev--jHe1vcu.js → dev-x_VqbV_8.js} +1469 -1458
  10. package/dist/index.d.ts +2 -0
  11. package/dist/index.js +1 -1
  12. package/dist/{serveCommand-93rRdEp0.js → serveCommand-Dttqe5Ms.js} +2 -2
  13. package/dist/serveEntry.js +2 -2
  14. package/package.json +22 -19
  15. package/templates/AGENTS.core.md +14 -0
  16. package/templates/AGENTS.md +14 -0
  17. package/templates/agent-docs/_manifest.json +1 -1
  18. package/templates/agent-docs/cli.md +59 -0
  19. package/templates/agent-docs/data.md +39 -0
  20. package/templates/agent-docs/database/advancedqueries.md +27 -0
  21. package/templates/agent-docs/deployment.md +3 -1
  22. package/templates/apps/api-ai/package.json +7 -7
  23. package/templates/apps/api-auth/package.json +8 -8
  24. package/templates/apps/api-backend/package.json +7 -7
  25. package/templates/apps/api-backend-deactivation/package.json +7 -7
  26. package/templates/apps/api-backend-mail/package.json +8 -8
  27. package/templates/apps/api-backend-mariadb/package.json +9 -9
  28. package/templates/apps/api-backend-storage/package.json +8 -8
  29. package/templates/apps/api-data-advanced/package.json +8 -8
  30. package/templates/apps/api-durable/package.json +8 -8
  31. package/templates/apps/api-feature-flags/package.json +9 -9
  32. package/templates/apps/api-governance/package.json +8 -8
  33. package/templates/apps/api-kv/package.json +8 -8
  34. package/templates/apps/api-moderation/package.json +8 -8
  35. package/templates/apps/api-observability/package.json +8 -8
  36. package/templates/apps/api-ratelimit/package.json +8 -8
  37. package/templates/apps/api-rbac/package.json +8 -8
  38. package/templates/apps/api-rest/package.json +7 -7
  39. package/templates/apps/api-saas/package.json +11 -11
  40. package/templates/apps/api-search/package.json +8 -8
  41. package/templates/apps/api-versioning/package.json +8 -8
  42. package/templates/apps/api-webhooks/package.json +8 -8
  43. package/templates/apps/changelog/package.json +6 -6
  44. package/templates/apps/edge-functions/package.json +2 -2
  45. package/templates/apps/frontend-admin/package.json +8 -8
  46. package/templates/apps/frontend-app/package.json +8 -8
  47. package/templates/apps/frontend-blank/package.json +7 -7
  48. package/templates/apps/frontend-contact/package.json +7 -7
  49. package/templates/apps/frontend-dashboard/package.json +7 -7
  50. package/templates/apps/frontend-docs/package.json +7 -7
  51. package/templates/apps/frontend-i18n/package.json +6 -6
  52. package/templates/apps/frontend-landing/package.json +7 -7
  53. package/templates/apps/frontend-spa/package.json +7 -7
  54. package/templates/apps/frontend-ssr/package.json +7 -7
  55. package/templates/apps/frontend-ssr-api/package.json +8 -8
  56. package/templates/apps/frontend-static-blog/package.json +6 -6
  57. package/templates/baselines/compose/docker/api.Dockerfile +10 -5
  58. package/templates/baselines/compose-mariadb/docker/api.Dockerfile +10 -5
  59. package/dist/apiBuild-OpZROja5.js +0 -2
  60. package/dist/apiBuild-o70rjpVJ.js +0 -184
  61. package/dist/dev-BKkZglQV.js +0 -2
package/dist/index.d.ts CHANGED
@@ -7,6 +7,8 @@ export declare type CommandSpec = {
7
7
  readonly summary: string;
8
8
  readonly status: 'wired' | 'stub';
9
9
  readonly run: (args: ReadonlyArray<string>) => Promise<number>;
10
+ /** Internal command — dispatchable but omitted from `voltro help`. */
11
+ readonly hidden?: boolean;
10
12
  };
11
13
 
12
14
  export declare const dispatch: (argv: ReadonlyArray<string>) => Promise<number>;
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { i as e, n as t, r as n, t as r } from "./commands-DQy4812j.js";
1
+ import { i as e, n as t, r as n, t as r } from "./commands-CXESev-z.js";
2
2
  //#region src/index.ts
3
3
  var i = "framework";
4
4
  //#endregion
@@ -1,6 +1,6 @@
1
- import { A as e, B as t, C as n, D as r, E as i, G as a, H as o, Kt as s, L as c, O as l, Q as u, R as d, S as f, T as p, U as ee, V as te, W as ne, X as re, Y as m, Z as ie, _ as ae, b as oe, d as se, f as h, g as ce, h as g, i as _, it as v, j as le, k as ue, m as de, n as fe, o as pe, p as y, q as me, s as b, t as x, tt as S, v as C, w as he, x as ge, y as w, z as _e } from "./dev--jHe1vcu.js";
1
+ import { A as e, B as t, C as n, D as r, E as i, G as a, H as o, Kt as s, L as c, O as l, Q as u, R as d, S as f, T as p, U as ee, V as te, W as ne, X as re, Y as m, Z as ie, _ as ae, b as oe, d as se, f as h, g as ce, h as g, i as _, it as v, j as le, k as ue, m as de, n as fe, o as pe, p as y, q as me, s as b, t as x, tt as S, v as C, w as he, x as ge, y as w, z as _e } from "./dev-x_VqbV_8.js";
2
2
  import { a as ve, r as ye } from "./startupRunner-DhlX9nqd.js";
3
- import { a as T, t as be } from "./apiBuild-o70rjpVJ.js";
3
+ import { a as T, t as be } from "./apiBuild-DdgYydVJ.js";
4
4
  import { join as xe } from "node:path";
5
5
  import { pathToFileURL as Se } from "node:url";
6
6
  import { Cause as Ce, Effect as E, Layer as D, ManagedRuntime as we } from "effect";
@@ -1,4 +1,4 @@
1
- import { At as e, nt as t } from "./dev--jHe1vcu.js";
1
+ import { At as e, nt as t } from "./dev-x_VqbV_8.js";
2
2
  import { a as n } from "./startupRunner-DhlX9nqd.js";
3
- import { t as r } from "./serveCommand-93rRdEp0.js";
3
+ import { t as r } from "./serveCommand-Dttqe5Ms.js";
4
4
  export { e as loadDotEnv, n as registerAppModules, t as registerDriver, r as runServe };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voltro/cli",
3
- "version": "0.2.2",
3
+ "version": "0.3.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",
@@ -52,27 +52,30 @@
52
52
  "@effect/platform-node": "^0.107.0",
53
53
  "@effect/sql": "^0.51.1",
54
54
  "@effect/workflow": "^0.18.2",
55
+ "@voltro/ai": "0.3.0",
56
+ "@voltro/cache": "0.3.0",
57
+ "@voltro/data-transfer": "0.3.0",
58
+ "@voltro/database": "0.3.0",
59
+ "@voltro/env": "0.3.0",
60
+ "@voltro/kv": "0.3.0",
61
+ "@voltro/logger": "0.3.0",
62
+ "@voltro/plugin-auth": "0.3.0",
63
+ "@voltro/plugin-broadcast": "0.3.0",
64
+ "@voltro/plugin-mail": "0.3.0",
65
+ "@voltro/plugin-storage": "0.3.0",
66
+ "@voltro/plugin-webhooks": "0.3.0",
67
+ "@voltro/protocol": "0.3.0",
68
+ "@voltro/runtime": "0.3.0",
69
+ "@voltro/serverless": "0.3.0",
70
+ "@voltro/workflow": "0.3.0",
71
+ "chokidar": "^5.0.0",
72
+ "ioredis": "^5.11.1",
73
+ "ts-morph": "^28.0.0"
74
+ },
75
+ "optionalDependencies": {
55
76
  "@tailwindcss/vite": "^4.3.2",
56
77
  "@vitejs/plugin-react": "^6.0.3",
57
- "@voltro/ai": "0.2.2",
58
- "@voltro/cache": "0.2.2",
59
- "@voltro/data-transfer": "0.2.2",
60
- "@voltro/database": "0.2.2",
61
- "@voltro/env": "0.2.2",
62
- "@voltro/kv": "0.2.2",
63
- "@voltro/logger": "0.2.2",
64
- "@voltro/plugin-auth": "0.2.2",
65
- "@voltro/plugin-broadcast": "0.2.2",
66
- "@voltro/plugin-mail": "0.2.2",
67
- "@voltro/plugin-storage": "0.2.2",
68
- "@voltro/plugin-webhooks": "0.2.2",
69
- "@voltro/protocol": "0.2.2",
70
- "@voltro/runtime": "0.2.2",
71
- "@voltro/serverless": "0.2.2",
72
- "@voltro/workflow": "0.2.2",
73
- "chokidar": "^5.0.0",
74
78
  "esbuild": "^0.28.0",
75
- "ioredis": "^5.11.1",
76
79
  "tsx": "^4.23.0",
77
80
  "vite": "^8.1.4"
78
81
  },
@@ -220,8 +220,22 @@ primitive → just save; the supervised dev loop respawns and regenerates.
220
220
  But DO call `assertOwnTenant(input.tenantId, ctx.request.subject)` (from
221
221
  `@voltro/plugin-multitenancy/guard`) in custom mutations that write raw rows,
222
222
  and declare `error: TenantMismatch` — subscriptions are auto-scoped, writes are not.
223
+ - **Gate authorization declaratively with `guards:`.** Add
224
+ `guards: [{ scope: 'notes:write' }]` to `defineMutation`/`defineQuery`/`defineAction`
225
+ — the framework enforces it BEFORE the executor (before the txn opens), fails
226
+ with a typed `ScopeError` (auto-merged into the wire error union), and checks the
227
+ caller's EFFECTIVE scopes (raw ∪ rbac roles). Guards are browser-safe DATA (scope
228
+ strings + a pure `resource: (input) => id` extractor — never a server fn). Use the
229
+ in-handler `ctx.access.has(scope)` / `yield* ctx.access.require(scope)` (or rbac's
230
+ `permission()`) only for checks that need LOADED data (row ownership).
223
231
  - **Don't store secrets in the schema or in `Subject`.** Declare env via
224
232
  `defineEnv` (`configuration.md`); carry only ids in `Subject`.
233
+ - **In production, `voltro build` BEFORE `voltro serve`.** A production
234
+ (`NODE_ENV=production`) serve REQUIRES the precompiled serve bundle and fails
235
+ loud if it's missing — production never transpiles on demand. The generated
236
+ Dockerfiles already do `voltro build` then `voltro serve`; if you write your
237
+ own prod start, build first. (`voltro dev` + a non-prod local `serve` still use
238
+ tsx.) Depth: the deployment topic.
225
239
  - **Don't copy prod data down unmasked.** `voltro data export` (and
226
240
  `--target api`) reads REAL rows — PII included. Copying prod → dev/stage MUST
227
241
  go through a masking profile (`--profile`; classify columns `.sensitive()` /
@@ -220,8 +220,22 @@ primitive → just save; the supervised dev loop respawns and regenerates.
220
220
  But DO call `assertOwnTenant(input.tenantId, ctx.request.subject)` (from
221
221
  `@voltro/plugin-multitenancy/guard`) in custom mutations that write raw rows,
222
222
  and declare `error: TenantMismatch` — subscriptions are auto-scoped, writes are not.
223
+ - **Gate authorization declaratively with `guards:`.** Add
224
+ `guards: [{ scope: 'notes:write' }]` to `defineMutation`/`defineQuery`/`defineAction`
225
+ — the framework enforces it BEFORE the executor (before the txn opens), fails
226
+ with a typed `ScopeError` (auto-merged into the wire error union), and checks the
227
+ caller's EFFECTIVE scopes (raw ∪ rbac roles). Guards are browser-safe DATA (scope
228
+ strings + a pure `resource: (input) => id` extractor — never a server fn). Use the
229
+ in-handler `ctx.access.has(scope)` / `yield* ctx.access.require(scope)` (or rbac's
230
+ `permission()`) only for checks that need LOADED data (row ownership).
223
231
  - **Don't store secrets in the schema or in `Subject`.** Declare env via
224
232
  `defineEnv` (`configuration.md`); carry only ids in `Subject`.
233
+ - **In production, `voltro build` BEFORE `voltro serve`.** A production
234
+ (`NODE_ENV=production`) serve REQUIRES the precompiled serve bundle and fails
235
+ loud if it's missing — production never transpiles on demand. The generated
236
+ Dockerfiles already do `voltro build` then `voltro serve`; if you write your
237
+ own prod start, build first. (`voltro dev` + a non-prod local `serve` still use
238
+ tsx.) Depth: the deployment topic.
225
239
  - **Don't copy prod data down unmasked.** `voltro data export` (and
226
240
  `--target api`) reads REAL rows — PII included. Copying prod → dev/stage MUST
227
241
  go through a masking profile (`--profile`; classify columns `.sensitive()` /
@@ -35,7 +35,7 @@
35
35
  "group": null,
36
36
  "description": "The voltro CLI — every command, grouped by purpose, with the flags that actually matter.",
37
37
  "path": "agent-docs/cli.md",
38
- "files": 8
38
+ "files": 9
39
39
  },
40
40
  {
41
41
  "id": "configuration",
@@ -25,6 +25,7 @@ The dispatcher routes `voltro <command> [args]` to the matching subcommand and p
25
25
  | [Build & run](/docs/cli/build-and-start) | `build`, `start`, `serve` |
26
26
  | Deploy | `deploy` (`plan` — auto-detect the target tier per app + function), [`serverless`](/docs/deployment/serverless-functions) (`list` / `dev` / `serve` / `build` / `deploy`), [`static`](/docs/deployment/static-sites) (`hosts` / `deploy`) |
27
27
  | [Database](/docs/cli/migrate) | `migrate`, `db` (`plan` / `apply` / `plans` / `drift` / `squash` / `restore-snapshot` / `migrate` / `rollback` / `status` / `seed`) |
28
+ | [Update](/docs/cli/update) | `update` (`--to` / `--dry-run` / `--force` / `--exact`) — bump every `@voltro/*`, install, run the codemods that adapt your source to the new version |
28
29
  | [Data transfer](/docs/cli/data) | `data` (`export` / `import` / `unpack` / `inspect` / `backup` / `restore`) — directory + single-file `.vbundle` bundles, streaming assets, masking, at-rest encryption |
29
30
  | Ops / infra | `cache` (`status` / `flush` / `invalidate`), `add` (`redis`), `baseline` (`list` / `status` / `set`), `schedule-manifest`, [`storage`](/docs/plugins/storage) (`doctor` / `cors`) |
30
31
  | AI / data | `embeddings backfill <table> --text <field> --vector <field>` — (re)embed rows the `vectorEmbedding()` mixin missed (pre-existing rows / a model change); `--dry-run` to preview |
@@ -149,6 +150,7 @@ The HTTP surface is reachable directly too — e.g. `curl -s localhost:4000/_vol
149
150
  - [Dev](/docs/cli/dev) — what happens during `voltro dev`
150
151
  - [Build & start](/docs/cli/build-and-start) — production paths
151
152
  - [Migrate](/docs/cli/migrate) — schema changes end-to-end
153
+ - [Update](/docs/cli/update) — upgrade the framework + run codemods
152
154
  - [Inspect & test](/docs/cli/inspect) — debugging + harness
153
155
 
154
156
 
@@ -1648,3 +1650,60 @@ The manifest is read through a TTL-cached source (~10 seconds): a procedure you
1648
1650
  ## Protocol scope
1649
1651
 
1650
1652
  MCP over JSON-RPC 2.0. `initialize` negotiates the protocol revision (`2025-06-18`, `2025-03-26`, `2024-11-05`) and advertises the `tools`, `resources`, and `prompts` capabilities; methods are `ping`, `tools/list`, `tools/call`, `resources/list`, `resources/read`, `prompts/list`, `prompts/get`. The stdio bin frames this as newline-delimited JSON-RPC; the HTTP bin serves it over Streamable HTTP. Both transports route to the same pure protocol core (`handleMcpRequest`, `callTool`, `listResources`/`readResource`, `listPrompts`/`getPrompt`, `routeHttp`), all exported from `@voltro/mcp`.
1653
+
1654
+
1655
+
1656
+ ---
1657
+
1658
+ <!-- source: en/cli/update.md -->
1659
+ ## Update
1660
+
1661
+ _voltro update — bump the framework to the latest version and run the codemods that adapt your source to any changed APIs._
1662
+
1663
+ `voltro update` upgrades an app to the latest framework release. It does three things in order:
1664
+
1665
+ 1. **Bump** every `@voltro/*` dependency in `package.json` to the target version.
1666
+ 2. **Install** with your package manager (detected from the lockfile — pnpm / npm / yarn / bun).
1667
+ 3. **Run the codemods** shipped with the target version — automatic source rewrites for any breaking API change, plus printed manual steps for anything that can't be automated.
1668
+
1669
+ ```bash
1670
+ voltro update # bump to the latest published version, install, run codemods
1671
+ voltro update --to 0.4.0 # pin an explicit target version
1672
+ voltro update --dry-run # preview the bump + which codemods would run — writes nothing
1673
+ voltro update --force # allow a dirty working tree (not recommended)
1674
+ voltro update --exact # pin exact versions (drop the ^ / ~ range prefix)
1675
+ ```
1676
+
1677
+ ## The clean-tree guard
1678
+
1679
+ Codemods **rewrite your source**, so you need a clean diff to review afterwards. `voltro update` refuses to run on a dirty git working tree — commit or stash first. Use `--dry-run` to preview without touching anything, or `--force` to override the guard (you accept a mixed diff).
1680
+
1681
+ ## What gets bumped
1682
+
1683
+ Every `@voltro/*` entry in `dependencies` and `devDependencies`, 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.
1684
+
1685
+ ## Codemods
1686
+
1687
+ Each breaking public-API change in a release ships a **codemod**. When you update across that release, `voltro update` applies it:
1688
+
1689
+ - A **transform codemod** rewrites your source automatically — renamed imports, moved modules, changed component props, restructured call signatures. The rewrite is scoped to files that actually import the affected symbol.
1690
+ - A **manual codemod** prints written steps during the update, only when your app is affected — for changes that can't be mechanically transformed (a behavior change, a descriptor/executor restructure). Where the affected sites can be found but the fix needs your judgment, a codemod inserts `// TODO(voltro-migration): …` markers so you can locate every spot.
1691
+
1692
+ Codemods that span multiple versions run in order (e.g. upgrading `0.2.0 → 0.4.0` runs the `0.3.0` and `0.4.0` codemods in sequence). Review the resulting diff before committing.
1693
+
1694
+ ## The database is separate
1695
+
1696
+ `voltro update` does **not** touch your database. Framework-owned `_voltro_*` tables (workflow runs, schedules, …) are reconciled by the declarative differ, not by codemods: when a release changes one of those tables, your next `voltro db apply` (or `voltro dev` boot, which auto-applies) picks up the change. After an update:
1697
+
1698
+ ```bash
1699
+ voltro update
1700
+ voltro db apply # reconcile any changed framework tables — NOT voltro db migrate
1701
+ # then run your typecheck to confirm your code compiles against the new API
1702
+ ```
1703
+
1704
+ Use `voltro db apply` (the declarative diff), not `voltro db migrate` (the imperative file-runner) — only the former reconciles framework tables.
1705
+
1706
+ ## Where to read next
1707
+
1708
+ - [Migrate](/docs/cli/migrate) — schema changes end-to-end
1709
+ - [Build & start](/docs/cli/build-and-start) — production paths
@@ -389,6 +389,17 @@ The descriptor is the wire contract. The `.mutation.server.ts` file is the serve
389
389
  5. Commit the transaction.
390
390
  6. Drain the batched change events so matching query subscriptions receive new snapshots or deltas.
391
391
 
392
+ ## Partial updates: `ctx.store.applyDefined`
393
+
394
+ A partial-update mutation should set only the fields the caller actually sent — not overwrite an omitted field with `undefined`. Instead of hand-writing `if (input.x !== undefined) patch.x = input.x` per field, use `ctx.store.applyDefined(input, keys)`:
395
+
396
+ ```ts
397
+ const execute = async (input: UpdateNote, ctx: AppContext) =>
398
+ ctx.store.update('notes', input.id, ctx.store.applyDefined(input, ['title', 'body', 'dueAt']))
399
+ ```
400
+
401
+ It returns a patch containing only the listed keys whose value is not `undefined` (a defined falsy value like `0` / `''` / `false` IS kept). Also importable standalone (`import { applyDefined } from '@voltro/runtime'`) for seeds/tests.
402
+
392
403
  ## Calling From React
393
404
 
394
405
  ```tsx
@@ -465,6 +476,34 @@ const create = useMutation('app', 'notes.create').withOptimistic((cache, input)
465
476
 
466
477
  Use `.withoutOptimistic()` for effects that should not preview locally.
467
478
 
479
+ ### Nested / path-targeted optimistic
480
+
481
+ By default a `target` patches the **flat top-level row array** a query returns, keyed by `id`. When a query returns a **nested array** — a JSON array column (`snapshot.projects`) or a computed/shaped value — add `path` (and, if the item key isn't `id`, `by`) to patch at **item** granularity, with no hand-written `.withOptimistic` reducer:
482
+
483
+ ```ts
484
+ target: {
485
+ table: 'projectRoadmaps', op: 'update',
486
+ path: 'snapshot.projects', // dot-path to the nested array in the value
487
+ identify: (input) => input.projectId, // which item to patch (default input.id)
488
+ }
489
+ ```
490
+
491
+ - `op: 'insert'` appends (or `order: 'prepend'`) a new item into the nested array — safe even on a computed query (a path insert targets a KNOWN document, not a blind top-level add).
492
+ - `op: 'delete'` filters the item out by its key.
493
+ - `by` overrides the item-key field (default `'id'`).
494
+
495
+ Add `match` to patch **only** the entries whose current value satisfies a predicate — the guard that stops a patch bleeding across sibling subscriptions sharing a source table:
496
+
497
+ ```ts
498
+ target: {
499
+ table: 'projectRoadmaps', op: 'update', path: 'snapshot.projects',
500
+ identify: (i) => i.projectId,
501
+ match: (value, input) => value.id === input.roadmapId, // only THIS roadmap's subscription
502
+ }
503
+ ```
504
+
505
+ `path`, `by`, and `match` are browser-safe descriptor data (a dot-path string + a pure predicate) — the same discipline as `identify`/`shape`.
506
+
468
507
  ## Typed Errors
469
508
 
470
509
  ```ts
@@ -219,6 +219,33 @@ text().unique() // single-column, on the column
219
219
 
220
220
  Multi-column uniqueness is declared at the table level with `.unique(name, [cols])` — see [Composite UNIQUE constraints](#composite-unique-constraints) below.
221
221
 
222
+ ## Unique among ACTIVE rows — `.uniqueActive([...])`
223
+
224
+ Enforce uniqueness only among the rows that aren't soft-deleted — "one active roadmap per (project, year)", where a soft-deleted roadmap frees the key for a new one:
225
+
226
+ ```ts
227
+ table('project_roadmaps', {
228
+ id: id(), projectId: text(), year: integer(), deletedAt: timestamp(),
229
+ })
230
+ .softDelete()
231
+ .uniqueActive(['projectId', 'year']) // partial UNIQUE among deletedAt IS NULL
232
+ ```
233
+
234
+ Emits a `CREATE UNIQUE INDEX … WHERE "deletedAt" IS NULL`, so a soft-deleted row leaves the active set and a NEW row with the same key inserts cleanly — no hand-written `generatedAs("CASE WHEN …")` column, and no [resurrection bug](/docs/database/migrations/troubleshooting) where a re-imported soft-deleted key collides. The predicate defaults to the `softDelete()` active set; override it for a custom one:
235
+
236
+ ```ts
237
+ .uniqueActive('byActiveSlug', ['orgId', 'slug'], { where: `"status" = 'open'` })
238
+ ```
239
+
240
+ Cross-dialect:
241
+
242
+ | Dialect | Support |
243
+ |---------------------|----------------------------------------------------------------|
244
+ | postgres / sqlite / mssql | native partial `CREATE UNIQUE INDEX … WHERE` |
245
+ | mysql / mariadb | FAILS LOUDLY at migrate — no partial-index support; a full unique index would forbid re-creating a soft-deleted key. Use a generated STORED column + `.unique([...])` there. |
246
+
247
+ The predicate is emitted verbatim (ANSI double-quoted identifiers, valid on the three supported dialects).
248
+
222
249
  ## When NOT to index
223
250
 
224
251
  - Tables with <10k rows on a fast disk — the cost of maintaining the index outweighs the seq-scan cost.
@@ -411,7 +411,9 @@ voltro build ./apps/api # produces .framework/dist-api/serveBundle/
411
411
  voltro serve ./apps/api # boots from the bundle automatically
412
412
  ```
413
413
 
414
- There is nothing to configure and nothing to opt into: building an API app produces the bundle, and `voltro serve` prefers it whenever it is present. It is fully fallback-safe a missing, stale, or unreadable bundle silently degrades to the standard boot path, so a bad build can never stop the app from serving. A container image that runs `voltro build` at build time and `voltro serve` at start gets the fast boot for free.
414
+ Building an API app produces the bundle, and `voltro serve` boots from it. In **production** (`NODE_ENV=production`) the bundle is **required** — you run `voltro build` before `voltro serve`, and a bundle-build failure is fatal: production **never transpiles on demand**, so it fails loud rather than silently falling back to the slow tsx path. (`voltro dev` and a non-production local `voltro serve` still fall back to tsx as a convenience.) The generated Dockerfiles already do this: `voltro build` at build time, `voltro serve` at start.
415
+
416
+ Because production never transpiles, the serve image needs none of the build toolchain. The framework declares `tsx`, `esbuild`, `vite`, and Tailwind as **optional** dependencies of `@voltro/cli`, and the production Dockerfiles isolate the app with `pnpm --prod --no-optional deploy` — which drops that whole tree (and its native binaries) from the image. A serve image ships only what it runs at runtime: your app, the framework, and the one SQL driver you declared.
415
417
 
416
418
  ## Tiers (Voltro Cloud — coming soon)
417
419
 
@@ -11,16 +11,16 @@
11
11
  "dependencies": {
12
12
  "@effect/platform": "^0.96.1",
13
13
  "@effect/rpc": "^0.75.1",
14
- "@voltro/ai": "0.2.2",
15
- "@voltro/cli": "0.2.2",
16
- "@voltro/database": "0.2.2",
17
- "@voltro/env": "0.2.2",
18
- "@voltro/protocol": "0.2.2",
19
- "@voltro/runtime": "0.2.2",
14
+ "@voltro/ai": "0.3.0",
15
+ "@voltro/cli": "0.3.0",
16
+ "@voltro/database": "0.3.0",
17
+ "@voltro/env": "0.3.0",
18
+ "@voltro/protocol": "0.3.0",
19
+ "@voltro/runtime": "0.3.0",
20
20
  "effect": "^3.21.2"
21
21
  },
22
22
  "devDependencies": {
23
- "@voltro/testing": "0.2.2",
23
+ "@voltro/testing": "0.3.0",
24
24
  "typescript": "^5.7.0",
25
25
  "vitest": "^3.0.0"
26
26
  }
@@ -12,17 +12,17 @@
12
12
  "dependencies": {
13
13
  "@effect/platform": "^0.96.1",
14
14
  "@effect/rpc": "^0.75.1",
15
- "@voltro/cli": "0.2.2",
16
- "@voltro/database": "0.2.2",
17
- "@voltro/env": "0.2.2",
18
- "@voltro/plugin-auth": "0.2.2",
19
- "@voltro/protocol": "0.2.2",
20
- "@voltro/runtime": "0.2.2",
21
- "@voltro/sql-postgres": "0.2.2",
15
+ "@voltro/cli": "0.3.0",
16
+ "@voltro/database": "0.3.0",
17
+ "@voltro/env": "0.3.0",
18
+ "@voltro/plugin-auth": "0.3.0",
19
+ "@voltro/protocol": "0.3.0",
20
+ "@voltro/runtime": "0.3.0",
21
+ "@voltro/sql-postgres": "0.3.0",
22
22
  "effect": "^3.21.2"
23
23
  },
24
24
  "devDependencies": {
25
- "@voltro/testing": "0.2.2",
25
+ "@voltro/testing": "0.3.0",
26
26
  "typescript": "^5.7.0",
27
27
  "vitest": "^3.0.0"
28
28
  }
@@ -12,16 +12,16 @@
12
12
  "dependencies": {
13
13
  "@effect/platform": "^0.96.1",
14
14
  "@effect/rpc": "^0.75.1",
15
- "@voltro/cli": "0.2.2",
16
- "@voltro/database": "0.2.2",
17
- "@voltro/env": "0.2.2",
18
- "@voltro/plugin-multitenancy": "0.2.2",
19
- "@voltro/protocol": "0.2.2",
20
- "@voltro/runtime": "0.2.2",
15
+ "@voltro/cli": "0.3.0",
16
+ "@voltro/database": "0.3.0",
17
+ "@voltro/env": "0.3.0",
18
+ "@voltro/plugin-multitenancy": "0.3.0",
19
+ "@voltro/protocol": "0.3.0",
20
+ "@voltro/runtime": "0.3.0",
21
21
  "effect": "^3.21.2"
22
22
  },
23
23
  "devDependencies": {
24
- "@voltro/testing": "0.2.2",
24
+ "@voltro/testing": "0.3.0",
25
25
  "typescript": "^5.7.0",
26
26
  "vitest": "^3.0.0"
27
27
  }
@@ -12,16 +12,16 @@
12
12
  "dependencies": {
13
13
  "@effect/platform": "^0.96.1",
14
14
  "@effect/rpc": "^0.75.1",
15
- "@voltro/cli": "0.2.2",
16
- "@voltro/database": "0.2.2",
17
- "@voltro/env": "0.2.2",
18
- "@voltro/plugin-deactivation": "0.2.2",
19
- "@voltro/protocol": "0.2.2",
20
- "@voltro/runtime": "0.2.2",
15
+ "@voltro/cli": "0.3.0",
16
+ "@voltro/database": "0.3.0",
17
+ "@voltro/env": "0.3.0",
18
+ "@voltro/plugin-deactivation": "0.3.0",
19
+ "@voltro/protocol": "0.3.0",
20
+ "@voltro/runtime": "0.3.0",
21
21
  "effect": "^3.21.2"
22
22
  },
23
23
  "devDependencies": {
24
- "@voltro/testing": "0.2.2",
24
+ "@voltro/testing": "0.3.0",
25
25
  "typescript": "^5.7.0",
26
26
  "vitest": "^3.0.0"
27
27
  }
@@ -12,18 +12,18 @@
12
12
  "dependencies": {
13
13
  "@react-email/components": "^1.0.12",
14
14
  "@react-email/render": "^1.4.0",
15
- "@voltro/cli": "0.2.2",
16
- "@voltro/database": "0.2.2",
17
- "@voltro/env": "0.2.2",
18
- "@voltro/plugin-mail": "0.2.2",
19
- "@voltro/plugin-multitenancy": "0.2.2",
20
- "@voltro/protocol": "0.2.2",
21
- "@voltro/runtime": "0.2.2",
15
+ "@voltro/cli": "0.3.0",
16
+ "@voltro/database": "0.3.0",
17
+ "@voltro/env": "0.3.0",
18
+ "@voltro/plugin-mail": "0.3.0",
19
+ "@voltro/plugin-multitenancy": "0.3.0",
20
+ "@voltro/protocol": "0.3.0",
21
+ "@voltro/runtime": "0.3.0",
22
22
  "effect": "^3.21.2",
23
23
  "react": "^19.0.0"
24
24
  },
25
25
  "devDependencies": {
26
- "@voltro/testing": "0.2.2",
26
+ "@voltro/testing": "0.3.0",
27
27
  "typescript": "^5.7.0",
28
28
  "vitest": "^3.0.0"
29
29
  }
@@ -12,18 +12,18 @@
12
12
  "dependencies": {
13
13
  "@effect/platform": "^0.96.1",
14
14
  "@effect/rpc": "^0.75.1",
15
- "@voltro/cli": "0.2.2",
16
- "@voltro/database": "0.2.2",
17
- "@voltro/env": "0.2.2",
18
- "@voltro/plugin-multitenancy": "0.2.2",
19
- "@voltro/plugin-storage": "0.2.2",
20
- "@voltro/protocol": "0.2.2",
21
- "@voltro/runtime": "0.2.2",
22
- "@voltro/sql-mysql": "0.2.2",
15
+ "@voltro/cli": "0.3.0",
16
+ "@voltro/database": "0.3.0",
17
+ "@voltro/env": "0.3.0",
18
+ "@voltro/plugin-multitenancy": "0.3.0",
19
+ "@voltro/plugin-storage": "0.3.0",
20
+ "@voltro/protocol": "0.3.0",
21
+ "@voltro/runtime": "0.3.0",
22
+ "@voltro/sql-mysql": "0.3.0",
23
23
  "effect": "^3.21.2"
24
24
  },
25
25
  "devDependencies": {
26
- "@voltro/testing": "0.2.2",
26
+ "@voltro/testing": "0.3.0",
27
27
  "typescript": "^5.7.0",
28
28
  "vitest": "^3.0.0"
29
29
  }
@@ -10,17 +10,17 @@
10
10
  "test": "voltro test"
11
11
  },
12
12
  "dependencies": {
13
- "@voltro/cli": "0.2.2",
14
- "@voltro/database": "0.2.2",
15
- "@voltro/env": "0.2.2",
16
- "@voltro/plugin-multitenancy": "0.2.2",
17
- "@voltro/plugin-storage": "0.2.2",
18
- "@voltro/protocol": "0.2.2",
19
- "@voltro/runtime": "0.2.2",
13
+ "@voltro/cli": "0.3.0",
14
+ "@voltro/database": "0.3.0",
15
+ "@voltro/env": "0.3.0",
16
+ "@voltro/plugin-multitenancy": "0.3.0",
17
+ "@voltro/plugin-storage": "0.3.0",
18
+ "@voltro/protocol": "0.3.0",
19
+ "@voltro/runtime": "0.3.0",
20
20
  "effect": "^3.21.2"
21
21
  },
22
22
  "devDependencies": {
23
- "@voltro/testing": "0.2.2",
23
+ "@voltro/testing": "0.3.0",
24
24
  "typescript": "^5.7.0",
25
25
  "vitest": "^3.0.0"
26
26
  }
@@ -11,17 +11,17 @@
11
11
  "dependencies": {
12
12
  "@effect/platform": "^0.96.1",
13
13
  "@effect/rpc": "^0.75.1",
14
- "@voltro/cli": "0.2.2",
15
- "@voltro/database": "0.2.2",
16
- "@voltro/env": "0.2.2",
17
- "@voltro/plugin-governance": "0.2.2",
18
- "@voltro/plugin-multitenancy": "0.2.2",
19
- "@voltro/protocol": "0.2.2",
20
- "@voltro/runtime": "0.2.2",
14
+ "@voltro/cli": "0.3.0",
15
+ "@voltro/database": "0.3.0",
16
+ "@voltro/env": "0.3.0",
17
+ "@voltro/plugin-governance": "0.3.0",
18
+ "@voltro/plugin-multitenancy": "0.3.0",
19
+ "@voltro/protocol": "0.3.0",
20
+ "@voltro/runtime": "0.3.0",
21
21
  "effect": "^3.21.2"
22
22
  },
23
23
  "devDependencies": {
24
- "@voltro/testing": "0.2.2",
24
+ "@voltro/testing": "0.3.0",
25
25
  "typescript": "^5.7.0",
26
26
  "vitest": "^3.0.0"
27
27
  }
@@ -11,17 +11,17 @@
11
11
  "dependencies": {
12
12
  "@effect/platform": "^0.96.1",
13
13
  "@effect/rpc": "^0.75.1",
14
- "@voltro/cli": "0.2.2",
15
- "@voltro/database": "0.2.2",
16
- "@voltro/env": "0.2.2",
17
- "@voltro/plugin-multitenancy": "0.2.2",
18
- "@voltro/protocol": "0.2.2",
19
- "@voltro/runtime": "0.2.2",
20
- "@voltro/workflow": "0.2.2",
14
+ "@voltro/cli": "0.3.0",
15
+ "@voltro/database": "0.3.0",
16
+ "@voltro/env": "0.3.0",
17
+ "@voltro/plugin-multitenancy": "0.3.0",
18
+ "@voltro/protocol": "0.3.0",
19
+ "@voltro/runtime": "0.3.0",
20
+ "@voltro/workflow": "0.3.0",
21
21
  "effect": "^3.21.2"
22
22
  },
23
23
  "devDependencies": {
24
- "@voltro/testing": "0.2.2",
24
+ "@voltro/testing": "0.3.0",
25
25
  "typescript": "^5.7.0",
26
26
  "vitest": "^3.0.0"
27
27
  }
@@ -12,18 +12,18 @@
12
12
  "dependencies": {
13
13
  "@effect/platform": "^0.96.1",
14
14
  "@effect/rpc": "^0.75.1",
15
- "@voltro/cli": "0.2.2",
16
- "@voltro/database": "0.2.2",
17
- "@voltro/env": "0.2.2",
18
- "@voltro/plugin-flags": "0.2.2",
19
- "@voltro/plugin-multitenancy": "0.2.2",
20
- "@voltro/protocol": "0.2.2",
21
- "@voltro/runtime": "0.2.2",
22
- "@voltro/sql-postgres": "0.2.2",
15
+ "@voltro/cli": "0.3.0",
16
+ "@voltro/database": "0.3.0",
17
+ "@voltro/env": "0.3.0",
18
+ "@voltro/plugin-flags": "0.3.0",
19
+ "@voltro/plugin-multitenancy": "0.3.0",
20
+ "@voltro/protocol": "0.3.0",
21
+ "@voltro/runtime": "0.3.0",
22
+ "@voltro/sql-postgres": "0.3.0",
23
23
  "effect": "^3.21.2"
24
24
  },
25
25
  "devDependencies": {
26
- "@voltro/testing": "0.2.2",
26
+ "@voltro/testing": "0.3.0",
27
27
  "typescript": "^5.7.0",
28
28
  "vitest": "^3.0.0"
29
29
  }