@voltro/cli 0.53.0 → 0.55.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 +335 -0
- package/dist/{agentsMd-0l980yhL.js → agentsMd-BaLC10Na.js} +110 -82
- package/dist/agentsMd-DCY1RSs8.js +2 -0
- package/dist/{apiBuild-CaPfoWku.js → apiBuild-CMvLJM_K.js} +2 -2
- package/dist/apiBuild-Cl0IDx8c.js +2 -0
- package/dist/bin.js +1 -1
- package/dist/{build-D-OnvNMf.js → build-S0QOzqPT.js} +115 -115
- package/dist/{checkCommand-D2ZduVlh.js → checkCommand-DNkY5kwF.js} +1 -1
- package/dist/{checkCommand-C5elt0tW.js → checkCommand-fbj9GDjN.js} +6 -6
- package/dist/{cloudCmd-QUXh-b5w.js → cloudCmd-DzKcSYuy.js} +1 -1
- package/dist/codegen-CN6vMM4J.js +2 -0
- package/dist/{codegen-FEk8AZHb.js → codegen-SIepQtUl.js} +76 -65
- package/dist/codegenCommand-3TDJezom.js +42 -0
- package/dist/{codemodRunner-BjtB2lq6.js → codemodRunner-C2zxZUIw.js} +64 -9
- package/dist/{commands-DyxAmhP0.js → commands-BBYJ7Q3B.js} +96 -73
- package/dist/{dashboardCommand-BdKTyT13.js → dashboardCommand-D2kmyCLL.js} +3 -3
- package/dist/{dataCommand-Bab9X7s8.js → dataCommand-BEPPQiTl.js} +267 -195
- package/dist/dbCommand-BTyBGhIA.js +2 -0
- package/dist/{dbCommand-06O2finM.js → dbCommand-DZTmOFT4.js} +3 -3
- package/dist/{dev-C6LGF4iY.js → dev-Ca_A_S9v.js} +2439 -2397
- package/dist/{dev-GjJWAYo2.js → dev-DfVZaoys.js} +1 -1
- package/dist/{doctorCommand-etMkflRc.js → doctorCommand-CGZJK_4o.js} +21 -21
- package/dist/doctorCommand-djmqEcDC.js +2 -0
- package/dist/{dormancyCommand-UwZ1AZzB.js → dormancyCommand-DY2rYpTa.js} +1 -1
- package/dist/{embeddingsCommand-C70zWHwo.js → embeddingsCommand-BoCqZsgp.js} +1 -1
- package/dist/{envCommand-dSyKvRkM.js → envCommand-Bxy2fOjc.js} +15 -15
- package/dist/{evolveCommand-CG0_ebO5.js → evolveCommand-BsbZ-XDg.js} +2 -2
- package/dist/fileConventions-l-RIXbx8.js +36 -0
- package/dist/{fileTaxonomy-B7uxipWS.js → fileTaxonomy-CbyMQYx_.js} +37 -37
- package/dist/frameworkTableAssembly-Df2Ymp2f.js +2 -0
- package/dist/{frameworkTableAssembly-DKx3ba3S.js → frameworkTableAssembly-Do-cf6RJ.js} +96 -102
- package/dist/index.js +2 -2
- package/dist/{infoCommand-_53iOc_j.js → infoCommand-EmM3jPKD.js} +1 -1
- package/dist/{inspect-Bd8-9wsi.js → inspect-DCqILJ1G.js} +4 -0
- package/dist/inspect-DGJwpOAb.js +2 -0
- package/dist/interruptedReplace-CwnkBb2X.js +41 -0
- package/dist/interruptedReplace-qzmFI020.js +2 -0
- package/dist/manifestBuild-CJ2zvPvT.js +2 -0
- package/dist/{manifestBuild-Cqgsx2bM.js → manifestBuild-DjX5MoXy.js} +1 -1
- package/dist/{metaCommands-Cn2oboG4.js → metaCommands-x7RCi2AF.js} +2 -2
- package/dist/{migrate-Cko9rswM.js → migrate-CGFZS-1a.js} +2 -2
- package/dist/mobileCommand-D9O6iq3D.js +428 -0
- package/dist/mobileCommand-DAum7tsG.js +2 -0
- package/dist/{pageConvention-C938S8oC.js → pageConvention-CMpfDN6r.js} +1 -1
- package/dist/{privacyCommand-DWTQMC6R.js → privacyCommand-BCa2OoZG.js} +2 -2
- package/dist/{probeCommand-DkGGLknv.js → probeCommand-Bs3iVBSL.js} +1 -1
- package/dist/{projectScaffold-EzlErR4E.js → projectScaffold-CJfP-xbT.js} +1 -1
- package/dist/{projectScaffold-B4dmTlwT.js → projectScaffold-CSN0OzBV.js} +2 -2
- package/dist/renderModeScan-43yQ2opo.js +147 -0
- package/dist/{renderProfile-CskIgAfn.js → renderProfile-DvrhVJHa.js} +2 -2
- package/dist/{runtimeTrace-c0APJz7E.js → runtimeTrace-C1BTpHGQ.js} +1 -1
- package/dist/{sdkgen-BiQCgIEr.js → sdkgen-CXMwLg9n.js} +1 -1
- package/dist/{serveCommand-CueKQgzl.js → serveCommand-C7IrCD58.js} +899 -897
- package/dist/serveCommand-Cjt5S9hD.js +2 -0
- package/dist/serveEntry.js +1 -1
- package/dist/start-DH7cat4-.js +3 -0
- package/dist/{start-ekPan8BT.js → start-EOV7s1NZ.js} +544 -527
- package/dist/startEntry.js +1 -1
- package/dist/{staticCommand-xlSL-IWk.js → staticCommand-ey0kYmOT.js} +1 -1
- package/dist/{subcommandNames-DpYs3DXr.js → subcommandNames-CDzfEtKV.js} +3 -3
- package/dist/{templates-BR-fb4SP.js → templates-BTWZkJJT.js} +41 -9
- package/dist/{test-BWPQcRoB.js → test-DO27-x2P.js} +1 -1
- package/dist/{updateCommand-C_8I8Rzo.js → updateCommand-C_jN1w18.js} +1 -1
- package/dist/updateCommand-nnFjDbl4.js +2 -0
- package/dist/{webDev-C7jWJ5dX.js → webDev-1XpVnYkW.js} +1 -1
- package/dist/{webDev-oczpugbx.js → webDev-B7vNj4Bq.js} +1231 -1186
- package/dist/{webhooksCommand-4SVPDjKg.js → webhooksCommand-B1LVcyO3.js} +1 -1
- package/dist/workspaceDeps-RKEkX92S.js +45 -0
- package/package.json +31 -19
- package/templates/AGENTS.core.md +2 -0
- package/templates/AGENTS.md +4 -2
- package/templates/agent-docs/_index.md +2 -2
- package/templates/agent-docs/_manifest.json +1 -1
- package/templates/agent-docs/ai.md +4 -4
- package/templates/agent-docs/authentication.md +115 -0
- package/templates/agent-docs/cli.md +98 -12
- package/templates/agent-docs/data.md +121 -21
- package/templates/agent-docs/database/advancedqueries.md +1 -1
- package/templates/agent-docs/database/migrations.md +1 -1
- package/templates/agent-docs/database/seedsdialects.md +64 -2
- package/templates/agent-docs/internationalization.md +2 -0
- package/templates/agent-docs/introduction.md +25 -0
- package/templates/agent-docs/local-first-mobile.md +139 -41
- package/templates/agent-docs/observability.md +4 -2
- package/templates/agent-docs/plugins/atlassian.md +2 -2
- package/templates/agent-docs/plugins/audit.md +2 -2
- package/templates/agent-docs/plugins/billing.md +1 -1
- package/templates/agent-docs/plugins/cdc-out.md +8 -3
- package/templates/agent-docs/plugins/comments.md +22 -0
- package/templates/agent-docs/plugins/presence.md +32 -3
- package/templates/agent-docs/plugins/prometheus.md +2 -0
- package/templates/agent-docs/plugins/queue.md +47 -4
- package/templates/agent-docs/plugins.md +52 -14
- package/templates/agent-docs/reference.md +25 -4
- package/templates/agent-docs/routing.md +81 -9
- package/templates/agent-docs/scheduling.md +1 -1
- package/templates/agent-docs/schema-driven-ui.md +137 -1
- package/templates/agent-docs/templates/appshells.md +36 -4
- package/templates/agent-docs/whats-new.md +75 -158
- package/templates/apps/api-ai/package.json +6 -6
- 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-sqlite/package.json +8 -8
- package/templates/apps/api-backend-storage/package.json +8 -8
- package/templates/apps/api-cms/package.json +9 -9
- package/templates/apps/api-collab/README.md +3 -3
- package/templates/apps/api-collab/app.config.ts +1 -1
- package/templates/apps/api-collab/database/schema.ts +12 -8
- package/templates/apps/api-collab/mutations/documents.create.mutation.server.ts +1 -1
- package/templates/apps/api-collab/mutations/documents.setBody.mutation.server.ts +1 -1
- package/templates/apps/api-collab/mutations/documents.setBody.mutation.ts +1 -1
- package/templates/apps/api-collab/package.json +8 -8
- package/templates/apps/api-collab/template.json +1 -1
- package/templates/apps/api-collab/tests/documents.setBody.test.ts +10 -2
- 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-row-history/package.json +8 -8
- package/templates/apps/api-saas/package.json +11 -10
- package/templates/apps/api-saas-starter/package.json +10 -10
- package/templates/apps/api-search/package.json +8 -8
- package/templates/apps/api-status/package.json +8 -8
- package/templates/apps/api-webhooks/package.json +9 -9
- package/templates/apps/changelog/package.json +7 -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 +9 -9
- package/templates/apps/frontend-auth/package.json +8 -8
- package/templates/apps/frontend-blank/package.json +7 -7
- package/templates/apps/frontend-cms/package.json +9 -9
- package/templates/apps/frontend-collab/README.md +43 -24
- package/templates/apps/frontend-collab/app.config.ts +3 -3
- package/templates/apps/frontend-collab/package.json +14 -10
- package/templates/apps/frontend-collab/src/locales/de.ts +1 -2
- package/templates/apps/frontend-collab/src/locales/en.ts +1 -2
- package/templates/apps/frontend-collab/src/pages/page.test.tsx +72 -76
- package/templates/apps/frontend-collab/src/pages/page.tsx +45 -22
- package/templates/apps/frontend-collab/template.json +2 -2
- 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 +8 -7
- package/templates/apps/frontend-i18n/package.json +6 -6
- package/templates/apps/frontend-landing/README.md +48 -0
- package/templates/apps/frontend-landing/app.config.ts +28 -0
- package/templates/apps/frontend-landing/package.json +7 -6
- package/templates/apps/frontend-landing/src/assets/hero.jpg +0 -0
- package/templates/apps/frontend-landing/src/fonts/Geist-Variable.woff2 +0 -0
- package/templates/apps/frontend-landing/src/fonts/LICENSE-Geist.txt +92 -0
- package/templates/apps/frontend-landing/src/globals.css +15 -0
- package/templates/apps/frontend-landing/src/globals.d.ts +17 -0
- package/templates/apps/frontend-landing/src/locales/de.ts +3 -2
- package/templates/apps/frontend-landing/src/locales/en.ts +5 -2
- package/templates/apps/frontend-landing/src/pages/page.test.tsx +79 -0
- package/templates/apps/frontend-landing/src/pages/page.tsx +26 -3
- package/templates/apps/frontend-landing/template.json +2 -2
- package/templates/apps/frontend-portal/package.json +8 -8
- package/templates/apps/frontend-saas/package.json +8 -8
- 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 +8 -7
- package/templates/apps/frontend-status/package.json +8 -8
- package/templates/apps/mobile-app/package.json +12 -11
- package/templates/apps/mobile-app/src/lib/deeplinks.ts +29 -17
- package/templates/apps/mobile-app/tests/deeplinks.test.ts +16 -0
- package/dist/agentsMd-SDDSkyl4.js +0 -2
- package/dist/apiBuild-DHtLXYx9.js +0 -2
- package/dist/codegen-BWpt3VgF.js +0 -2
- package/dist/codegenCommand-BOiWQ5hz.js +0 -137
- package/dist/dbCommand-B1EXBC6f.js +0 -2
- package/dist/doctorCommand-B0hX0tdz.js +0 -2
- package/dist/fileConventions-DASGEmj-.js +0 -35
- package/dist/frameworkTableAssembly-C_7Z-rMs.js +0 -2
- package/dist/inspect-CuoDInfZ.js +0 -2
- package/dist/interruptedReplace-C3O3M1MM.js +0 -28
- package/dist/interruptedReplace-CvmiAM9K.js +0 -2
- package/dist/manifestBuild-C4-J1-m_.js +0 -2
- package/dist/renderModeScan-CUbOeOAg.js +0 -122
- package/dist/serveCommand-DsnrVN3U.js +0 -2
- package/dist/start-BJzZLbt8.js +0 -3
- package/dist/updateCommand-Bqql_rsQ.js +0 -2
|
@@ -222,7 +222,7 @@ createVerifier({ secret: [process.env.WEBHOOK_SECRET, process.env.WEBHOOK_SECRET
|
|
|
222
222
|
...t === void 0 ? {} : { payload: t }
|
|
223
223
|
};
|
|
224
224
|
}, S = u({ scope: "voltro:webhooks" }), C = ["--out", "--name"], w = async (e) => {
|
|
225
|
-
let { walk: t, loadDiscovered: n } = await import("./dev-
|
|
225
|
+
let { walk: t, loadDiscovered: n } = await import("./dev-DfVZaoys.js"), { outgoingFromEvents: r } = await import("./webhookDiscovery-il9ti-HE.js");
|
|
226
226
|
return r((await n(await t(e))).events.map((e) => ({
|
|
227
227
|
file: e.file,
|
|
228
228
|
descriptor: e.descriptor
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { dirname as e, join as t } from "node:path";
|
|
2
|
+
import { promises as n } from "node:fs";
|
|
3
|
+
//#region src/workspaceDeps.ts
|
|
4
|
+
var r = async (r, i) => {
|
|
5
|
+
let a = r;
|
|
6
|
+
for (;;) {
|
|
7
|
+
let r = t(a, "node_modules", i);
|
|
8
|
+
try {
|
|
9
|
+
if ((await n.stat(r)).isDirectory()) {
|
|
10
|
+
let e = await n.realpath(r);
|
|
11
|
+
return e.includes("/node_modules/") ? null : e;
|
|
12
|
+
}
|
|
13
|
+
} catch {}
|
|
14
|
+
let o = e(a);
|
|
15
|
+
if (o === a) return null;
|
|
16
|
+
a = o;
|
|
17
|
+
}
|
|
18
|
+
}, i = async (e) => {
|
|
19
|
+
let i;
|
|
20
|
+
try {
|
|
21
|
+
let r = await n.readFile(t(e, "package.json"), "utf8"), a = JSON.parse(r);
|
|
22
|
+
i = Object.keys({
|
|
23
|
+
...a.dependencies,
|
|
24
|
+
...a.devDependencies
|
|
25
|
+
});
|
|
26
|
+
} catch {
|
|
27
|
+
return [];
|
|
28
|
+
}
|
|
29
|
+
return (await Promise.all(i.map(async (i) => {
|
|
30
|
+
let a = await r(e, i);
|
|
31
|
+
if (a === null) return null;
|
|
32
|
+
let o = t(a, "src");
|
|
33
|
+
try {
|
|
34
|
+
if (!(await n.stat(o)).isDirectory()) return null;
|
|
35
|
+
} catch {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
return {
|
|
39
|
+
pkg: i,
|
|
40
|
+
dir: o
|
|
41
|
+
};
|
|
42
|
+
}))).filter((e) => e !== null);
|
|
43
|
+
};
|
|
44
|
+
//#endregion
|
|
45
|
+
export { i as n, r as t };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@voltro/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.55.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",
|
|
@@ -754,6 +754,18 @@
|
|
|
754
754
|
"title": "WidgetKind gained 'array' and 'reference' — total widget registries need two new entries",
|
|
755
755
|
"kind": "manual"
|
|
756
756
|
},
|
|
757
|
+
{
|
|
758
|
+
"version": "0.55.0",
|
|
759
|
+
"id": "0.55.0/01_target-relations-declare-columns",
|
|
760
|
+
"title": "A target's `relations:` names the junction's two columns now, not just the table",
|
|
761
|
+
"kind": "manual"
|
|
762
|
+
},
|
|
763
|
+
{
|
|
764
|
+
"version": "0.55.0",
|
|
765
|
+
"id": "0.55.0/02_widget-kind-gained-rich-text",
|
|
766
|
+
"title": "WidgetKind gained 'rich-text' — total widget registries need one new entry",
|
|
767
|
+
"kind": "manual"
|
|
768
|
+
},
|
|
757
769
|
{
|
|
758
770
|
"version": "0.6.0",
|
|
759
771
|
"id": "0.6.0/01_no-dev-session-secret",
|
|
@@ -836,24 +848,24 @@
|
|
|
836
848
|
"@effect/platform-node": "^0.108.0",
|
|
837
849
|
"@effect/sql": "^0.52.0",
|
|
838
850
|
"@effect/workflow": "^0.19.0",
|
|
839
|
-
"@voltro/ai": "0.
|
|
840
|
-
"@voltro/cache": "0.
|
|
841
|
-
"@voltro/client": "0.
|
|
842
|
-
"@voltro/content": "0.
|
|
843
|
-
"@voltro/data-transfer": "0.
|
|
844
|
-
"@voltro/database": "0.
|
|
845
|
-
"@voltro/env": "0.
|
|
846
|
-
"@voltro/kv": "0.
|
|
847
|
-
"@voltro/logger": "0.
|
|
848
|
-
"@voltro/plugin-auth": "0.
|
|
849
|
-
"@voltro/plugin-broadcast": "0.
|
|
850
|
-
"@voltro/plugin-mail": "0.
|
|
851
|
-
"@voltro/plugin-storage": "0.
|
|
852
|
-
"@voltro/plugin-webhooks": "0.
|
|
853
|
-
"@voltro/protocol": "0.
|
|
854
|
-
"@voltro/runtime": "0.
|
|
855
|
-
"@voltro/serverless": "0.
|
|
856
|
-
"@voltro/workflow": "0.
|
|
851
|
+
"@voltro/ai": "0.55.0",
|
|
852
|
+
"@voltro/cache": "0.55.0",
|
|
853
|
+
"@voltro/client": "0.55.0",
|
|
854
|
+
"@voltro/content": "0.55.0",
|
|
855
|
+
"@voltro/data-transfer": "0.55.0",
|
|
856
|
+
"@voltro/database": "0.55.0",
|
|
857
|
+
"@voltro/env": "0.55.0",
|
|
858
|
+
"@voltro/kv": "0.55.0",
|
|
859
|
+
"@voltro/logger": "0.55.0",
|
|
860
|
+
"@voltro/plugin-auth": "0.55.0",
|
|
861
|
+
"@voltro/plugin-broadcast": "0.55.0",
|
|
862
|
+
"@voltro/plugin-mail": "0.55.0",
|
|
863
|
+
"@voltro/plugin-storage": "0.55.0",
|
|
864
|
+
"@voltro/plugin-webhooks": "0.55.0",
|
|
865
|
+
"@voltro/protocol": "0.55.0",
|
|
866
|
+
"@voltro/runtime": "0.55.0",
|
|
867
|
+
"@voltro/serverless": "0.55.0",
|
|
868
|
+
"@voltro/workflow": "0.55.0",
|
|
857
869
|
"chokidar": "^5.0.0",
|
|
858
870
|
"ioredis": "^5.11.1",
|
|
859
871
|
"tinyglobby": "^0.2.17",
|
package/templates/AGENTS.core.md
CHANGED
|
@@ -368,6 +368,8 @@ public REST routes, declared via `restRoutes` in `app.config.ts`).
|
|
|
368
368
|
| `*.agent.tsx` + `*.agent.server.tsx` | server-side LLM chat |
|
|
369
369
|
| `*.tool.tsx` | tool an agent can call |
|
|
370
370
|
| `*.entity.ts` / `*.schema.ts` / `schema.ts` | one table per file |
|
|
371
|
+
| `*.collection.ts` | content collection (`defineCollection`) — schema-typed markdown/JSON |
|
|
372
|
+
| `content/<name>/**` | that collection's files (markdown + frontmatter, or `.json`) |
|
|
371
373
|
| `page.tsx` (under `src/pages/`) | the route its DIRECTORY serves + `page.test.tsx` |
|
|
372
374
|
| `*.component.tsx` | exactly ONE component (+ types) |
|
|
373
375
|
| `*.component.ui.tsx` | presentational: one component, READS only — never writes |
|
package/templates/AGENTS.md
CHANGED
|
@@ -368,6 +368,8 @@ public REST routes, declared via `restRoutes` in `app.config.ts`).
|
|
|
368
368
|
| `*.agent.tsx` + `*.agent.server.tsx` | server-side LLM chat |
|
|
369
369
|
| `*.tool.tsx` | tool an agent can call |
|
|
370
370
|
| `*.entity.ts` / `*.schema.ts` / `schema.ts` | one table per file |
|
|
371
|
+
| `*.collection.ts` | content collection (`defineCollection`) — schema-typed markdown/JSON |
|
|
372
|
+
| `content/<name>/**` | that collection's files (markdown + frontmatter, or `.json`) |
|
|
371
373
|
| `page.tsx` (under `src/pages/`) | the route its DIRECTORY serves + `page.test.tsx` |
|
|
372
374
|
| `*.component.tsx` | exactly ONE component (+ types) |
|
|
373
375
|
| `*.component.ui.tsx` | presentational: one component, READS only — never writes |
|
|
@@ -728,7 +730,7 @@ each plugin's own README.
|
|
|
728
730
|
|
|
729
731
|
| Topic | Open | Summary |
|
|
730
732
|
|---|---|---|
|
|
731
|
-
| **What's new in 0.
|
|
733
|
+
| **What's new in 0.55.0** | `node_modules/@voltro/cli/templates/agent-docs/whats-new.md` | Everything that changed in this version. Read it before hand-rolling something the framework may now ship. |
|
|
732
734
|
| 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. |
|
|
733
735
|
| 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. |
|
|
734
736
|
| 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. |
|
|
@@ -786,7 +788,7 @@ each plugin's own README.
|
|
|
786
788
|
| auth-workos | `node_modules/@voltro/cli/templates/agent-docs/plugins/auth-workos.md` (or `node_modules/@voltro/plugin-auth-workos/README.md`) | WorkOS AuthStrategy — verifies WorkOS AuthKit / SSO JWTs via JWKS (no API key) and maps org_id → tenantId. |
|
|
787
789
|
| billing | `node_modules/@voltro/cli/templates/agent-docs/plugins/billing.md` (or `node_modules/@voltro/plugin-billing/README.md`) | Subscriptions, plans, entitlements, and usage metering over a pluggable provider (Stripe + mock). Money is integer minor units. |
|
|
788
790
|
| broadcast | `node_modules/@voltro/cli/templates/agent-docs/plugins/broadcast.md` (or `node_modules/@voltro/plugin-broadcast/README.md`) | Cross-replica reactivity over a pub/sub bus (Redis / NATS) for non-postgres dialects — closes the single-instance gap so a write on one pod surfaces on another. |
|
|
789
|
-
| cdc-out | `node_modules/@voltro/cli/templates/agent-docs/plugins/cdc-out.md` (or `node_modules/@voltro/plugin-cdc-out/README.md`) | Declaratively mirror table changes outward to external sinks (webhook, plus a CdcSink interface for custom sinks) through a durable outbox — ordered per pipe, at-least-once from enqueue, retried with backoff, dead-lettered. |
|
|
791
|
+
| cdc-out | `node_modules/@voltro/cli/templates/agent-docs/plugins/cdc-out.md` (or `node_modules/@voltro/plugin-cdc-out/README.md`) | Declaratively mirror table changes outward to external sinks (webhook, Kafka, plus a CdcSink interface for custom sinks) through a durable outbox — ordered per pipe, at-least-once from enqueue, retried with backoff, dead-lettered. |
|
|
790
792
|
| clickhouse | `node_modules/@voltro/cli/templates/agent-docs/plugins/clickhouse.md` (or `node_modules/@voltro/plugin-clickhouse/README.md`) | Production-grade OLAP AnalyticsSink over ClickHouse — self-hosted or ClickHouse Cloud — for billions of events with millisecond aggregates. |
|
|
791
793
|
| comments | `node_modules/@voltro/cli/templates/agent-docs/plugins/comments.md` (or `node_modules/@voltro/plugin-comments/README.md`) | Comment threads on any app entity — replies, resolve/reopen, @-mentions with notifications, reactions, unread counters — live over the reactive engine, with an ejectable thread UI. |
|
|
792
794
|
| datadog | `node_modules/@voltro/cli/templates/agent-docs/plugins/datadog.md` (or `node_modules/@voltro/plugin-datadog/README.md`) | Agentless Datadog metrics exporter — pushes the unified Metrics-API to Datadog's /api/v2/series HTTP intake. |
|
|
@@ -9,7 +9,7 @@ each plugin's own README.
|
|
|
9
9
|
|
|
10
10
|
| Topic | Open | Summary |
|
|
11
11
|
|---|---|---|
|
|
12
|
-
| **What's new in 0.
|
|
12
|
+
| **What's new in 0.55.0** | `node_modules/@voltro/cli/templates/agent-docs/whats-new.md` | Everything that changed in this version. Read it before hand-rolling something the framework may now ship. |
|
|
13
13
|
| AI | `node_modules/@voltro/cli/templates/agent-docs/ai.md` | How Voltro treats AI — agents, tools, streaming, RAG — all primitives over the same WebSocket as the rest of the framework. |
|
|
14
14
|
| Authentication | `node_modules/@voltro/cli/templates/agent-docs/authentication.md` | How @voltro/plugin-auth wires password + session-cookie auth across api + web, plus the pluggable identity-strategy protocol. |
|
|
15
15
|
| Caching | `node_modules/@voltro/cli/templates/agent-docs/caching.md` | Voltro's caching layer (@voltro/cache) — an always-on memory default, swappable Redis-compatible backends, a low-level wrap primitive, and automatic query-result invalidation. |
|
|
@@ -67,7 +67,7 @@ each plugin's own README.
|
|
|
67
67
|
| auth-workos | `node_modules/@voltro/cli/templates/agent-docs/plugins/auth-workos.md` (or `node_modules/@voltro/plugin-auth-workos/README.md`) | WorkOS AuthStrategy — verifies WorkOS AuthKit / SSO JWTs via JWKS (no API key) and maps org_id → tenantId. |
|
|
68
68
|
| billing | `node_modules/@voltro/cli/templates/agent-docs/plugins/billing.md` (or `node_modules/@voltro/plugin-billing/README.md`) | Subscriptions, plans, entitlements, and usage metering over a pluggable provider (Stripe + mock). Money is integer minor units. |
|
|
69
69
|
| broadcast | `node_modules/@voltro/cli/templates/agent-docs/plugins/broadcast.md` (or `node_modules/@voltro/plugin-broadcast/README.md`) | Cross-replica reactivity over a pub/sub bus (Redis / NATS) for non-postgres dialects — closes the single-instance gap so a write on one pod surfaces on another. |
|
|
70
|
-
| cdc-out | `node_modules/@voltro/cli/templates/agent-docs/plugins/cdc-out.md` (or `node_modules/@voltro/plugin-cdc-out/README.md`) | Declaratively mirror table changes outward to external sinks (webhook, plus a CdcSink interface for custom sinks) through a durable outbox — ordered per pipe, at-least-once from enqueue, retried with backoff, dead-lettered. |
|
|
70
|
+
| cdc-out | `node_modules/@voltro/cli/templates/agent-docs/plugins/cdc-out.md` (or `node_modules/@voltro/plugin-cdc-out/README.md`) | Declaratively mirror table changes outward to external sinks (webhook, Kafka, plus a CdcSink interface for custom sinks) through a durable outbox — ordered per pipe, at-least-once from enqueue, retried with backoff, dead-lettered. |
|
|
71
71
|
| clickhouse | `node_modules/@voltro/cli/templates/agent-docs/plugins/clickhouse.md` (or `node_modules/@voltro/plugin-clickhouse/README.md`) | Production-grade OLAP AnalyticsSink over ClickHouse — self-hosted or ClickHouse Cloud — for billions of events with millisecond aggregates. |
|
|
72
72
|
| comments | `node_modules/@voltro/cli/templates/agent-docs/plugins/comments.md` (or `node_modules/@voltro/plugin-comments/README.md`) | Comment threads on any app entity — replies, resolve/reopen, @-mentions with notifications, reactions, unread counters — live over the reactive engine, with an ejectable thread UI. |
|
|
73
73
|
| datadog | `node_modules/@voltro/cli/templates/agent-docs/plugins/datadog.md` (or `node_modules/@voltro/plugin-datadog/README.md`) | Agentless Datadog metrics exporter — pushes the unified Metrics-API to Datadog's /api/v2/series HTTP intake. |
|
|
@@ -460,7 +460,7 @@
|
|
|
460
460
|
{
|
|
461
461
|
"slug": "cdc-out",
|
|
462
462
|
"title": "CDC-out (reverse-ETL)",
|
|
463
|
-
"description": "Declaratively mirror table changes outward to external sinks (webhook, plus a CdcSink interface for custom sinks) through a durable outbox — ordered per pipe, at-least-once from enqueue, retried with backoff, dead-lettered.",
|
|
463
|
+
"description": "Declaratively mirror table changes outward to external sinks (webhook, Kafka, plus a CdcSink interface for custom sinks) through a durable outbox — ordered per pipe, at-least-once from enqueue, retried with backoff, dead-lettered.",
|
|
464
464
|
"pkg": "@voltro/plugin-cdc-out",
|
|
465
465
|
"doc": "plugins/cdc-out.md",
|
|
466
466
|
"module": "agent-docs/plugins/cdc-out.md"
|
|
@@ -116,7 +116,7 @@ Env vars `providerFromEnv()` reads:
|
|
|
116
116
|
| `AI_PROVIDER` | `mock` | `mock` \| `anthropic` \| `openai` \| `gateway`. |
|
|
117
117
|
| `AI_MODEL` | per provider (see below) | Override the model. Defaults: `claude-opus-4-8` (anthropic), `gpt-5.5` (openai), `mock` (mock). **REQUIRED for `gateway`** — a `creator/model` id; boot throws if unset. |
|
|
118
118
|
|
|
119
|
-
Provider API keys are read by the underlying `@ai-sdk/*` packages from their standard env vars — `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, `AI_GATEWAY_API_KEY`. The framework doesn't read a separate `AI_API_KEY`. (To give a SINGLE agent its own key from code instead of env, see [Per-config key + base URL](#per-config-key
|
|
119
|
+
Provider API keys are read by the underlying `@ai-sdk/*` packages from their standard env vars — `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, `AI_GATEWAY_API_KEY`. The framework doesn't read a separate `AI_API_KEY`. (To give a SINGLE agent its own key from code instead of env, see [Per-config key + base URL](#per-config-key-base-url) below.)
|
|
120
120
|
|
|
121
121
|
## Anthropic
|
|
122
122
|
|
|
@@ -141,7 +141,7 @@ AI_MODEL=gpt-5.5
|
|
|
141
141
|
OPENAI_API_KEY=sk-…
|
|
142
142
|
```
|
|
143
143
|
|
|
144
|
-
Backed by `@ai-sdk/openai`. `AI_MODEL` is a plain OpenAI model id (`gpt-5.5`, `gpt-4o-mini`, …). For a self-hosted / Azure-style / proxy endpoint, set `baseURL` on a `ProviderConfig` (see [Per-config key + base URL](#per-config-key
|
|
144
|
+
Backed by `@ai-sdk/openai`. `AI_MODEL` is a plain OpenAI model id (`gpt-5.5`, `gpt-4o-mini`, …). For a self-hosted / Azure-style / proxy endpoint, set `baseURL` on a `ProviderConfig` (see [Per-config key + base URL](#per-config-key-base-url)) rather than an env var.
|
|
145
145
|
|
|
146
146
|
## Vercel AI Gateway
|
|
147
147
|
|
|
@@ -239,7 +239,7 @@ export default (input: { prompt: string }) =>
|
|
|
239
239
|
})
|
|
240
240
|
```
|
|
241
241
|
|
|
242
|
-
`GenerateTextOptions` is `{ prompt, system?, provider?, fallbacks?, maxTokens? }`. There is no `messages`/`effort` shape — the prompt is a single string the SDK wraps as the user turn; `system` steers it. `fallbacks` is a [provider fallback chain](#fallback-chain
|
|
242
|
+
`GenerateTextOptions` is `{ prompt, system?, provider?, fallbacks?, maxTokens? }`. There is no `messages`/`effort` shape — the prompt is a single string the SDK wraps as the user turn; `system` steers it. `fallbacks` is a [provider fallback chain](#fallback-chain-survive-a-provider-outage).
|
|
243
243
|
|
|
244
244
|
Structured output:
|
|
245
245
|
|
|
@@ -276,7 +276,7 @@ type ProviderConfig =
|
|
|
276
276
|
| { name: 'gateway'; model: `${string}/${string}`; apiKey?: string; baseURL?: string } // creator/model
|
|
277
277
|
```
|
|
278
278
|
|
|
279
|
-
The mock-only `mockText` / `script` can't appear on a real provider (the type rejects it), the gateway's `model` is a `creator/model`-typed string (a bare `'gpt-5.5'` is a compile error, not a boot crash), and the per-provider model-id types (`AnthropicModel` / `OpenAIModel`) are **open unions** — known ids autocomplete, but any string the provider ships tomorrow still type-checks. Use `provider` for per-request model selection (e.g. a cheaper model on a fallback path), or to pin a specific key/endpoint (see [Per-config key + base URL](#per-config-key
|
|
279
|
+
The mock-only `mockText` / `script` can't appear on a real provider (the type rejects it), the gateway's `model` is a `creator/model`-typed string (a bare `'gpt-5.5'` is a compile error, not a boot crash), and the per-provider model-id types (`AnthropicModel` / `OpenAIModel`) are **open unions** — known ids autocomplete, but any string the provider ships tomorrow still type-checks. Use `provider` for per-request model selection (e.g. a cheaper model on a fallback path), or to pin a specific key/endpoint (see [Per-config key + base URL](#per-config-key-base-url)).
|
|
280
280
|
|
|
281
281
|
For runtime provider switching across a whole layer, bind an `AiServiceImpl` to the `AiService` Context tag at boot and read it with `yield* AiService` — `defaultAiService` (backed by `providerFromEnv`) is the default.
|
|
282
282
|
|
|
@@ -2602,6 +2602,121 @@ A membership that ends mid-subscription therefore stops serving rows — the
|
|
|
2602
2602
|
caller's open ticket list drops the rows they can no longer see, without a
|
|
2603
2603
|
refresh and without the subscription having to be torn down.
|
|
2604
2604
|
|
|
2605
|
+
**On every transport, and that list is complete: the WebSocket, an [SSE
|
|
2606
|
+
stream](/docs/data/rest-routes#live-updates-over-http-stream-sse), and a
|
|
2607
|
+
[gRPC](/docs/data/grpc) server-streaming rpc.** All three open their
|
|
2608
|
+
subscription through the same code and resolve the filter per delivery, so
|
|
2609
|
+
"stays open for hours" never becomes a way to hold a stale predicate. The same
|
|
2610
|
+
delivery also re-checks the query's `guards:`; a filter resolution that FAILS
|
|
2611
|
+
revokes the subscription on all three rather than falling back to an unfiltered
|
|
2612
|
+
or empty read.
|
|
2613
|
+
|
|
2614
|
+
## Declare which tables it narrows — `tables:`
|
|
2615
|
+
|
|
2616
|
+
Optional, one line, and it buys back a feature the filter otherwise switches
|
|
2617
|
+
off for the whole app:
|
|
2618
|
+
|
|
2619
|
+
```ts
|
|
2620
|
+
const OWNED = new Set(['documents', 'comments'])
|
|
2621
|
+
|
|
2622
|
+
setRowFilter({
|
|
2623
|
+
load,
|
|
2624
|
+
predicate: (ctx, table) => (OWNED.has(table) ? eq('ownerId', ctx.userId) : undefined),
|
|
2625
|
+
// Derived from the same set the predicate reads. Two hand-kept lists is the
|
|
2626
|
+
// shape in which a table lands in exactly one of them.
|
|
2627
|
+
tables: [...OWNED],
|
|
2628
|
+
})
|
|
2629
|
+
```
|
|
2630
|
+
|
|
2631
|
+
**What it buys.** [Delta-resume](/docs/data/wire-protocol#reconnect-delta-resume)
|
|
2632
|
+
is excluded for a subscription whose row set is re-resolved per delivery —
|
|
2633
|
+
replaying deltas could serve rows the subject has since lost. Without a
|
|
2634
|
+
declaration the framework cannot tell which tables your predicate may reach, so
|
|
2635
|
+
it excludes them all: one registration disables cheap reconnects for every
|
|
2636
|
+
subscription in the process, including every one reading a table your predicate
|
|
2637
|
+
never returns anything for. With the declaration, only subscriptions on the
|
|
2638
|
+
listed tables are excluded.
|
|
2639
|
+
|
|
2640
|
+
**What it does not buy, and this bounds the whole feature.** A subscription only
|
|
2641
|
+
has a delta chain when its executor returns a **descriptor**. One that returns a
|
|
2642
|
+
mapped value or a page envelope —
|
|
2643
|
+
|
|
2644
|
+
```ts
|
|
2645
|
+
export default async ({ database }) => {
|
|
2646
|
+
const rows = await database.notifications.where(...)
|
|
2647
|
+
return { notifications: rows.map(toDto), hasMore: rows.length === 20 }
|
|
2648
|
+
}
|
|
2649
|
+
```
|
|
2650
|
+
|
|
2651
|
+
— re-runs an opaque handler and emits snapshots, with or without a filter. So
|
|
2652
|
+
the count `tables:` gives back is the count of descriptor-returning
|
|
2653
|
+
subscriptions, not the number of queries in your app. Declare it anyway (it
|
|
2654
|
+
costs nothing, and it applies the moment such a query returns the builder), but
|
|
2655
|
+
measure before expecting a change.
|
|
2656
|
+
|
|
2657
|
+
**How to see which of yours are which.** The two shapes are indistinguishable
|
|
2658
|
+
from the outside — a subscription that resumed and one that was never eligible
|
|
2659
|
+
both reconnect with rows on the screen. So the runtime records its own verdict
|
|
2660
|
+
at the moment it decides, per query label:
|
|
2661
|
+
|
|
2662
|
+
```sh
|
|
2663
|
+
curl -s localhost:4000/_voltro/inspect/subscriptions | jq .resume
|
|
2664
|
+
```
|
|
2665
|
+
|
|
2666
|
+
```json
|
|
2667
|
+
[
|
|
2668
|
+
{ "label": "documents.list", "resumable": 12, "excluded": {} },
|
|
2669
|
+
{ "label": "notifications.list", "resumable": 0, "excluded": { "computed": 8 } },
|
|
2670
|
+
{ "label": "comments.list", "resumable": 0, "excluded": { "row-filter": 3 } }
|
|
2671
|
+
]
|
|
2672
|
+
```
|
|
2673
|
+
|
|
2674
|
+
A label appears once something has subscribed to it, so click through the app
|
|
2675
|
+
first. `computed` means no declaration can ever help that query; `row-filter`
|
|
2676
|
+
means the filter narrows its source and the exclusion is the point;
|
|
2677
|
+
`eager-load` means dropping the `.with(...)` would flip it. `voltro dev` also
|
|
2678
|
+
logs each verdict once per label under the `voltro:resume` scope.
|
|
2679
|
+
|
|
2680
|
+
**Why a declaration and not a probe.** Resolving the scope at subscribe time
|
|
2681
|
+
and treating "returns `undefined` for this table" as safe is cheaper and
|
|
2682
|
+
unsound: your predicate is a function of freshly loaded context, so a table it
|
|
2683
|
+
does not narrow now may be narrowed on the next delivery — which is the entire
|
|
2684
|
+
reason the filter is re-resolved per delivery. A static list is a promise about
|
|
2685
|
+
every future resolution.
|
|
2686
|
+
|
|
2687
|
+
**It is verified, not trusted.** Returning a predicate for a table outside the
|
|
2688
|
+
list raises `RowFilterDeclarationViolated` at the read that did it — the
|
|
2689
|
+
request fails and a subscription is revoked. A declaration nobody checks is a
|
|
2690
|
+
comment, and this one is load-bearing: the resume grant is issued on its
|
|
2691
|
+
strength. Omit `tables:` entirely and nothing is verified and nothing resumes —
|
|
2692
|
+
the conservative default.
|
|
2693
|
+
|
|
2694
|
+
## Eager loads are refused, not silently unfiltered
|
|
2695
|
+
|
|
2696
|
+
A relation pulled in with `.with(...)` is resolved **below** the seam that
|
|
2697
|
+
applies the filter: the stores expand the eager tree themselves (the memory
|
|
2698
|
+
store recurses through its own raw read; the SQL stores fold the relation into
|
|
2699
|
+
one join). So a filtered table reached through a relation would come back
|
|
2700
|
+
unfiltered.
|
|
2701
|
+
|
|
2702
|
+
Rather than serve those rows, the read **refuses**, naming the table and both
|
|
2703
|
+
ways out:
|
|
2704
|
+
|
|
2705
|
+
```
|
|
2706
|
+
row filter: 'readers' is reached through an eager load on 'notes', and an eager
|
|
2707
|
+
relation is resolved BELOW the row filter — its rows would come back
|
|
2708
|
+
unfiltered. Refusing the read rather than serving it.
|
|
2709
|
+
→ read 'readers' as its own query (it is filtered there), or
|
|
2710
|
+
→ drop 'readers' from this .with(...) if the relation does not need
|
|
2711
|
+
row-level narrowing.
|
|
2712
|
+
```
|
|
2713
|
+
|
|
2714
|
+
Only relations reaching a table your filter actually narrows are affected —
|
|
2715
|
+
every other eager load is untouched. Applying the filter inside eager
|
|
2716
|
+
compilation is the real fix and it is a per-dialect change; until then this is
|
|
2717
|
+
a refusal rather than a leak, for the same reason the module refuses to
|
|
2718
|
+
fail open.
|
|
2719
|
+
|
|
2605
2720
|
## Row filters vs. guards
|
|
2606
2721
|
|
|
2607
2722
|
They answer different questions, and a complete policy usually wants both:
|
|
@@ -32,6 +32,7 @@ The dispatcher routes `voltro <command> [args]` to the matching subcommand and p
|
|
|
32
32
|
| Integrate | [`webhooks`](/docs/plugins/webhooks#voltro-webhooks-consumer-the-package-your-subscribers-install) (`consumer` / `events`) — generate the ZERO-dependency Standard-Webhooks verification package your subscribers install, from your own declared events (`--out` / `--name`); list the events a subscriber can register for (`--json`) |
|
|
33
33
|
| [Inspect & debug](/docs/cli/inspect) | `inspect`, `logs`, `traces`, `workflows`, `cluster`, `check` |
|
|
34
34
|
| [Health & surface](/docs/cli/build-and-start) | [`doctor`](/docs/cli/build-and-start) — serve preflight + the hand-roll detector (names the shipped primitive at the spot you're rebuilding it); [`capabilities`](/docs/cli/build-and-start) (`--json`) — the export surface read from your installed `@voltro/*`, so it can be verified instead of recalled; `info` (`--json`) — CLI / node / package-manager / dialect + every installed `@voltro/*` version, flagging lockstep skew (exits 1 on skew) |
|
|
35
|
+
| Mobile | `mobile` (`codegen` / `links`) — the Expo app's build-time steps: the typed rpc client + deep-link table, and the `apple-app-site-association` / `assetlinks.json` a universal link needs. `voltro codegen` inside a mobile app runs the same generators. |
|
|
35
36
|
| Harness | `test`, `e2e` |
|
|
36
37
|
| Cloud | `cloud` (`login` / `whoami` / `projects` / `env` / `import`); `login` is a top-level alias of `cloud login` |
|
|
37
38
|
| Secrets | `secret` (`generate [purpose]` — the right var+format per secret; `generate` alone → a generic secret; `list`) |
|
|
@@ -143,6 +144,8 @@ config value always winning:
|
|
|
143
144
|
| `VOLTRO_HSTS` | `Strict-Transport-Security` value. `off` drops just this one. |
|
|
144
145
|
| `VOLTRO_MAX_RPC_BODY_BYTES` | Cap on the buffered `POST /rpc` JSON body (default 8 MiB) — an oversized body is refused `413` and never buffered past the cap. File uploads ride plugin routes with their own limits. |
|
|
145
146
|
| `VOLTRO_MAX_BODY_BYTES` | Cap on every OTHER body read — plugin HTTP routes, REST routes, incoming webhooks (default 8 MiB, matching the rpc cap). The config-file spelling is `http.maxBodyBytes` in `app.config.ts`; per-route overrides (`defineRestRoute({ maxBodyBytes })`, a webhook handler's `maxBodyBytes`) win over both. Oversize is `413` for `Content-Length` and chunked alike. |
|
|
147
|
+
| `VOLTRO_CRDT_COMPACT_MAX_BYTES` | Size above which a merged `crdtText()` / `crdtDoc()` blob is soft-compacted (default 512 KiB, `0` disables). The config-file spelling is `crdt.compactMaxBytes` in `app.config.ts`; this variable wins over it. See [local-first](/docs/local-first/overview#rich-text-crdtdoc-usecrdtdoc-usecrdteditor). |
|
|
148
|
+
| `VOLTRO_GRPC_DRAIN_MS` | How long the [gRPC surface](/docs/data/grpc) lets open calls finish on shutdown before force-closing them (default 5000, `0` forces immediately). The config-file spelling is `grpc.drainMs`; this variable wins over it. Keep it below your orchestrator's termination grace. |
|
|
146
149
|
|
|
147
150
|
Response compression for the buffered non-rpc surfaces (and `voltro start`'s
|
|
148
151
|
HTML) is configured in the same `http:` block — `http.compression.{enabled,minBytes}`
|
|
@@ -2031,7 +2034,7 @@ package's README.
|
|
|
2031
2034
|
|
|
2032
2035
|
_voltro migrate — apply the declared schema through the declarative differ (an alias of voltro db apply)._
|
|
2033
2036
|
|
|
2034
|
-
`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.
|
|
2037
|
+
`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-diff-workflow-voltro-db)**: it diffs the declared schema against the live database and emits the ALTERs, so a changed column or a new index actually lands.
|
|
2035
2038
|
|
|
2036
2039
|
> 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`.
|
|
2037
2040
|
|
|
@@ -2966,17 +2969,67 @@ the swap could not run: 2 row(s) in the bundle reference a row the bundle does n
|
|
|
2966
2969
|
The target is UNCHANGED — the swap runs in one transaction and none of it committed.
|
|
2967
2970
|
```
|
|
2968
2971
|
|
|
2969
|
-
Staging tables
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
+
### Staging tables a dead run left behind
|
|
2973
|
+
|
|
2974
|
+
A staged run RECORDS the scratch tables it creates, in the same
|
|
2975
|
+
`_voltro_replace_in_progress` table an interrupted destructive `replace` writes
|
|
2976
|
+
to — with one difference that matters: **a staging record never refuses a boot.**
|
|
2977
|
+
Nothing was destroyed, so there is nothing to refuse over. The boot reports
|
|
2978
|
+
instead:
|
|
2979
|
+
|
|
2980
|
+
```
|
|
2981
|
+
staged data-import leftovers:
|
|
2982
|
+
- 3 staging table(s) from a `replace` over api, last active 74 minute(s) ago — DROPPED: the run
|
|
2983
|
+
is not resumable and has been silent long enough that nothing is loading into them.
|
|
2984
|
+
The target of a staged `replace` is untouched until one short swap at the end, so none of this is
|
|
2985
|
+
a reason to refuse the boot — it is a reason to know the disk is holding a copy of a bundle.
|
|
2986
|
+
```
|
|
2987
|
+
|
|
2988
|
+
The run refreshes a heartbeat on that record every couple of seconds while rows
|
|
2989
|
+
land, which is what lets a boot tell the three cases apart:
|
|
2990
|
+
|
|
2991
|
+
| what the record says | what the boot does |
|
|
2992
|
+
|---|---|
|
|
2993
|
+
| silent past the threshold, started without `--no-atomic` | **drops** the tables it names |
|
|
2994
|
+
| still beating | leaves them — an import is loading into them right now, here or on another replica |
|
|
2995
|
+
| started `--no-atomic` | leaves them — its staging IS the resume point |
|
|
2996
|
+
|
|
2997
|
+
The threshold is `30` minutes by default. It is deliberately generous: the cost
|
|
2998
|
+
of collecting too early is that an in-flight import's swap fails with a missing
|
|
2999
|
+
table and you re-run it — the target is untouched either way — but the cost is
|
|
3000
|
+
still a re-run.
|
|
3001
|
+
|
|
3002
|
+
Declare a different one for a deployment whose imports routinely pause longer
|
|
3003
|
+
than that, waiting on an upstream export or a maintenance window:
|
|
3004
|
+
|
|
3005
|
+
```ts
|
|
3006
|
+
// app.config.ts
|
|
3007
|
+
export default {
|
|
3008
|
+
dataTransfer: {
|
|
3009
|
+
stagingStaleMinutes: 90,
|
|
3010
|
+
},
|
|
3011
|
+
}
|
|
3012
|
+
```
|
|
3013
|
+
|
|
3014
|
+
`VOLTRO_STAGING_STALE_MINUTES` overrides the declaration in turn — an operator
|
|
3015
|
+
acting on a running deployment outranks what the project declared. Note that the
|
|
3016
|
+
threshold decides only what a boot DROPS: leftover staging tables are named in
|
|
3017
|
+
the boot log either way.
|
|
3018
|
+
|
|
3019
|
+
What the boot does NOT collect, you can:
|
|
2972
3020
|
|
|
2973
3021
|
```bash
|
|
2974
3022
|
voltro data clear-staging --yes
|
|
2975
3023
|
```
|
|
2976
3024
|
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
3025
|
+
It now labels each table with what its own run says, so a resume point is
|
|
3026
|
+
distinguishable from a leftover before you drop it:
|
|
3027
|
+
|
|
3028
|
+
```
|
|
3029
|
+
2 staging table(s) from an earlier `--mode replace`:
|
|
3030
|
+
_voltro_staging_tasks — RESUMABLE: a `--no-atomic` re-run continues from it, last active 4 min ago
|
|
3031
|
+
_voltro_staging_notes — no run claims it (an orphan, or from before the marker)
|
|
3032
|
+
```
|
|
2980
3033
|
|
|
2981
3034
|
A **cycle** in the bundle's foreign keys is detected before the load, not after
|
|
2982
3035
|
it. The swap inserts parents first, so two tables referencing each other cannot
|
|
@@ -3559,7 +3612,14 @@ A native run reports **blobs**, not rows: the vendor tool reports no row count w
|
|
|
3559
3612
|
|
|
3560
3613
|
### The provenance stamp — a restore that refuses the wrong DB
|
|
3561
3614
|
|
|
3562
|
-
A native dump is opaque: it doesn't say which dialect made it, which schema shape it carries, or when. `backup` writes a sidecar `voltro-backup-stamp.json` next to the artifact recording exactly that — `dialect`, the
|
|
3615
|
+
A native dump is opaque: it doesn't say which dialect made it, which schema shape it carries, or when. `backup` writes a sidecar `voltro-backup-stamp.json` next to the artifact recording exactly that — `dialect`, the `@voltro/cli` version, the timestamp, and **two** schema fingerprints.
|
|
3616
|
+
|
|
3617
|
+
Two, because they are different facts and only one of them is a claim about the artifact:
|
|
3618
|
+
|
|
3619
|
+
- **`schemaFingerprint`** — the source database's whole live schema at backup time. This is what the skew warning below compares against a target.
|
|
3620
|
+
- **`dumpFingerprint`** — the schema the **artifact carries**: that same snapshot minus the tables the dump excludes. On postgres and the mysql family those are `_voltro_replace_in_progress` and `_voltro_data_transfers` (see above); on sqlite, turso and mssql nothing is excluded and the two values are equal.
|
|
3621
|
+
|
|
3622
|
+
The distinction is not bookkeeping. `voltro data backup` opens its own run row in `_voltro_data_transfers` *before* it dumps, so on any database the framework has run against, the artifact is two tables short of the live schema it was taken from. Anything comparing a restored schema against a stamped one has to compare against `dumpFingerprint` — the drill did not, and failed every healthy backup with *"the artifact is inconsistent."*
|
|
3563
3623
|
|
|
3564
3624
|
`restore` reads the stamp **before touching the DB** and acts on two failures that are otherwise silent until they corrupt:
|
|
3565
3625
|
|
|
@@ -3575,13 +3635,39 @@ voltro data restore ./backups/2026-07-01 --drill --drill-url postgres://…/scra
|
|
|
3575
3635
|
# or set DRILL_DB_URL and just: voltro data restore ./backups/2026-07-01 --drill
|
|
3576
3636
|
```
|
|
3577
3637
|
|
|
3578
|
-
`--drill` restores the artifact into a **throwaway** database (from `--drill-url` / `DRILL_DB_URL`) and verifies it — **without ever touching the live DB**. It refuses a drill target that resolves to your live connection (a drill that `--clean`s production is the disaster it exists to rehearse against). After the restore it introspects the throwaway DB and
|
|
3638
|
+
`--drill` restores the artifact into a **throwaway** database (from `--drill-url` / `DRILL_DB_URL`) and verifies it — **without ever touching the live DB**. It refuses a drill target that resolves to your live connection (a drill that `--clean`s production is the disaster it exists to rehearse against). After the restore it introspects the throwaway DB and probes its migration ledger:
|
|
3579
3639
|
|
|
3580
3640
|
- **zero tables restored** → FAIL (the dump is empty or unreadable — this backup would not recover you),
|
|
3581
|
-
- **fingerprint disagrees with the stamp
|
|
3582
|
-
-
|
|
3641
|
+
- **schema fingerprint disagrees with the stamp's `dumpFingerprint`** → FAIL (the restore didn't reproduce what was backed up),
|
|
3642
|
+
- **`_voltro_migration_plans` restored EMPTY** → FAIL (see below),
|
|
3643
|
+
- **tables + matching fingerprint + a populated or absent ledger** → PASS.
|
|
3644
|
+
|
|
3645
|
+
A stamp too old to carry a `dumpFingerprint` gives a **PASS (partial)** that says the shape could not be cross-checked. It does not fall back to `schemaFingerprint`: that is the comparison that fails a healthy backup, and a check that is red on every real input gets switched off — taking its genuine failures with it.
|
|
3646
|
+
|
|
3647
|
+
It exits non-zero on any FAIL, so a scheduled CI job turns a silently-broken backup into a red build. Run it against your latest artifact on a cron — a backup you've never restored is a hypothesis, and this is how you keep it a fact.
|
|
3648
|
+
|
|
3649
|
+
#### The ledger check — the one thing a schema comparison cannot see
|
|
3650
|
+
|
|
3651
|
+
A fingerprint answers *"is the shape right?"*. A drill's real question is *"would my app come up against this?"*, and the gap between them is **content** — a framework table that restored with the right columns and the wrong rows.
|
|
3652
|
+
|
|
3653
|
+
`voltro serve`'s boot gate reads the newest row of `_voltro_migration_plans` and refuses with `prod-mismatch` when there is none. So a ledger table that restores with exactly the right columns and **zero rows** is a database no source tree can boot, and its schema fingerprint is identical to a healthy one's. The drill fails that, and names it:
|
|
3654
|
+
|
|
3655
|
+
```
|
|
3656
|
+
FAIL — restored 30 table(s) with the right shape, but `_voltro_migration_plans`
|
|
3657
|
+
came back EMPTY.
|
|
3658
|
+
`voltro serve` reads the newest row of that table as its boot gate and
|
|
3659
|
+
refuses with `prod-mismatch` when there is none.
|
|
3660
|
+
```
|
|
3661
|
+
|
|
3662
|
+
A restored database with **no ledger table at all** is not a voltro-managed schema (a hand-made dump, someone else's database) — the drill says so and claims nothing about booting it, rather than failing it.
|
|
3663
|
+
|
|
3664
|
+
What the drill deliberately does **not** judge is a ledger whose fingerprint differs from what your code declares. It has no way to know which commit you will deploy next to this database, and `voltro db apply` clears that state anyway; failing a backup for it would make the drill red for a reason that is not about the backup.
|
|
3665
|
+
|
|
3666
|
+
#### Why there is no full app boot
|
|
3667
|
+
|
|
3668
|
+
Booting a real app against the restored database sounds like the stronger check, and it would be a weaker one. There is no app in the drill's path — it would have to boot a **fixture**, and a fixture booting says nothing about whether *your* app boots. It moves the drill from *"proves your backup"* to *"proves our fixture"* while reading as the bigger claim.
|
|
3583
3669
|
|
|
3584
|
-
|
|
3670
|
+
The part worth having does not need a process: the boot gate is a comparison, not a startup sequence, so the one boot-fatal condition that holds regardless of which code you deploy is reachable with a `SELECT`. That is the ledger check above.
|
|
3585
3671
|
|
|
3586
3672
|
### Point-in-time recovery (PITR) is your database's job, not the framework's
|
|
3587
3673
|
|