@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.
- package/CHANGELOG.md +20 -0
- package/THIRD-PARTY-NOTICES.md +234 -1
- package/bin/voltro.mjs +71 -1
- package/dist/apiBuild-DdgYydVJ.js +193 -0
- package/dist/apiBuild-cadmH8ca.js +2 -0
- package/dist/bin.js +2 -2
- package/dist/{commands-DQy4812j.js → commands-CXESev-z.js} +2182 -1780
- package/dist/dev-DemiMSSl.js +2 -0
- package/dist/{dev--jHe1vcu.js → dev-x_VqbV_8.js} +1469 -1458
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -1
- package/dist/{serveCommand-93rRdEp0.js → serveCommand-Dttqe5Ms.js} +2 -2
- package/dist/serveEntry.js +2 -2
- package/package.json +22 -19
- package/templates/AGENTS.core.md +14 -0
- package/templates/AGENTS.md +14 -0
- package/templates/agent-docs/_manifest.json +1 -1
- package/templates/agent-docs/cli.md +59 -0
- package/templates/agent-docs/data.md +39 -0
- package/templates/agent-docs/database/advancedqueries.md +27 -0
- package/templates/agent-docs/deployment.md +3 -1
- package/templates/apps/api-ai/package.json +7 -7
- package/templates/apps/api-auth/package.json +8 -8
- package/templates/apps/api-backend/package.json +7 -7
- package/templates/apps/api-backend-deactivation/package.json +7 -7
- package/templates/apps/api-backend-mail/package.json +8 -8
- package/templates/apps/api-backend-mariadb/package.json +9 -9
- package/templates/apps/api-backend-storage/package.json +8 -8
- package/templates/apps/api-data-advanced/package.json +8 -8
- package/templates/apps/api-durable/package.json +8 -8
- package/templates/apps/api-feature-flags/package.json +9 -9
- package/templates/apps/api-governance/package.json +8 -8
- package/templates/apps/api-kv/package.json +8 -8
- package/templates/apps/api-moderation/package.json +8 -8
- package/templates/apps/api-observability/package.json +8 -8
- package/templates/apps/api-ratelimit/package.json +8 -8
- package/templates/apps/api-rbac/package.json +8 -8
- package/templates/apps/api-rest/package.json +7 -7
- package/templates/apps/api-saas/package.json +11 -11
- package/templates/apps/api-search/package.json +8 -8
- package/templates/apps/api-versioning/package.json +8 -8
- package/templates/apps/api-webhooks/package.json +8 -8
- package/templates/apps/changelog/package.json +6 -6
- package/templates/apps/edge-functions/package.json +2 -2
- package/templates/apps/frontend-admin/package.json +8 -8
- package/templates/apps/frontend-app/package.json +8 -8
- package/templates/apps/frontend-blank/package.json +7 -7
- package/templates/apps/frontend-contact/package.json +7 -7
- package/templates/apps/frontend-dashboard/package.json +7 -7
- package/templates/apps/frontend-docs/package.json +7 -7
- package/templates/apps/frontend-i18n/package.json +6 -6
- package/templates/apps/frontend-landing/package.json +7 -7
- package/templates/apps/frontend-spa/package.json +7 -7
- package/templates/apps/frontend-ssr/package.json +7 -7
- package/templates/apps/frontend-ssr-api/package.json +8 -8
- package/templates/apps/frontend-static-blog/package.json +6 -6
- package/templates/baselines/compose/docker/api.Dockerfile +10 -5
- package/templates/baselines/compose-mariadb/docker/api.Dockerfile +10 -5
- package/dist/apiBuild-OpZROja5.js +0 -2
- package/dist/apiBuild-o70rjpVJ.js +0 -184
- 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,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
|
|
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-
|
|
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";
|
package/dist/serveEntry.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { At as e, nt as t } from "./dev
|
|
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-
|
|
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.
|
|
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
|
},
|
package/templates/AGENTS.core.md
CHANGED
|
@@ -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()` /
|
package/templates/AGENTS.md
CHANGED
|
@@ -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()` /
|
|
@@ -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
|
-
|
|
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.
|
|
15
|
-
"@voltro/cli": "0.
|
|
16
|
-
"@voltro/database": "0.
|
|
17
|
-
"@voltro/env": "0.
|
|
18
|
-
"@voltro/protocol": "0.
|
|
19
|
-
"@voltro/runtime": "0.
|
|
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.
|
|
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.
|
|
16
|
-
"@voltro/database": "0.
|
|
17
|
-
"@voltro/env": "0.
|
|
18
|
-
"@voltro/plugin-auth": "0.
|
|
19
|
-
"@voltro/protocol": "0.
|
|
20
|
-
"@voltro/runtime": "0.
|
|
21
|
-
"@voltro/sql-postgres": "0.
|
|
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.
|
|
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.
|
|
16
|
-
"@voltro/database": "0.
|
|
17
|
-
"@voltro/env": "0.
|
|
18
|
-
"@voltro/plugin-multitenancy": "0.
|
|
19
|
-
"@voltro/protocol": "0.
|
|
20
|
-
"@voltro/runtime": "0.
|
|
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.
|
|
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.
|
|
16
|
-
"@voltro/database": "0.
|
|
17
|
-
"@voltro/env": "0.
|
|
18
|
-
"@voltro/plugin-deactivation": "0.
|
|
19
|
-
"@voltro/protocol": "0.
|
|
20
|
-
"@voltro/runtime": "0.
|
|
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.
|
|
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.
|
|
16
|
-
"@voltro/database": "0.
|
|
17
|
-
"@voltro/env": "0.
|
|
18
|
-
"@voltro/plugin-mail": "0.
|
|
19
|
-
"@voltro/plugin-multitenancy": "0.
|
|
20
|
-
"@voltro/protocol": "0.
|
|
21
|
-
"@voltro/runtime": "0.
|
|
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.
|
|
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.
|
|
16
|
-
"@voltro/database": "0.
|
|
17
|
-
"@voltro/env": "0.
|
|
18
|
-
"@voltro/plugin-multitenancy": "0.
|
|
19
|
-
"@voltro/plugin-storage": "0.
|
|
20
|
-
"@voltro/protocol": "0.
|
|
21
|
-
"@voltro/runtime": "0.
|
|
22
|
-
"@voltro/sql-mysql": "0.
|
|
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.
|
|
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.
|
|
14
|
-
"@voltro/database": "0.
|
|
15
|
-
"@voltro/env": "0.
|
|
16
|
-
"@voltro/plugin-multitenancy": "0.
|
|
17
|
-
"@voltro/plugin-storage": "0.
|
|
18
|
-
"@voltro/protocol": "0.
|
|
19
|
-
"@voltro/runtime": "0.
|
|
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.
|
|
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.
|
|
15
|
-
"@voltro/database": "0.
|
|
16
|
-
"@voltro/env": "0.
|
|
17
|
-
"@voltro/plugin-governance": "0.
|
|
18
|
-
"@voltro/plugin-multitenancy": "0.
|
|
19
|
-
"@voltro/protocol": "0.
|
|
20
|
-
"@voltro/runtime": "0.
|
|
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.
|
|
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.
|
|
15
|
-
"@voltro/database": "0.
|
|
16
|
-
"@voltro/env": "0.
|
|
17
|
-
"@voltro/plugin-multitenancy": "0.
|
|
18
|
-
"@voltro/protocol": "0.
|
|
19
|
-
"@voltro/runtime": "0.
|
|
20
|
-
"@voltro/workflow": "0.
|
|
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.
|
|
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.
|
|
16
|
-
"@voltro/database": "0.
|
|
17
|
-
"@voltro/env": "0.
|
|
18
|
-
"@voltro/plugin-flags": "0.
|
|
19
|
-
"@voltro/plugin-multitenancy": "0.
|
|
20
|
-
"@voltro/protocol": "0.
|
|
21
|
-
"@voltro/runtime": "0.
|
|
22
|
-
"@voltro/sql-postgres": "0.
|
|
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.
|
|
26
|
+
"@voltro/testing": "0.3.0",
|
|
27
27
|
"typescript": "^5.7.0",
|
|
28
28
|
"vitest": "^3.0.0"
|
|
29
29
|
}
|