@voltro/cli 0.11.3 → 0.12.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 +436 -0
- package/THIRD-PARTY-NOTICES.md +4 -4
- package/dist/{apiBuild-zFDv0u8y.js → apiBuild-GefjNI5d.js} +10 -9
- package/dist/apiBuild-s7swuEU5.js +2 -0
- package/dist/appModuleLoader-C9r9mxZt.js +19 -0
- package/dist/bin.js +24 -6
- package/dist/cliArgs-qdZSElM3.js +28 -0
- package/dist/{commands-BDGYrBhK.js → commands-BmNi1RgZ.js} +2580 -2493
- package/dist/dbCommand-Bt6BCG7g.js +2 -0
- package/dist/dbCommand-DNrkb58g.js +862 -0
- package/dist/dev-YHbHmeXt.js +3 -0
- package/dist/{dev-CGt0PP1f.js → dev-d0LjACxE.js} +1591 -2332
- package/dist/{devActivity-1WtIVyHc.js → devActivity-BhIu6ncs.js} +7 -1
- package/dist/devActivity.js +1 -1
- package/dist/index.d.ts +22 -0
- package/dist/index.js +3 -3
- package/dist/inspect-DcZ04OME.js +2 -0
- package/dist/inspect-Dwx0_tUj.js +921 -0
- package/dist/inspectMetrics-uEfIS1vp.js +3399 -0
- package/dist/manifestBuild-D1MzJAiQ.js +2 -0
- package/dist/manifestBuild-P9yuCY2d.js +39 -0
- package/dist/seedRunner-DZmhoqNt.js +821 -0
- package/dist/serveCommand-B2-YV9TU.js +1112 -0
- package/dist/serveEntry.js +4 -4
- package/dist/{start-Crl39M38.js → start-DKcQd52a.js} +348 -337
- package/dist/startEntry.js +2 -2
- package/dist/startup.js +1 -1
- package/dist/{startupRunner-DhlX9nqd.js → startupRunner-DPGFchOa.js} +19 -33
- package/package.json +17 -17
- package/templates/AGENTS.md +1 -0
- package/templates/agent-docs/_index.md +1 -0
- package/templates/agent-docs/_manifest.json +1 -1
- package/templates/agent-docs/cli.md +266 -16
- package/templates/agent-docs/data.md +143 -1
- package/templates/agent-docs/database/misc.md +32 -0
- package/templates/agent-docs/database/querying.md +14 -1
- package/templates/agent-docs/internationalization.md +3 -8
- package/templates/agent-docs/reference.md +35 -0
- package/templates/agent-docs/scheduling.md +35 -1
- package/templates/agent-docs/security.md +48 -0
- package/templates/agent-docs/templates/apibackends.md +90 -17
- package/templates/agent-docs/whats-new.md +177 -0
- package/templates/agent-docs/workflows.md +31 -0
- 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/README.md +3 -1
- 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/dist/apiBuild-BmPdmhgi.js +0 -2
- package/dist/dev-D_PGP9Kx.js +0 -2
- package/dist/inspectMetrics-SRtv8KDy.js +0 -4247
- package/dist/serveCommand-p6e5Ahgx.js +0 -1070
package/dist/startEntry.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { t } from "./start-
|
|
1
|
+
import { X as e } from "./inspectMetrics-uEfIS1vp.js";
|
|
2
|
+
import { t } from "./start-DKcQd52a.js";
|
|
3
3
|
export { e as loadDotEnv, t as runStartCommand };
|
package/dist/startup.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { n as e, t } from "./startupRunner-
|
|
1
|
+
import { n as e, t } from "./startupRunner-DPGFchOa.js";
|
|
2
2
|
export { t as loadStartupFiles, e as runStartups };
|
|
@@ -1,44 +1,30 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { n as e } from "./appModuleLoader-C9r9mxZt.js";
|
|
2
|
+
import { basename as t } from "node:path";
|
|
3
3
|
import { publishServerError as n } from "@voltro/protocol";
|
|
4
|
-
//#region src/
|
|
5
|
-
var r =
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
if (n) return n;
|
|
10
|
-
let a = r.get(e);
|
|
11
|
-
if (a !== void 0) {
|
|
12
|
-
if (typeof a == "function") {
|
|
13
|
-
let t = await a();
|
|
14
|
-
return i.set(e, t), t;
|
|
15
|
-
}
|
|
16
|
-
return a;
|
|
17
|
-
}
|
|
18
|
-
return await import(t(e).href);
|
|
19
|
-
}, c = (e) => e.replace(/\.startup\.tsx?$/, ""), l = async (t, n) => {
|
|
20
|
-
let r = [];
|
|
21
|
-
for (let i of t) {
|
|
22
|
-
let t;
|
|
4
|
+
//#region src/startupRunner.ts
|
|
5
|
+
var r = (e) => e.replace(/\.startup\.tsx?$/, ""), i = async (n, i) => {
|
|
6
|
+
let a = [];
|
|
7
|
+
for (let o of n) {
|
|
8
|
+
let n;
|
|
23
9
|
try {
|
|
24
|
-
|
|
10
|
+
n = await e(o);
|
|
25
11
|
} catch (e) {
|
|
26
|
-
|
|
12
|
+
i.warn("startup: failed to import", { file: o }, e);
|
|
27
13
|
continue;
|
|
28
14
|
}
|
|
29
|
-
let
|
|
30
|
-
if (typeof
|
|
31
|
-
|
|
15
|
+
let s = n.default;
|
|
16
|
+
if (typeof s != "function") {
|
|
17
|
+
i.warn("startup: no default-export function", { file: o });
|
|
32
18
|
continue;
|
|
33
19
|
}
|
|
34
|
-
|
|
35
|
-
file:
|
|
36
|
-
id:
|
|
37
|
-
fn:
|
|
20
|
+
a.push({
|
|
21
|
+
file: o,
|
|
22
|
+
id: r(t(o)),
|
|
23
|
+
fn: s
|
|
38
24
|
});
|
|
39
25
|
}
|
|
40
|
-
return
|
|
41
|
-
},
|
|
26
|
+
return a;
|
|
27
|
+
}, a = async (e, t, r) => {
|
|
42
28
|
let i = [];
|
|
43
29
|
for (let a of e) {
|
|
44
30
|
let e = r.child(`startup:${a.id}`), o = {
|
|
@@ -82,4 +68,4 @@ var r = /* @__PURE__ */ new Map(), i = /* @__PURE__ */ new Map(), a = (e) => {
|
|
|
82
68
|
} };
|
|
83
69
|
};
|
|
84
70
|
//#endregion
|
|
85
|
-
export { a
|
|
71
|
+
export { a as n, i as t };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@voltro/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.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.
|
|
66
|
-
"@voltro/cache": "0.
|
|
67
|
-
"@voltro/data-transfer": "0.
|
|
68
|
-
"@voltro/database": "0.
|
|
69
|
-
"@voltro/env": "0.
|
|
70
|
-
"@voltro/kv": "0.
|
|
71
|
-
"@voltro/logger": "0.
|
|
72
|
-
"@voltro/plugin-auth": "0.
|
|
73
|
-
"@voltro/plugin-broadcast": "0.
|
|
74
|
-
"@voltro/plugin-mail": "0.
|
|
75
|
-
"@voltro/plugin-storage": "0.
|
|
76
|
-
"@voltro/plugin-webhooks": "0.
|
|
77
|
-
"@voltro/protocol": "0.
|
|
78
|
-
"@voltro/runtime": "0.
|
|
79
|
-
"@voltro/serverless": "0.
|
|
80
|
-
"@voltro/workflow": "0.
|
|
65
|
+
"@voltro/ai": "0.12.0",
|
|
66
|
+
"@voltro/cache": "0.12.0",
|
|
67
|
+
"@voltro/data-transfer": "0.12.0",
|
|
68
|
+
"@voltro/database": "0.12.0",
|
|
69
|
+
"@voltro/env": "0.12.0",
|
|
70
|
+
"@voltro/kv": "0.12.0",
|
|
71
|
+
"@voltro/logger": "0.12.0",
|
|
72
|
+
"@voltro/plugin-auth": "0.12.0",
|
|
73
|
+
"@voltro/plugin-broadcast": "0.12.0",
|
|
74
|
+
"@voltro/plugin-mail": "0.12.0",
|
|
75
|
+
"@voltro/plugin-storage": "0.12.0",
|
|
76
|
+
"@voltro/plugin-webhooks": "0.12.0",
|
|
77
|
+
"@voltro/protocol": "0.12.0",
|
|
78
|
+
"@voltro/runtime": "0.12.0",
|
|
79
|
+
"@voltro/serverless": "0.12.0",
|
|
80
|
+
"@voltro/workflow": "0.12.0",
|
|
81
81
|
"chokidar": "^5.0.0",
|
|
82
82
|
"ioredis": "^5.11.1",
|
|
83
83
|
"tinyglobby": "^0.2.17",
|
package/templates/AGENTS.md
CHANGED
|
@@ -452,6 +452,7 @@ each plugin's own README.
|
|
|
452
452
|
|
|
453
453
|
| Topic | Open | Summary |
|
|
454
454
|
|---|---|---|
|
|
455
|
+
| **What's new in 0.11.4** | `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. |
|
|
455
456
|
| 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. |
|
|
456
457
|
| 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. |
|
|
457
458
|
| 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,6 +9,7 @@ each plugin's own README.
|
|
|
9
9
|
|
|
10
10
|
| Topic | Open | Summary |
|
|
11
11
|
|---|---|---|
|
|
12
|
+
| **What's new in 0.11.4** | `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
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. |
|
|
13
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. |
|
|
14
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. |
|
|
@@ -21,9 +21,9 @@ The dispatcher routes `voltro <command> [args]` to the matching subcommand and p
|
|
|
21
21
|
|---|---|
|
|
22
22
|
| [Scaffolding](/docs/cli/scaffolding) | `create-project`, `add-app`, `list-templates` |
|
|
23
23
|
| Packages | `package` (`create` / `publishable` / `private` / `status`), `create-package` |
|
|
24
|
-
| [Dev](/docs/cli/dev) | `dev`, `codegen`, `agents-md` |
|
|
24
|
+
| [Dev](/docs/cli/dev) | `dev`, `codegen`, `agents-md`, [`env`](/docs/cli/env) (`check` / `sync` / `types` / `turbo`), [`generate`](/docs/cli/scaffolding) (AI app-builder), `dashboard` (serve the DevTools dashboard standalone; `--port`, `VOLTRO_DASHBOARD_APPS`) |
|
|
25
25
|
| [Build & run](/docs/cli/build-and-start) | `build`, `start`, `serve` |
|
|
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`) |
|
|
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`), `dormancy` (single-node scale-to-zero: fronts the app, stops it when idle, wakes on the next request; `--idle-grace-ms` / `--tick-ms`) |
|
|
27
27
|
| [Database](/docs/cli/migrate) | `migrate`, `db` (`plan` / `apply` / `plans` / `drift` / `squash` / `restore-snapshot` / `migrate` / `rollback` / `status` / `seed`) |
|
|
28
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 |
|
|
29
29
|
| [Data transfer](/docs/cli/data) | `data` (`export` / `import` / `unpack` / `inspect` / `backup` / `restore`) — directory + single-file `.vbundle` bundles, streaming assets, masking, at-rest encryption |
|
|
@@ -105,8 +105,7 @@ pnpm dev # turbo adds the new app to its parallel boot
|
|
|
105
105
|
voltro db plan # diff declared schema vs live, color-coded
|
|
106
106
|
# review the plan
|
|
107
107
|
voltro db apply # execute it (dev)
|
|
108
|
-
#
|
|
109
|
-
voltro migrate
|
|
108
|
+
# `voltro migrate` is an alias of `db apply` — same differ, shorter name (CI / ops)
|
|
110
109
|
```
|
|
111
110
|
|
|
112
111
|
### "I want a clean rebuild"
|
|
@@ -130,19 +129,69 @@ voltro workflows list # recent workflow runs
|
|
|
130
129
|
|
|
131
130
|
### "Did my edit break a binding?"
|
|
132
131
|
|
|
133
|
-
`voltro check` runs blast-radius checks
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
132
|
+
`voltro check` runs blast-radius checks over your app's typed graph — dangling
|
|
133
|
+
`source` / `target` tables, scopes no role grants, unguarded mutations, broken
|
|
134
|
+
route bindings, orphan tables — at edit time instead of at runtime. `--json`
|
|
135
|
+
emits LLM-shaped diagnostics so a coding agent can fix-and-repeat; `--diff`
|
|
136
|
+
previews the blast radius of a removal BEFORE you apply it.
|
|
137
|
+
|
|
138
|
+
It prefers a running api (its manifest is ground truth, including live table
|
|
139
|
+
introspection). **With none reachable it assembles the same graph from source**,
|
|
140
|
+
so it works as a pre-commit hook or a CI gate without a second terminal —
|
|
141
|
+
`--offline` forces that path.
|
|
139
142
|
|
|
140
143
|
```bash
|
|
141
|
-
voltro check #
|
|
144
|
+
voltro check # running api if there is one, else from source
|
|
145
|
+
voltro check --offline # never contact a server — the CI form
|
|
146
|
+
voltro check --url https://api.example.com # a DEPLOYED app
|
|
142
147
|
voltro check --json # { ok, diagnostics: [{ rule, node, breaks, fix }] }
|
|
143
148
|
voltro check --diff removeTable:todos # what a proposed removal would break, before applying
|
|
144
149
|
```
|
|
145
150
|
|
|
151
|
+
`rbac/unknown-scope` needs a declared scope vocabulary to compare against —
|
|
152
|
+
`rbacPlugin({ roles })` publishes one automatically. It catches a guard
|
|
153
|
+
requiring a scope no role grants, which makes that procedure permanently and
|
|
154
|
+
silently uncallable. (With a custom `resolvePermissions` the vocabulary isn't
|
|
155
|
+
exhaustive, so the rule stays quiet rather than flagging correct code.)
|
|
156
|
+
|
|
157
|
+
#### Declared vs OBSERVED — reconciled against reality
|
|
158
|
+
|
|
159
|
+
A query's `source` and a mutation's `targets` are not documentation: the
|
|
160
|
+
framework routes optimistic patches and decides which subscriptions a write
|
|
161
|
+
invalidates from them. A wrong declaration is a live, user-visible bug that
|
|
162
|
+
nothing type-checks — the mutation succeeds, the write lands, and the wrong list
|
|
163
|
+
fails to update.
|
|
164
|
+
|
|
165
|
+
`voltro dev` records what each procedure ACTUALLY touched, into
|
|
166
|
+
`app.graph.observed.json` (gitignored automatically). When that file is present,
|
|
167
|
+
`check` diffs it against the declarations:
|
|
168
|
+
|
|
169
|
+
```
|
|
170
|
+
observed: 12/34 procedures exercised (35%)
|
|
171
|
+
mutation(orders.place) (api/orders/place.mutation.ts)
|
|
172
|
+
writes 'inventory' (update) but declares no target for it
|
|
173
|
+
subscriptions on that table are not invalidated by this mutation
|
|
174
|
+
fix: add { table: 'inventory', op: 'update' } to this procedure's targets
|
|
175
|
+
22 unexercised — no recording, so nothing is claimed about them
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
Three things about that output are deliberate:
|
|
179
|
+
|
|
180
|
+
- **Coverage comes first.** Three findings at 8% coverage and three at 95% are
|
|
181
|
+
different claims. Hiding the denominator is how a check starts overstating
|
|
182
|
+
what it knows.
|
|
183
|
+
- **`unexercised` is a third state, not a finding.** A procedure no test and no
|
|
184
|
+
dev session ever ran has *no observation* — which is not the same as "touches
|
|
185
|
+
nothing". It is counted and never reported as a problem.
|
|
186
|
+
- **Observed diagnostics never fail the build.** An observation is evidence
|
|
187
|
+
about the runs that happened, not a proof about the ones that didn't, and
|
|
188
|
+
`check`'s exit code gates CI. They are always warnings.
|
|
189
|
+
|
|
190
|
+
No file → the section is skipped silently. This is not derived by parsing your
|
|
191
|
+
handlers: a static pass over code that reaches the store through shared helpers,
|
|
192
|
+
behind conditionals, has a long tail of both false positives and false negatives
|
|
193
|
+
— and a check that is *sometimes* wrong is one people stop reading.
|
|
194
|
+
|
|
146
195
|
The HTTP surface is reachable directly too — e.g. `curl -s localhost:4000/_voltro/inspect/rpc | jq` (there is no `/_voltro/inspect/queries` endpoint; it's `rpc` for procedures, `routes` for the web page tree, `subscriptions` for active subscribers).
|
|
147
196
|
|
|
148
197
|
## Where to read next
|
|
@@ -798,6 +847,78 @@ Docker step) and prints the exact remedy: add a `voltro build .` step before
|
|
|
798
847
|
`voltro serve .`. Drop it into your image build right after `voltro build` to
|
|
799
848
|
guarantee the artefact is present before the image ships.
|
|
800
849
|
|
|
850
|
+
### The predicate-column check
|
|
851
|
+
|
|
852
|
+
`eq` / `isNull` / `inSet` are free functions, so the column name arrives as a bare
|
|
853
|
+
`string` and the builder cannot relate it to the table the predicate is attached
|
|
854
|
+
to:
|
|
855
|
+
|
|
856
|
+
```ts
|
|
857
|
+
database.teamAppointments.where(isNull('deletedAt'))
|
|
858
|
+
// ^ teamAppointments has no softDelete() mixin,
|
|
859
|
+
// so no `deletedAt` column exists. tsc: OK.
|
|
860
|
+
```
|
|
861
|
+
|
|
862
|
+
That type-checks — review and CI pass — and then fails at runtime as a bare SQL
|
|
863
|
+
error. `voltro doctor` checks every literal predicate column against the table's
|
|
864
|
+
declared columns and names both:
|
|
865
|
+
|
|
866
|
+
```
|
|
867
|
+
✗ predicate columns: 1 filter on a column that does not exist (214 checked)
|
|
868
|
+
api/appointments/rollforward.query.server.ts:31 'teamAppointments' has no column 'deletedAt'
|
|
869
|
+
columns: id, teamId, startsAt, createdAt, updatedAt
|
|
870
|
+
This type-checks today and fails at runtime as a bare SQL error.
|
|
871
|
+
```
|
|
872
|
+
|
|
873
|
+
Matching is on the AST, never on text, so a column name in a comment or an
|
|
874
|
+
unrelated string cannot trip it. A call site whose table cannot be resolved is
|
|
875
|
+
skipped silently — an unresolvable receiver is usually not a table at all.
|
|
876
|
+
|
|
877
|
+
The type-level fix (binding the predicate to the row, `where(c => isNull(c.x))`)
|
|
878
|
+
is the right end state and is planned separately. This check is the half that
|
|
879
|
+
works **retroactively**: it finds the bug in code that already exists, which a
|
|
880
|
+
type change never will.
|
|
881
|
+
|
|
882
|
+
The detector also flags **raw `fetch()` in server files**. The SSRF guard the
|
|
883
|
+
framework ships lives in the `HttpClient` handlers `yield*` — so it protects
|
|
884
|
+
exactly the apps that already adopted it, and misses the ones that never did.
|
|
885
|
+
Those are usually the same apps that secured least elsewhere, which is why the
|
|
886
|
+
absence is worth naming out loud rather than assuming the default did its job.
|
|
887
|
+
|
|
888
|
+
Server files only (`*.server.ts`, `*.cron.ts`, `*.subscribe.ts`, …): `fetch` is
|
|
889
|
+
unremarkable in a browser component, and flagging it there would make the rule
|
|
890
|
+
noise that gets scrolled past — taking the real findings with it.
|
|
891
|
+
|
|
892
|
+
### The `workflows.start` audit
|
|
893
|
+
|
|
894
|
+
`voltro doctor` also diffs every `workflows.start(name, payload)` call site
|
|
895
|
+
against the workflows the app actually registers:
|
|
896
|
+
|
|
897
|
+
```
|
|
898
|
+
✗ workflow starts: 13/14 call sites checked
|
|
899
|
+
api/crons/weekly.cron.ts:22 'sprint.report' payload is missing: teamId
|
|
900
|
+
api/crons/weekly.cron.ts:22 'sprint.report' payload has unknown field(s): scheduledAt
|
|
901
|
+
accepted: sinceIso, teamId
|
|
902
|
+
1 UNCHECKED (not verified — not a pass):
|
|
903
|
+
api/crons/digest.cron.ts:8 — payload spreads a value
|
|
904
|
+
```
|
|
905
|
+
|
|
906
|
+
**Why this exists even though `workflows.start` validates at runtime.** Runtime
|
|
907
|
+
validation fires on the next firing — which for a daily cron is hours, for a
|
|
908
|
+
weekly one is a week, and for a quarterly one is a quarter. And
|
|
909
|
+
`voltro inspect schedules --failing` cannot see a job that has *never fired*,
|
|
910
|
+
because its roll-up is built from recorded runs. A weekly workflow broken by a
|
|
911
|
+
refactor is invisible to both until it next runs.
|
|
912
|
+
|
|
913
|
+
**`UNCHECKED` is never folded into a pass.** A payload built with a spread or a
|
|
914
|
+
computed key can contribute any name, so its key set is unknowable here. Those
|
|
915
|
+
call sites are counted and listed rather than passed silently — `0 issues` must
|
|
916
|
+
not be readable as "all verified". The workflow **name** is checked regardless,
|
|
917
|
+
since a rename or a deletion is decidable whatever the payload looks like.
|
|
918
|
+
|
|
919
|
+
The required keys come from the live `payloadSchema`, the same source the runtime
|
|
920
|
+
validation reads, so the two cannot disagree about what a payload needs.
|
|
921
|
+
|
|
801
922
|
### The hand-roll detector
|
|
802
923
|
|
|
803
924
|
`voltro doctor` also scans your source for shapes the framework already has a
|
|
@@ -1007,16 +1128,18 @@ package's README.
|
|
|
1007
1128
|
<!-- source: en/cli/migrate.md -->
|
|
1008
1129
|
## Migrate
|
|
1009
1130
|
|
|
1010
|
-
_voltro migrate — the
|
|
1131
|
+
_voltro migrate — apply the declared schema through the declarative differ (an alias of voltro db apply)._
|
|
1132
|
+
|
|
1133
|
+
`voltro migrate` applies your declared schema (`*.entity.ts` / `*.schema.ts` / `schema.ts`) to the configured database. It is an **alias of [`voltro db apply`](#the-declarative-workflow)**: it diffs the declared schema against the live database and emits the ALTERs, so a changed column or a new index actually lands.
|
|
1011
1134
|
|
|
1012
|
-
|
|
1135
|
+
> Before 0.11.4 this command was a create-only apply (`CREATE TABLE IF NOT EXISTS`, no diffing), which meant a column or type change reported success having applied **nothing**. If you need that bootstrap-only behaviour for a brand-new database, it is now `voltro migrate --create-only`.
|
|
1013
1136
|
|
|
1014
1137
|
```bash
|
|
1015
1138
|
voltro migrate # apply the discovered schema to the configured store
|
|
1016
1139
|
voltro migrate apps/api # explicit app directory (defaults to cwd)
|
|
1017
1140
|
```
|
|
1018
1141
|
|
|
1019
|
-
|
|
1142
|
+
`voltro migrate` forwards its arguments to `voltro db apply`, so the same flags apply; `--create-only` selects the bootstrap-only emitter instead. The diff / plan / apply / drift / squash workflow lives under `voltro db` (see below).
|
|
1020
1143
|
|
|
1021
1144
|
For the deep dive on the schema DSL + day-to-day patterns, see [Database / Migrations](/docs/database/migrations).
|
|
1022
1145
|
|
|
@@ -1125,12 +1248,61 @@ voltro inspect rpc # procedures + workflow descript
|
|
|
1125
1248
|
voltro inspect metrics # rolling rpc latency buckets
|
|
1126
1249
|
voltro inspect cache # web data-cache stats (web apps only)
|
|
1127
1250
|
voltro inspect schedules # cron registrations + coordination mode
|
|
1251
|
+
voltro inspect schedules --failing # only broken crons — exits 1 if any (see below)
|
|
1128
1252
|
voltro inspect aggregates # materialised aggregate views
|
|
1129
1253
|
voltro inspect invoke --tag users.list --input '{}' # call a procedure over HTTP
|
|
1130
1254
|
```
|
|
1131
1255
|
|
|
1132
1256
|
Flags on every subcommand: `--process <name>` narrows to one api; `--format pretty|json` (default `pretty`). `invoke` additionally takes `--tag <procedureTag>` and `--input <json>`. With no subcommand it prints the endpoint map + the live processes it can reach.
|
|
1133
1257
|
|
|
1258
|
+
### `schedules --failing` — is any cron actually broken?
|
|
1259
|
+
|
|
1260
|
+
A schedule fires unattended: there is no user watching it fail. The plain
|
|
1261
|
+
`schedules` listing answers *which crons exist and when they fire next* — never
|
|
1262
|
+
whether they **work**. `--failing` rolls each schedule's recent runs
|
|
1263
|
+
(`/_voltro/inspect/schedules/runs`) into a verdict and prints only the broken
|
|
1264
|
+
ones:
|
|
1265
|
+
|
|
1266
|
+
```bash
|
|
1267
|
+
$ voltro inspect schedules --failing
|
|
1268
|
+
# schedules @api
|
|
1269
|
+
sprint.report 0 2 * * * FAILING x87
|
|
1270
|
+
every recorded run failed (last 20)
|
|
1271
|
+
Workflow "sprint.report" was started with an invalid payload. missing required field(s): teamId
|
|
1272
|
+
```
|
|
1273
|
+
|
|
1274
|
+
It **exits 1 when anything is failing**, so it works as a post-deploy gate and
|
|
1275
|
+
not only as something someone remembers to run:
|
|
1276
|
+
|
|
1277
|
+
```bash
|
|
1278
|
+
voltro inspect schedules --failing || echo "broken cron — do not promote"
|
|
1279
|
+
```
|
|
1280
|
+
|
|
1281
|
+
A trailing success ends a streak (a recovered job is not reported), and
|
|
1282
|
+
`skipped` / `missed` runs are ignored — those are coordination outcomes (another
|
|
1283
|
+
pod took the tick, the process was down), not handler verdicts. A schedule that
|
|
1284
|
+
has never run is not "failing".
|
|
1285
|
+
|
|
1286
|
+
Pair it with `voltro logs --level error`: a failing handler now logs at **error**
|
|
1287
|
+
level, so the two surfaces agree.
|
|
1288
|
+
|
|
1289
|
+
## Targeting a deployed app
|
|
1290
|
+
|
|
1291
|
+
Every command in this family resolves its target from the local runtime registry — the apps running on *this* machine. Pass `--url` to point one at a **deployed** app instead:
|
|
1292
|
+
|
|
1293
|
+
```bash
|
|
1294
|
+
voltro inspect app --url https://api.example.com --token "$TOKEN"
|
|
1295
|
+
voltro logs --tail 100 --url https://api.example.com
|
|
1296
|
+
voltro traces --errors --url https://api.example.com
|
|
1297
|
+
voltro check --url https://api.example.com
|
|
1298
|
+
```
|
|
1299
|
+
|
|
1300
|
+
`--token` (or `VOLTRO_INSPECT_TOKEN`) supplies the bearer; `VOLTRO_INSPECT_URL` sets a default target so you can drop the flag. Works for `inspect`, `logs`, `traces`, `workflows`, `cluster` and `check`.
|
|
1301
|
+
|
|
1302
|
+
## Securing the local surface
|
|
1303
|
+
|
|
1304
|
+
`voltro dev` mints a per-project `VOLTRO_INSPECT_TOKEN` into `.env.local`, so the inspect surface is authenticated from the first boot — the dev server listens on every interface, and without a token anyone on the same network could read your rows, schema and logs. You don't have to wire it anywhere: the CLI picks the token up from the runtime registry (so the commands work from any directory), and the dashboard's server-side proxy supplies it for same-machine targets. Setting `VOLTRO_INSPECT_TOKEN` yourself always wins.
|
|
1305
|
+
|
|
1134
1306
|
## The inspect HTTP surface
|
|
1135
1307
|
|
|
1136
1308
|
Every `voltro dev` / `voltro start` instance exposes a read-only introspection surface under `/_voltro/inspect/*`. The [Voltro Dashboard](/docs/observability/dashboard) consumes it to render the route sitemap, RPC list, subscription panel, workflow runs, and metrics. You can also hit the endpoints directly with `curl` (the `voltro inspect` subcommands above are the thin wrapper over exactly these).
|
|
@@ -1235,6 +1407,18 @@ voltro traces --errors --format json | jq '.traces[]'
|
|
|
1235
1407
|
|
|
1236
1408
|
Workflow: `voltro traces --errors --format json` to find a failure, then `voltro logs --trace <id> --format json` for the full chain (frontend → api → api, in order). The full flag set (`--min-duration`, `--status`, `--process`, …) lives in [Traces & logs from the shell](/docs/observability/cli).
|
|
1237
1409
|
|
|
1410
|
+
## `voltro cluster`
|
|
1411
|
+
|
|
1412
|
+
`voltro cluster status` gives a clustering snapshot of every running api — one row per instance with its `replicaId`, runner address, dialect, CDC flavour, coordination mode, and `server_id`, plus a flag for any SQL runner stuck on `localhost` (a common misconfiguration that silently breaks cross-instance work). Use it to confirm a multi-instance deployment actually formed a cluster rather than N isolated nodes.
|
|
1413
|
+
|
|
1414
|
+
```bash
|
|
1415
|
+
voltro cluster status # pretty table across every running api
|
|
1416
|
+
voltro cluster status --json # machine-readable (also --format json)
|
|
1417
|
+
voltro cluster status --process api # narrow to one named process
|
|
1418
|
+
```
|
|
1419
|
+
|
|
1420
|
+
Like the rest of the inspect family it reads the live `/_voltro/inspect/*` surface, so an api has to be running.
|
|
1421
|
+
|
|
1238
1422
|
## `voltro test`
|
|
1239
1423
|
|
|
1240
1424
|
```bash
|
|
@@ -1249,6 +1433,30 @@ Runs Vitest with the framework's preset:
|
|
|
1249
1433
|
- `STORE=memory` by default for fast isolation.
|
|
1250
1434
|
- Plays well with `@voltro/testing` helpers (mock stores).
|
|
1251
1435
|
|
|
1436
|
+
### Every vitest flag is forwarded
|
|
1437
|
+
|
|
1438
|
+
Anything the command does not interpret itself goes straight to vitest — parsed
|
|
1439
|
+
by **vitest's own CLI parser**, not a list this wrapper maintains:
|
|
1440
|
+
|
|
1441
|
+
```bash
|
|
1442
|
+
voltro test --coverage
|
|
1443
|
+
voltro test --reporter=junit --outputFile=reports/junit.xml
|
|
1444
|
+
voltro test --coverage --reporter=junit --outputFile=reports/junit.xml
|
|
1445
|
+
```
|
|
1446
|
+
|
|
1447
|
+
That covers coverage numbers and a JUnit report for a merge-request widget, which
|
|
1448
|
+
is what most pipelines want beyond the exit code.
|
|
1449
|
+
|
|
1450
|
+
The framework keeps three decisions for itself and they win over a forwarded
|
|
1451
|
+
flag: the **root** (a positional that is an existing directory, which vitest
|
|
1452
|
+
would otherwise read as a filter), `--watch`, and `passWithNoTests` — an explicit
|
|
1453
|
+
filter that matches no file is an *error*, which vitest cannot decide because it
|
|
1454
|
+
does not know which positional was treated as a root.
|
|
1455
|
+
|
|
1456
|
+
An unrecognised flag is ignored rather than fatal, and a flag vitest cannot parse
|
|
1457
|
+
at all degrades to "run without the extra flags" with a warning instead of taking
|
|
1458
|
+
the run down.
|
|
1459
|
+
|
|
1252
1460
|
The actual test runner is Vitest; this command is a thin wrapper that injects the framework's config. You can run vitest directly if you prefer:
|
|
1253
1461
|
|
|
1254
1462
|
```bash
|
|
@@ -1288,7 +1496,9 @@ The framework's `voltro e2e` handles the lifecycle for you: it boots the api + w
|
|
|
1288
1496
|
|
|
1289
1497
|
## Securing the inspect surface
|
|
1290
1498
|
|
|
1291
|
-
The
|
|
1499
|
+
The surface is **fail-closed**: with no `VOLTRO_INSPECT_TOKEN` configured, every endpoint answers `401`. The absence of a secret is not consent — a check you were configured not to perform is a refusal, not a pass.
|
|
1500
|
+
|
|
1501
|
+
That is why `voltro dev` mints one for you (above) and why nothing mints outside dev: in production a missing secret must stay a boot-time decision rather than an invented value. So a production `voltro start` serves nothing here until you set the token yourself:
|
|
1292
1502
|
|
|
1293
1503
|
```bash
|
|
1294
1504
|
VOLTRO_INSPECT_TOKEN=$(openssl rand -hex 32)
|
|
@@ -1306,7 +1516,7 @@ Or disable the surface entirely: `VOLTRO_INSPECT=off`. The dashboard then can't
|
|
|
1306
1516
|
|
|
1307
1517
|
## Anti-patterns
|
|
1308
1518
|
|
|
1309
|
-
- **
|
|
1519
|
+
- **Assuming the surface is open because you didn't configure it.** It is the reverse: no token means `401`, not "everyone". If a production dashboard suddenly stops introspecting, the missing `VOLTRO_INSPECT_TOKEN` is the first thing to check — not a network problem.
|
|
1310
1520
|
- **`voltro test` against `STORE=postgres` by default.** Slower + flaky (test isolation harder). Use postgres only for integration tests that NEED it.
|
|
1311
1521
|
- **Skipping `voltro e2e` because "it's slow".** It catches integration bugs that unit tests miss. Run it in CI on every PR; locally for changes that touch queries.
|
|
1312
1522
|
|
|
@@ -2052,3 +2262,43 @@ Use `voltro db apply` (the declarative diff), not `voltro db migrate` (the imper
|
|
|
2052
2262
|
|
|
2053
2263
|
- [Migrate](/docs/cli/migrate) — schema changes end-to-end
|
|
2054
2264
|
- [Build & start](/docs/cli/build-and-start) — production paths
|
|
2265
|
+
|
|
2266
|
+
|
|
2267
|
+
|
|
2268
|
+
---
|
|
2269
|
+
|
|
2270
|
+
<!-- source: en/cli/env.md -->
|
|
2271
|
+
## Env
|
|
2272
|
+
|
|
2273
|
+
_voltro env — inspect and sync the typed-env manifest; emit env.generated.d.ts so CI tsc knows your env vars._
|
|
2274
|
+
|
|
2275
|
+
`voltro env` works with the **typed-env manifest** — the set of environment variables your app declares (via `envVar(...)`) and the framework requires. It's how a missing or malformed env var becomes a boot-time error with a clear message instead of a mysterious runtime failure.
|
|
2276
|
+
|
|
2277
|
+
```bash
|
|
2278
|
+
voltro env # check (default) — validate the current env against the manifest
|
|
2279
|
+
voltro env check # explicit
|
|
2280
|
+
voltro env sync # write/update .env.example from the manifest
|
|
2281
|
+
voltro env types # emit env.generated.d.ts (typed process.env for CI tsc)
|
|
2282
|
+
voltro env turbo # emit the turbo globalEnv/globalPassThroughEnv list
|
|
2283
|
+
```
|
|
2284
|
+
|
|
2285
|
+
## `check` (default)
|
|
2286
|
+
|
|
2287
|
+
Validates the current environment against the declared manifest and fails (non-zero) on a missing required var or a value that doesn't parse. Run it in a pre-deploy step to catch a misconfigured environment before the app boots. Run with no subcommand and it defaults to `check`.
|
|
2288
|
+
|
|
2289
|
+
## `sync` — keep `.env.example` honest
|
|
2290
|
+
|
|
2291
|
+
Writes `.env.example` from the manifest so the committed template always matches what the app actually reads — new `envVar(...)` declarations show up without hand-editing.
|
|
2292
|
+
|
|
2293
|
+
## `types` — typed `process.env` for CI
|
|
2294
|
+
|
|
2295
|
+
Emits `env.generated.d.ts`, which types `process.env` to your declared vars. Commit it (or generate it in CI before `tsc`) so a typo in a `process.env.MY_VAR` reference is a type error, not a runtime `undefined`.
|
|
2296
|
+
|
|
2297
|
+
## `turbo` — monorepo cache correctness
|
|
2298
|
+
|
|
2299
|
+
Emits the `globalEnv` / `globalPassThroughEnv` entries for `turbo.json`, so Turbo's cache invalidates when a relevant env var changes and passes the right vars through to tasks.
|
|
2300
|
+
|
|
2301
|
+
## Related
|
|
2302
|
+
|
|
2303
|
+
- [Secrets](/docs/cli/overview#common-env-vars) — generating secret values with `voltro secret`.
|
|
2304
|
+
- [Configuration](/docs/configuration) — declaring env vars with `envVar(...)`.
|