@voltro/cli 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (626) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/LICENSE +57 -0
  3. package/README.md +26 -0
  4. package/SECURITY.md +56 -0
  5. package/THIRD-PARTY-NOTICES.md +20236 -0
  6. package/bin/voltro.mjs +43 -0
  7. package/dist/bin.d.ts +1 -0
  8. package/dist/bin.js +9 -0
  9. package/dist/commands-CfPH2Wf4.js +18061 -0
  10. package/dist/frameworkInspectState-CX2250XB.js +86 -0
  11. package/dist/index.d.ts +48 -0
  12. package/dist/index.js +5 -0
  13. package/dist/inspectState.d.ts +116 -0
  14. package/dist/inspectState.js +2 -0
  15. package/dist/startup.d.ts +26 -0
  16. package/dist/startup.js +2 -0
  17. package/dist/startupRunner-CRhuUl91.js +71 -0
  18. package/package.json +88 -0
  19. package/templates/AGENTS.core.md +258 -0
  20. package/templates/AGENTS.md +351 -0
  21. package/templates/agent-docs/_index.md +93 -0
  22. package/templates/agent-docs/_manifest.json +655 -0
  23. package/templates/agent-docs/ai.md +1845 -0
  24. package/templates/agent-docs/authentication.md +1788 -0
  25. package/templates/agent-docs/caching.md +624 -0
  26. package/templates/agent-docs/cli.md +1650 -0
  27. package/templates/agent-docs/configuration.md +295 -0
  28. package/templates/agent-docs/data.md +2172 -0
  29. package/templates/agent-docs/database/advancedqueries.md +1583 -0
  30. package/templates/agent-docs/database/columntypes.md +1200 -0
  31. package/templates/agent-docs/database/hosting.md +881 -0
  32. package/templates/agent-docs/database/migrations.md +2938 -0
  33. package/templates/agent-docs/database/misc.md +270 -0
  34. package/templates/agent-docs/database/overview.md +108 -0
  35. package/templates/agent-docs/database/querying.md +1622 -0
  36. package/templates/agent-docs/database/scaling.md +331 -0
  37. package/templates/agent-docs/database/schema.md +1458 -0
  38. package/templates/agent-docs/database/seedsdialects.md +1235 -0
  39. package/templates/agent-docs/database/transactions.md +285 -0
  40. package/templates/agent-docs/deployment.md +999 -0
  41. package/templates/agent-docs/internationalization.md +359 -0
  42. package/templates/agent-docs/introduction.md +438 -0
  43. package/templates/agent-docs/multi-tenancy.md +610 -0
  44. package/templates/agent-docs/observability.md +350 -0
  45. package/templates/agent-docs/plugins.md +1273 -0
  46. package/templates/agent-docs/reference.md +978 -0
  47. package/templates/agent-docs/routing.md +1553 -0
  48. package/templates/agent-docs/scheduling.md +666 -0
  49. package/templates/agent-docs/schema-driven-ui.md +607 -0
  50. package/templates/agent-docs/security.md +42 -0
  51. package/templates/agent-docs/templates/apibackends.md +3214 -0
  52. package/templates/agent-docs/templates/appshells.md +2062 -0
  53. package/templates/agent-docs/templates/custom.md +128 -0
  54. package/templates/agent-docs/templates/overview.md +122 -0
  55. package/templates/agent-docs/templates/serverless.md +315 -0
  56. package/templates/agent-docs/testing.md +376 -0
  57. package/templates/agent-docs/workflows.md +1351 -0
  58. package/templates/apps/api-ai/README.md +105 -0
  59. package/templates/apps/api-ai/actions/summarize.action.server.tsx +32 -0
  60. package/templates/apps/api-ai/actions/summarize.action.ts +33 -0
  61. package/templates/apps/api-ai/agents/support.agent.server.tsx +31 -0
  62. package/templates/apps/api-ai/agents/support.agent.tsx +27 -0
  63. package/templates/apps/api-ai/app.config.ts +57 -0
  64. package/templates/apps/api-ai/database/schema.ts +67 -0
  65. package/templates/apps/api-ai/package.json +27 -0
  66. package/templates/apps/api-ai/seeds/docs.seed.ts +67 -0
  67. package/templates/apps/api-ai/template.json +6 -0
  68. package/templates/apps/api-ai/tests/summarize.test.ts +50 -0
  69. package/templates/apps/api-ai/tools/searchDocs.tool.tsx +60 -0
  70. package/templates/apps/api-ai/tsconfig.json +5 -0
  71. package/templates/apps/api-auth/.env +17 -0
  72. package/templates/apps/api-auth/README.md +109 -0
  73. package/templates/apps/api-auth/actions/me.action.server.ts +20 -0
  74. package/templates/apps/api-auth/actions/me.action.ts +19 -0
  75. package/templates/apps/api-auth/app.config.ts +59 -0
  76. package/templates/apps/api-auth/database/schema.ts +26 -0
  77. package/templates/apps/api-auth/package.json +28 -0
  78. package/templates/apps/api-auth/template.json +6 -0
  79. package/templates/apps/api-auth/tests/me.test.ts +37 -0
  80. package/templates/apps/api-auth/tsconfig.json +5 -0
  81. package/templates/apps/api-backend/README.md +25 -0
  82. package/templates/apps/api-backend/app.config.ts +39 -0
  83. package/templates/apps/api-backend/database/schema.ts +57 -0
  84. package/templates/apps/api-backend/mutations/notes.create.mutation.server.ts +19 -0
  85. package/templates/apps/api-backend/mutations/notes.create.mutation.ts +37 -0
  86. package/templates/apps/api-backend/package.json +28 -0
  87. package/templates/apps/api-backend/queries/notes.query.server.ts +14 -0
  88. package/templates/apps/api-backend/queries/notes.query.ts +20 -0
  89. package/templates/apps/api-backend/template.json +6 -0
  90. package/templates/apps/api-backend/tests/notes.create.test.ts +50 -0
  91. package/templates/apps/api-backend/tsconfig.json +5 -0
  92. package/templates/apps/api-backend-deactivation/README.md +49 -0
  93. package/templates/apps/api-backend-deactivation/actions/users.get.action.server.ts +17 -0
  94. package/templates/apps/api-backend-deactivation/actions/users.get.action.ts +18 -0
  95. package/templates/apps/api-backend-deactivation/app.config.ts +20 -0
  96. package/templates/apps/api-backend-deactivation/database/schema.ts +41 -0
  97. package/templates/apps/api-backend-deactivation/mutations/users.create.mutation.server.ts +6 -0
  98. package/templates/apps/api-backend-deactivation/mutations/users.create.mutation.ts +16 -0
  99. package/templates/apps/api-backend-deactivation/mutations/users.deactivate.mutation.server.ts +10 -0
  100. package/templates/apps/api-backend-deactivation/mutations/users.deactivate.mutation.ts +19 -0
  101. package/templates/apps/api-backend-deactivation/package.json +28 -0
  102. package/templates/apps/api-backend-deactivation/template.json +6 -0
  103. package/templates/apps/api-backend-deactivation/tests/users.deactivate.test.ts +47 -0
  104. package/templates/apps/api-backend-deactivation/tsconfig.json +5 -0
  105. package/templates/apps/api-backend-mail/README.md +39 -0
  106. package/templates/apps/api-backend-mail/actions/sendWelcome.action.server.ts +15 -0
  107. package/templates/apps/api-backend-mail/actions/sendWelcome.action.ts +21 -0
  108. package/templates/apps/api-backend-mail/app.config.ts +34 -0
  109. package/templates/apps/api-backend-mail/database/schema.ts +57 -0
  110. package/templates/apps/api-backend-mail/emails/welcome.email.tsx +106 -0
  111. package/templates/apps/api-backend-mail/mutations/notes.create.mutation.server.ts +19 -0
  112. package/templates/apps/api-backend-mail/mutations/notes.create.mutation.ts +37 -0
  113. package/templates/apps/api-backend-mail/package.json +30 -0
  114. package/templates/apps/api-backend-mail/queries/notes.query.server.ts +14 -0
  115. package/templates/apps/api-backend-mail/queries/notes.query.ts +20 -0
  116. package/templates/apps/api-backend-mail/template.json +6 -0
  117. package/templates/apps/api-backend-mail/tests/notes.create.test.ts +50 -0
  118. package/templates/apps/api-backend-mail/tsconfig.json +5 -0
  119. package/templates/apps/api-backend-mariadb/.env.example +35 -0
  120. package/templates/apps/api-backend-mariadb/README.md +31 -0
  121. package/templates/apps/api-backend-mariadb/app.config.ts +68 -0
  122. package/templates/apps/api-backend-mariadb/database/schema.ts +57 -0
  123. package/templates/apps/api-backend-mariadb/mutations/notes.create.mutation.server.ts +19 -0
  124. package/templates/apps/api-backend-mariadb/mutations/notes.create.mutation.ts +37 -0
  125. package/templates/apps/api-backend-mariadb/package.json +30 -0
  126. package/templates/apps/api-backend-mariadb/queries/notes.query.server.ts +14 -0
  127. package/templates/apps/api-backend-mariadb/queries/notes.query.ts +20 -0
  128. package/templates/apps/api-backend-mariadb/template.json +6 -0
  129. package/templates/apps/api-backend-mariadb/tests/notes.create.test.ts +50 -0
  130. package/templates/apps/api-backend-mariadb/tsconfig.json +5 -0
  131. package/templates/apps/api-backend-storage/README.md +86 -0
  132. package/templates/apps/api-backend-storage/actions/uploadAvatar.action.server.ts +21 -0
  133. package/templates/apps/api-backend-storage/actions/uploadAvatar.action.ts +21 -0
  134. package/templates/apps/api-backend-storage/actions/uploadDocument.action.server.ts +21 -0
  135. package/templates/apps/api-backend-storage/actions/uploadDocument.action.ts +20 -0
  136. package/templates/apps/api-backend-storage/app.config.ts +38 -0
  137. package/templates/apps/api-backend-storage/database/schema.ts +57 -0
  138. package/templates/apps/api-backend-storage/mutations/notes.create.mutation.server.ts +19 -0
  139. package/templates/apps/api-backend-storage/mutations/notes.create.mutation.ts +37 -0
  140. package/templates/apps/api-backend-storage/package.json +27 -0
  141. package/templates/apps/api-backend-storage/queries/notes.query.server.ts +14 -0
  142. package/templates/apps/api-backend-storage/queries/notes.query.ts +20 -0
  143. package/templates/apps/api-backend-storage/template.json +6 -0
  144. package/templates/apps/api-backend-storage/tests/notes.create.test.ts +50 -0
  145. package/templates/apps/api-backend-storage/tsconfig.json +5 -0
  146. package/templates/apps/api-data-advanced/.env +17 -0
  147. package/templates/apps/api-data-advanced/README.md +88 -0
  148. package/templates/apps/api-data-advanced/app.config.ts +56 -0
  149. package/templates/apps/api-data-advanced/database/actors.entity.ts +14 -0
  150. package/templates/apps/api-data-advanced/database/authors.entity.ts +30 -0
  151. package/templates/apps/api-data-advanced/database/authors.relations.ts +15 -0
  152. package/templates/apps/api-data-advanced/database/books.entity.ts +59 -0
  153. package/templates/apps/api-data-advanced/database/books.relations.ts +10 -0
  154. package/templates/apps/api-data-advanced/database/index.ts +23 -0
  155. package/templates/apps/api-data-advanced/database/tenants.entity.ts +11 -0
  156. package/templates/apps/api-data-advanced/package.json +28 -0
  157. package/templates/apps/api-data-advanced/queries/authors.withBooks.query.server.ts +18 -0
  158. package/templates/apps/api-data-advanced/queries/authors.withBooks.query.ts +40 -0
  159. package/templates/apps/api-data-advanced/queries/books.search.query.server.ts +17 -0
  160. package/templates/apps/api-data-advanced/queries/books.search.query.ts +39 -0
  161. package/templates/apps/api-data-advanced/seeds/catalog.seed.ts +101 -0
  162. package/templates/apps/api-data-advanced/template.json +6 -0
  163. package/templates/apps/api-data-advanced/tests/queries.test.ts +124 -0
  164. package/templates/apps/api-data-advanced/tsconfig.json +5 -0
  165. package/templates/apps/api-durable/README.md +81 -0
  166. package/templates/apps/api-durable/aggregates/orderStats.aggregate.ts +49 -0
  167. package/templates/apps/api-durable/app.config.ts +41 -0
  168. package/templates/apps/api-durable/database/schema.ts +65 -0
  169. package/templates/apps/api-durable/mutations/orders.approve.mutation.server.ts +43 -0
  170. package/templates/apps/api-durable/mutations/orders.approve.mutation.ts +23 -0
  171. package/templates/apps/api-durable/mutations/orders.place.mutation.server.ts +45 -0
  172. package/templates/apps/api-durable/mutations/orders.place.mutation.ts +40 -0
  173. package/templates/apps/api-durable/package.json +28 -0
  174. package/templates/apps/api-durable/schedules/nightlyReport.cron.tsx +37 -0
  175. package/templates/apps/api-durable/startup/warm.startup.tsx +33 -0
  176. package/templates/apps/api-durable/subscribers/orderChanges.subscribe.ts +27 -0
  177. package/templates/apps/api-durable/template.json +6 -0
  178. package/templates/apps/api-durable/tests/orders.place.test.ts +91 -0
  179. package/templates/apps/api-durable/triggers/order.placed.trigger.tsx +21 -0
  180. package/templates/apps/api-durable/tsconfig.json +5 -0
  181. package/templates/apps/api-durable/workflows/order.fulfill.workflow.server.tsx +103 -0
  182. package/templates/apps/api-durable/workflows/order.fulfill.workflow.tsx +25 -0
  183. package/templates/apps/api-feature-flags/README.md +63 -0
  184. package/templates/apps/api-feature-flags/actions/notes.export.action.server.ts +11 -0
  185. package/templates/apps/api-feature-flags/actions/notes.export.action.ts +18 -0
  186. package/templates/apps/api-feature-flags/app.config.ts +41 -0
  187. package/templates/apps/api-feature-flags/database/schema.ts +37 -0
  188. package/templates/apps/api-feature-flags/mutations/notes.create.mutation.server.ts +29 -0
  189. package/templates/apps/api-feature-flags/mutations/notes.create.mutation.ts +28 -0
  190. package/templates/apps/api-feature-flags/package.json +29 -0
  191. package/templates/apps/api-feature-flags/template.json +6 -0
  192. package/templates/apps/api-feature-flags/tests/notes.create.test.ts +67 -0
  193. package/templates/apps/api-feature-flags/tsconfig.json +5 -0
  194. package/templates/apps/api-governance/.env +4 -0
  195. package/templates/apps/api-governance/README.md +61 -0
  196. package/templates/apps/api-governance/actions/profiles.get.action.server.ts +17 -0
  197. package/templates/apps/api-governance/actions/profiles.get.action.ts +17 -0
  198. package/templates/apps/api-governance/app.config.ts +35 -0
  199. package/templates/apps/api-governance/database/schema.ts +41 -0
  200. package/templates/apps/api-governance/mutations/profiles.create.mutation.server.ts +17 -0
  201. package/templates/apps/api-governance/mutations/profiles.create.mutation.ts +21 -0
  202. package/templates/apps/api-governance/package.json +29 -0
  203. package/templates/apps/api-governance/template.json +6 -0
  204. package/templates/apps/api-governance/tests/profiles.create.test.ts +59 -0
  205. package/templates/apps/api-governance/tsconfig.json +5 -0
  206. package/templates/apps/api-kv/README.md +100 -0
  207. package/templates/apps/api-kv/actions/sync.pull.action.server.ts +75 -0
  208. package/templates/apps/api-kv/actions/sync.pull.action.ts +25 -0
  209. package/templates/apps/api-kv/actions/sync.reset.action.server.ts +25 -0
  210. package/templates/apps/api-kv/actions/sync.reset.action.ts +22 -0
  211. package/templates/apps/api-kv/actions/sync.status.action.server.ts +28 -0
  212. package/templates/apps/api-kv/actions/sync.status.action.ts +17 -0
  213. package/templates/apps/api-kv/app.config.ts +48 -0
  214. package/templates/apps/api-kv/database/schema.ts +62 -0
  215. package/templates/apps/api-kv/package.json +28 -0
  216. package/templates/apps/api-kv/queries/events.list.query.server.ts +14 -0
  217. package/templates/apps/api-kv/queries/events.list.query.ts +23 -0
  218. package/templates/apps/api-kv/template.json +6 -0
  219. package/templates/apps/api-kv/tests/sync.test.ts +103 -0
  220. package/templates/apps/api-kv/tsconfig.json +5 -0
  221. package/templates/apps/api-moderation/README.md +46 -0
  222. package/templates/apps/api-moderation/app.config.ts +33 -0
  223. package/templates/apps/api-moderation/database/schema.ts +45 -0
  224. package/templates/apps/api-moderation/mutations/comments.create.mutation.server.ts +8 -0
  225. package/templates/apps/api-moderation/mutations/comments.create.mutation.ts +20 -0
  226. package/templates/apps/api-moderation/mutations/posts.create.mutation.server.ts +9 -0
  227. package/templates/apps/api-moderation/mutations/posts.create.mutation.ts +25 -0
  228. package/templates/apps/api-moderation/package.json +29 -0
  229. package/templates/apps/api-moderation/template.json +6 -0
  230. package/templates/apps/api-moderation/tests/posts.create.test.ts +79 -0
  231. package/templates/apps/api-moderation/tsconfig.json +5 -0
  232. package/templates/apps/api-observability/README.md +106 -0
  233. package/templates/apps/api-observability/app.config.ts +32 -0
  234. package/templates/apps/api-observability/database/schema.ts +38 -0
  235. package/templates/apps/api-observability/mutations/notes.create.mutation.server.ts +33 -0
  236. package/templates/apps/api-observability/mutations/notes.create.mutation.ts +15 -0
  237. package/templates/apps/api-observability/package.json +29 -0
  238. package/templates/apps/api-observability/queries/notes.list.query.server.ts +7 -0
  239. package/templates/apps/api-observability/queries/notes.list.query.ts +14 -0
  240. package/templates/apps/api-observability/template.json +6 -0
  241. package/templates/apps/api-observability/tests/notes.create.test.ts +36 -0
  242. package/templates/apps/api-observability/tsconfig.json +5 -0
  243. package/templates/apps/api-ratelimit/README.md +49 -0
  244. package/templates/apps/api-ratelimit/app.config.ts +42 -0
  245. package/templates/apps/api-ratelimit/database/schema.ts +37 -0
  246. package/templates/apps/api-ratelimit/mutations/notes.create.mutation.server.ts +16 -0
  247. package/templates/apps/api-ratelimit/mutations/notes.create.mutation.ts +34 -0
  248. package/templates/apps/api-ratelimit/package.json +29 -0
  249. package/templates/apps/api-ratelimit/template.json +6 -0
  250. package/templates/apps/api-ratelimit/tests/notes.create.test.ts +69 -0
  251. package/templates/apps/api-ratelimit/tsconfig.json +5 -0
  252. package/templates/apps/api-rbac/README.md +59 -0
  253. package/templates/apps/api-rbac/app.config.ts +48 -0
  254. package/templates/apps/api-rbac/database/schema.ts +37 -0
  255. package/templates/apps/api-rbac/mutations/notes.create.mutation.server.ts +22 -0
  256. package/templates/apps/api-rbac/mutations/notes.create.mutation.ts +26 -0
  257. package/templates/apps/api-rbac/package.json +29 -0
  258. package/templates/apps/api-rbac/template.json +6 -0
  259. package/templates/apps/api-rbac/tests/notes.create.test.ts +80 -0
  260. package/templates/apps/api-rbac/tsconfig.json +5 -0
  261. package/templates/apps/api-rest/README.md +85 -0
  262. package/templates/apps/api-rest/app.config.ts +70 -0
  263. package/templates/apps/api-rest/database/schema.ts +52 -0
  264. package/templates/apps/api-rest/lib/product.ts +30 -0
  265. package/templates/apps/api-rest/package.json +27 -0
  266. package/templates/apps/api-rest/routes/v1/products.create.route.tsx +45 -0
  267. package/templates/apps/api-rest/routes/v1/products.delete.route.tsx +25 -0
  268. package/templates/apps/api-rest/routes/v1/products.get.route.tsx +29 -0
  269. package/templates/apps/api-rest/routes/v1/products.list.route.tsx +43 -0
  270. package/templates/apps/api-rest/template.json +6 -0
  271. package/templates/apps/api-rest/tests/products.create.test.ts +53 -0
  272. package/templates/apps/api-rest/tsconfig.json +5 -0
  273. package/templates/apps/api-saas/README.md +106 -0
  274. package/templates/apps/api-saas/app.config.ts +51 -0
  275. package/templates/apps/api-saas/database/schema.ts +50 -0
  276. package/templates/apps/api-saas/mutations/projects.create.mutation.server.ts +52 -0
  277. package/templates/apps/api-saas/mutations/projects.create.mutation.ts +18 -0
  278. package/templates/apps/api-saas/package.json +31 -0
  279. package/templates/apps/api-saas/queries/projects.list.query.server.ts +7 -0
  280. package/templates/apps/api-saas/queries/projects.list.query.ts +18 -0
  281. package/templates/apps/api-saas/template.json +6 -0
  282. package/templates/apps/api-saas/tests/projects.create.test.ts +47 -0
  283. package/templates/apps/api-saas/tsconfig.json +5 -0
  284. package/templates/apps/api-search/README.md +68 -0
  285. package/templates/apps/api-search/app.config.ts +31 -0
  286. package/templates/apps/api-search/database/schema.ts +47 -0
  287. package/templates/apps/api-search/lib/search.ts +27 -0
  288. package/templates/apps/api-search/mutations/articles.create.mutation.server.ts +19 -0
  289. package/templates/apps/api-search/mutations/articles.create.mutation.ts +35 -0
  290. package/templates/apps/api-search/package.json +29 -0
  291. package/templates/apps/api-search/queries/articles.list.query.server.ts +14 -0
  292. package/templates/apps/api-search/queries/articles.list.query.ts +19 -0
  293. package/templates/apps/api-search/seeds/articles.seed.ts +41 -0
  294. package/templates/apps/api-search/startup/searchBackfill.startup.tsx +24 -0
  295. package/templates/apps/api-search/template.json +6 -0
  296. package/templates/apps/api-search/tests/articles.create.test.ts +69 -0
  297. package/templates/apps/api-search/tsconfig.json +5 -0
  298. package/templates/apps/api-versioning/README.md +51 -0
  299. package/templates/apps/api-versioning/actions/documents.asOf.action.server.ts +13 -0
  300. package/templates/apps/api-versioning/actions/documents.asOf.action.ts +14 -0
  301. package/templates/apps/api-versioning/actions/documents.history.action.server.ts +15 -0
  302. package/templates/apps/api-versioning/actions/documents.history.action.ts +17 -0
  303. package/templates/apps/api-versioning/app.config.ts +24 -0
  304. package/templates/apps/api-versioning/database/schema.ts +38 -0
  305. package/templates/apps/api-versioning/mutations/documents.create.mutation.server.ts +8 -0
  306. package/templates/apps/api-versioning/mutations/documents.create.mutation.ts +21 -0
  307. package/templates/apps/api-versioning/mutations/documents.update.mutation.server.ts +9 -0
  308. package/templates/apps/api-versioning/mutations/documents.update.mutation.ts +21 -0
  309. package/templates/apps/api-versioning/package.json +29 -0
  310. package/templates/apps/api-versioning/template.json +6 -0
  311. package/templates/apps/api-versioning/tests/documents.create.test.ts +37 -0
  312. package/templates/apps/api-versioning/tsconfig.json +5 -0
  313. package/templates/apps/api-webhooks/.env +6 -0
  314. package/templates/apps/api-webhooks/README.md +106 -0
  315. package/templates/apps/api-webhooks/app.config.ts +16 -0
  316. package/templates/apps/api-webhooks/database/schema.ts +49 -0
  317. package/templates/apps/api-webhooks/events/order.completed.webhook.tsx +22 -0
  318. package/templates/apps/api-webhooks/mutations/orders.fulfill.mutation.server.ts +37 -0
  319. package/templates/apps/api-webhooks/mutations/orders.fulfill.mutation.ts +15 -0
  320. package/templates/apps/api-webhooks/package.json +28 -0
  321. package/templates/apps/api-webhooks/queries/orders.list.query.server.ts +7 -0
  322. package/templates/apps/api-webhooks/queries/orders.list.query.ts +15 -0
  323. package/templates/apps/api-webhooks/template.json +6 -0
  324. package/templates/apps/api-webhooks/tests/orders.fulfill.test.ts +51 -0
  325. package/templates/apps/api-webhooks/tsconfig.json +5 -0
  326. package/templates/apps/api-webhooks/webhooks/orders.webhook.tsx +35 -0
  327. package/templates/apps/changelog/README.md +77 -0
  328. package/templates/apps/changelog/app.config.ts +27 -0
  329. package/templates/apps/changelog/content/releases/0.1.0.mdx +19 -0
  330. package/templates/apps/changelog/content/releases/0.2.0.mdx +28 -0
  331. package/templates/apps/changelog/package.json +30 -0
  332. package/templates/apps/changelog/scripts/generate-rss.mjs +38 -0
  333. package/templates/apps/changelog/src/globals.css +66 -0
  334. package/templates/apps/changelog/src/globals.d.ts +16 -0
  335. package/templates/apps/changelog/src/lib/locale.test.ts +72 -0
  336. package/templates/apps/changelog/src/lib/locale.ts +55 -0
  337. package/templates/apps/changelog/src/lib/releases.ts +21 -0
  338. package/templates/apps/changelog/src/locales/de.ts +22 -0
  339. package/templates/apps/changelog/src/locales/en.ts +30 -0
  340. package/templates/apps/changelog/src/pages/[locale]/[slug].tsx +24 -0
  341. package/templates/apps/changelog/src/pages/[locale]/index.tsx +13 -0
  342. package/templates/apps/changelog/src/pages/[locale]/mirrors.test.tsx +95 -0
  343. package/templates/apps/changelog/src/pages/[slug].test.tsx +127 -0
  344. package/templates/apps/changelog/src/pages/[slug].tsx +52 -0
  345. package/templates/apps/changelog/src/pages/index.test.tsx +112 -0
  346. package/templates/apps/changelog/src/pages/index.tsx +55 -0
  347. package/templates/apps/changelog/src/pages/layout.tsx +84 -0
  348. package/templates/apps/changelog/template.json +6 -0
  349. package/templates/apps/changelog/tsconfig.json +11 -0
  350. package/templates/apps/edge-functions/README.md +55 -0
  351. package/templates/apps/edge-functions/functions/aiComplete.serverless.ts +58 -0
  352. package/templates/apps/edge-functions/functions/currencyConvert.serverless.ts +47 -0
  353. package/templates/apps/edge-functions/functions/geoGreeting.serverless.ts +44 -0
  354. package/templates/apps/edge-functions/functions/health.serverless.ts +29 -0
  355. package/templates/apps/edge-functions/functions/resolveLink.serverless.ts +32 -0
  356. package/templates/apps/edge-functions/functions/shareLink.serverless.ts +30 -0
  357. package/templates/apps/edge-functions/functions/slackNotify.serverless.ts +40 -0
  358. package/templates/apps/edge-functions/functions/verifySignature.serverless.ts +52 -0
  359. package/templates/apps/edge-functions/package.json +21 -0
  360. package/templates/apps/edge-functions/template.json +6 -0
  361. package/templates/apps/edge-functions/tsconfig.json +10 -0
  362. package/templates/apps/frontend-admin/README.md +40 -0
  363. package/templates/apps/frontend-admin/app.config.ts +37 -0
  364. package/templates/apps/frontend-admin/package.json +30 -0
  365. package/templates/apps/frontend-admin/src/config.ts +8 -0
  366. package/templates/apps/frontend-admin/src/globals.css +76 -0
  367. package/templates/apps/frontend-admin/src/globals.d.ts +6 -0
  368. package/templates/apps/frontend-admin/src/lib/admin.ts +16 -0
  369. package/templates/apps/frontend-admin/src/lib/auth.ts +24 -0
  370. package/templates/apps/frontend-admin/src/locales/de.ts +67 -0
  371. package/templates/apps/frontend-admin/src/locales/en.ts +79 -0
  372. package/templates/apps/frontend-admin/src/locales/index.ts +15 -0
  373. package/templates/apps/frontend-admin/src/pages/(marketing)/index.test.tsx +55 -0
  374. package/templates/apps/frontend-admin/src/pages/(marketing)/index.tsx +32 -0
  375. package/templates/apps/frontend-admin/src/pages/(marketing)/layout.tsx +29 -0
  376. package/templates/apps/frontend-admin/src/pages/(marketing)/login.test.tsx +73 -0
  377. package/templates/apps/frontend-admin/src/pages/(marketing)/login.tsx +35 -0
  378. package/templates/apps/frontend-admin/src/pages/admin/[entity].tsx +121 -0
  379. package/templates/apps/frontend-admin/src/pages/admin/entity.test.tsx +119 -0
  380. package/templates/apps/frontend-admin/src/pages/admin/error.tsx +20 -0
  381. package/templates/apps/frontend-admin/src/pages/admin/fallbacks.test.tsx +68 -0
  382. package/templates/apps/frontend-admin/src/pages/admin/index.test.tsx +88 -0
  383. package/templates/apps/frontend-admin/src/pages/admin/index.tsx +65 -0
  384. package/templates/apps/frontend-admin/src/pages/admin/layout.test.tsx +114 -0
  385. package/templates/apps/frontend-admin/src/pages/admin/layout.tsx +97 -0
  386. package/templates/apps/frontend-admin/src/pages/admin/loading.tsx +15 -0
  387. package/templates/apps/frontend-admin/src/pages/admin/not-found.tsx +15 -0
  388. package/templates/apps/frontend-admin/src/pages/layout.tsx +12 -0
  389. package/templates/apps/frontend-admin/src/pages/layouts.test.tsx +54 -0
  390. package/templates/apps/frontend-admin/template.json +6 -0
  391. package/templates/apps/frontend-admin/tsconfig.json +11 -0
  392. package/templates/apps/frontend-app/README.md +98 -0
  393. package/templates/apps/frontend-app/app.config.ts +43 -0
  394. package/templates/apps/frontend-app/package.json +30 -0
  395. package/templates/apps/frontend-app/src/locales/de.ts +36 -0
  396. package/templates/apps/frontend-app/src/locales/en.ts +43 -0
  397. package/templates/apps/frontend-app/src/locales/index.ts +15 -0
  398. package/templates/apps/frontend-app/src/pages/index.test.tsx +167 -0
  399. package/templates/apps/frontend-app/src/pages/index.tsx +136 -0
  400. package/templates/apps/frontend-app/src/pages/layout.tsx +41 -0
  401. package/templates/apps/frontend-app/src/pages/schema-ui.test.tsx +99 -0
  402. package/templates/apps/frontend-app/src/pages/schema-ui.tsx +74 -0
  403. package/templates/apps/frontend-app/template.json +6 -0
  404. package/templates/apps/frontend-app/tsconfig.json +11 -0
  405. package/templates/apps/frontend-blank/README.md +18 -0
  406. package/templates/apps/frontend-blank/app.config.ts +29 -0
  407. package/templates/apps/frontend-blank/package.json +29 -0
  408. package/templates/apps/frontend-blank/src/locales/de.ts +15 -0
  409. package/templates/apps/frontend-blank/src/locales/en.ts +22 -0
  410. package/templates/apps/frontend-blank/src/locales/index.ts +15 -0
  411. package/templates/apps/frontend-blank/src/pages/index.test.tsx +55 -0
  412. package/templates/apps/frontend-blank/src/pages/index.tsx +27 -0
  413. package/templates/apps/frontend-blank/src/pages/layout.test.tsx +54 -0
  414. package/templates/apps/frontend-blank/src/pages/layout.tsx +35 -0
  415. package/templates/apps/frontend-blank/template.json +6 -0
  416. package/templates/apps/frontend-blank/tsconfig.json +11 -0
  417. package/templates/apps/frontend-contact/README.md +65 -0
  418. package/templates/apps/frontend-contact/app.config.ts +25 -0
  419. package/templates/apps/frontend-contact/functions/sendMessage.serverless.ts +69 -0
  420. package/templates/apps/frontend-contact/package.json +33 -0
  421. package/templates/apps/frontend-contact/src/components/ContactForm.island.test.tsx +142 -0
  422. package/templates/apps/frontend-contact/src/components/ContactForm.island.tsx +104 -0
  423. package/templates/apps/frontend-contact/src/config.ts +12 -0
  424. package/templates/apps/frontend-contact/src/globals.css +84 -0
  425. package/templates/apps/frontend-contact/src/lib/locale.ts +55 -0
  426. package/templates/apps/frontend-contact/src/locales/de.ts +26 -0
  427. package/templates/apps/frontend-contact/src/locales/en.ts +29 -0
  428. package/templates/apps/frontend-contact/src/pages/[locale]/index.tsx +14 -0
  429. package/templates/apps/frontend-contact/src/pages/index.test.tsx +70 -0
  430. package/templates/apps/frontend-contact/src/pages/index.tsx +63 -0
  431. package/templates/apps/frontend-contact/src/pages/layout.tsx +65 -0
  432. package/templates/apps/frontend-contact/template.json +6 -0
  433. package/templates/apps/frontend-contact/tsconfig.json +11 -0
  434. package/templates/apps/frontend-dashboard/README.md +54 -0
  435. package/templates/apps/frontend-dashboard/app.config.ts +39 -0
  436. package/templates/apps/frontend-dashboard/package.json +29 -0
  437. package/templates/apps/frontend-dashboard/src/config.ts +8 -0
  438. package/templates/apps/frontend-dashboard/src/globals.css +72 -0
  439. package/templates/apps/frontend-dashboard/src/globals.d.ts +6 -0
  440. package/templates/apps/frontend-dashboard/src/lib/auth.ts +27 -0
  441. package/templates/apps/frontend-dashboard/src/locales/de.ts +49 -0
  442. package/templates/apps/frontend-dashboard/src/locales/en.ts +60 -0
  443. package/templates/apps/frontend-dashboard/src/locales/index.ts +15 -0
  444. package/templates/apps/frontend-dashboard/src/pages/(marketing)/index.test.tsx +55 -0
  445. package/templates/apps/frontend-dashboard/src/pages/(marketing)/index.tsx +34 -0
  446. package/templates/apps/frontend-dashboard/src/pages/(marketing)/layout.tsx +32 -0
  447. package/templates/apps/frontend-dashboard/src/pages/(marketing)/login.test.tsx +74 -0
  448. package/templates/apps/frontend-dashboard/src/pages/(marketing)/login.tsx +40 -0
  449. package/templates/apps/frontend-dashboard/src/pages/dashboard/error.tsx +21 -0
  450. package/templates/apps/frontend-dashboard/src/pages/dashboard/fallbacks.test.tsx +68 -0
  451. package/templates/apps/frontend-dashboard/src/pages/dashboard/index.test.tsx +55 -0
  452. package/templates/apps/frontend-dashboard/src/pages/dashboard/index.tsx +38 -0
  453. package/templates/apps/frontend-dashboard/src/pages/dashboard/layout.test.tsx +88 -0
  454. package/templates/apps/frontend-dashboard/src/pages/dashboard/layout.tsx +69 -0
  455. package/templates/apps/frontend-dashboard/src/pages/dashboard/loading.tsx +21 -0
  456. package/templates/apps/frontend-dashboard/src/pages/dashboard/not-found.tsx +19 -0
  457. package/templates/apps/frontend-dashboard/src/pages/dashboard/settings.test.tsx +69 -0
  458. package/templates/apps/frontend-dashboard/src/pages/dashboard/settings.tsx +35 -0
  459. package/templates/apps/frontend-dashboard/src/pages/layout.tsx +12 -0
  460. package/templates/apps/frontend-dashboard/src/pages/layouts.test.tsx +54 -0
  461. package/templates/apps/frontend-dashboard/template.json +6 -0
  462. package/templates/apps/frontend-dashboard/tsconfig.json +11 -0
  463. package/templates/apps/frontend-docs/README.md +19 -0
  464. package/templates/apps/frontend-docs/app.config.ts +25 -0
  465. package/templates/apps/frontend-docs/package.json +29 -0
  466. package/templates/apps/frontend-docs/src/globals.css +32 -0
  467. package/templates/apps/frontend-docs/src/lib/locale.test.ts +72 -0
  468. package/templates/apps/frontend-docs/src/lib/locale.ts +56 -0
  469. package/templates/apps/frontend-docs/src/locales/de.ts +24 -0
  470. package/templates/apps/frontend-docs/src/locales/en.ts +28 -0
  471. package/templates/apps/frontend-docs/src/pages/[locale]/docs/[...slug].tsx +18 -0
  472. package/templates/apps/frontend-docs/src/pages/[locale]/index.tsx +13 -0
  473. package/templates/apps/frontend-docs/src/pages/[locale]/mirrors.test.tsx +67 -0
  474. package/templates/apps/frontend-docs/src/pages/docs/[...slug].test.tsx +115 -0
  475. package/templates/apps/frontend-docs/src/pages/docs/[...slug].tsx +82 -0
  476. package/templates/apps/frontend-docs/src/pages/index.test.tsx +90 -0
  477. package/templates/apps/frontend-docs/src/pages/index.tsx +45 -0
  478. package/templates/apps/frontend-docs/src/pages/layout.test.tsx +84 -0
  479. package/templates/apps/frontend-docs/src/pages/layout.tsx +66 -0
  480. package/templates/apps/frontend-docs/template.json +6 -0
  481. package/templates/apps/frontend-docs/tsconfig.json +11 -0
  482. package/templates/apps/frontend-i18n/README.md +61 -0
  483. package/templates/apps/frontend-i18n/app.config.ts +33 -0
  484. package/templates/apps/frontend-i18n/package.json +28 -0
  485. package/templates/apps/frontend-i18n/src/globals.css +46 -0
  486. package/templates/apps/frontend-i18n/src/globals.d.ts +6 -0
  487. package/templates/apps/frontend-i18n/src/lib/locale.test.ts +72 -0
  488. package/templates/apps/frontend-i18n/src/lib/locale.ts +55 -0
  489. package/templates/apps/frontend-i18n/src/locales/de.ts +24 -0
  490. package/templates/apps/frontend-i18n/src/locales/en.ts +25 -0
  491. package/templates/apps/frontend-i18n/src/pages/[locale]/about.tsx +11 -0
  492. package/templates/apps/frontend-i18n/src/pages/[locale]/index.tsx +13 -0
  493. package/templates/apps/frontend-i18n/src/pages/[locale]/mirrors.test.tsx +50 -0
  494. package/templates/apps/frontend-i18n/src/pages/about.test.tsx +75 -0
  495. package/templates/apps/frontend-i18n/src/pages/about.tsx +31 -0
  496. package/templates/apps/frontend-i18n/src/pages/index.test.tsx +102 -0
  497. package/templates/apps/frontend-i18n/src/pages/index.tsx +43 -0
  498. package/templates/apps/frontend-i18n/src/pages/layout.test.tsx +86 -0
  499. package/templates/apps/frontend-i18n/src/pages/layout.tsx +70 -0
  500. package/templates/apps/frontend-i18n/template.json +6 -0
  501. package/templates/apps/frontend-i18n/tsconfig.json +11 -0
  502. package/templates/apps/frontend-landing/README.md +17 -0
  503. package/templates/apps/frontend-landing/app.config.ts +25 -0
  504. package/templates/apps/frontend-landing/package.json +29 -0
  505. package/templates/apps/frontend-landing/src/globals.css +23 -0
  506. package/templates/apps/frontend-landing/src/lib/locale.test.ts +72 -0
  507. package/templates/apps/frontend-landing/src/lib/locale.ts +55 -0
  508. package/templates/apps/frontend-landing/src/locales/de.ts +24 -0
  509. package/templates/apps/frontend-landing/src/locales/en.ts +26 -0
  510. package/templates/apps/frontend-landing/src/pages/[locale]/index.tsx +13 -0
  511. package/templates/apps/frontend-landing/src/pages/[locale]/mirrors.test.tsx +37 -0
  512. package/templates/apps/frontend-landing/src/pages/index.test.tsx +110 -0
  513. package/templates/apps/frontend-landing/src/pages/index.tsx +75 -0
  514. package/templates/apps/frontend-landing/src/pages/layout.test.tsx +84 -0
  515. package/templates/apps/frontend-landing/src/pages/layout.tsx +66 -0
  516. package/templates/apps/frontend-landing/template.json +6 -0
  517. package/templates/apps/frontend-landing/tsconfig.json +11 -0
  518. package/templates/apps/frontend-spa/README.md +45 -0
  519. package/templates/apps/frontend-spa/app.config.ts +27 -0
  520. package/templates/apps/frontend-spa/package.json +29 -0
  521. package/templates/apps/frontend-spa/src/globals.css +84 -0
  522. package/templates/apps/frontend-spa/src/locales/de.ts +22 -0
  523. package/templates/apps/frontend-spa/src/locales/en.ts +29 -0
  524. package/templates/apps/frontend-spa/src/locales/index.ts +15 -0
  525. package/templates/apps/frontend-spa/src/pages/index.test.tsx +137 -0
  526. package/templates/apps/frontend-spa/src/pages/index.tsx +123 -0
  527. package/templates/apps/frontend-spa/src/pages/layout.tsx +27 -0
  528. package/templates/apps/frontend-spa/template.json +6 -0
  529. package/templates/apps/frontend-spa/tsconfig.json +11 -0
  530. package/templates/apps/frontend-ssr/README.md +68 -0
  531. package/templates/apps/frontend-ssr/app.config.ts +32 -0
  532. package/templates/apps/frontend-ssr/package.json +29 -0
  533. package/templates/apps/frontend-ssr/src/globals.css +67 -0
  534. package/templates/apps/frontend-ssr/src/locales/de.ts +41 -0
  535. package/templates/apps/frontend-ssr/src/locales/en.ts +54 -0
  536. package/templates/apps/frontend-ssr/src/locales/index.ts +16 -0
  537. package/templates/apps/frontend-ssr/src/pages/feed-swr.test.tsx +69 -0
  538. package/templates/apps/frontend-ssr/src/pages/feed-swr.tsx +54 -0
  539. package/templates/apps/frontend-ssr/src/pages/feed.test.tsx +73 -0
  540. package/templates/apps/frontend-ssr/src/pages/feed.tsx +64 -0
  541. package/templates/apps/frontend-ssr/src/pages/index.test.tsx +89 -0
  542. package/templates/apps/frontend-ssr/src/pages/index.tsx +72 -0
  543. package/templates/apps/frontend-ssr/src/pages/layout.tsx +37 -0
  544. package/templates/apps/frontend-ssr/template.json +6 -0
  545. package/templates/apps/frontend-ssr/tsconfig.json +11 -0
  546. package/templates/apps/frontend-ssr-api/README.md +50 -0
  547. package/templates/apps/frontend-ssr-api/app.config.ts +43 -0
  548. package/templates/apps/frontend-ssr-api/package.json +30 -0
  549. package/templates/apps/frontend-ssr-api/src/globals.css +38 -0
  550. package/templates/apps/frontend-ssr-api/src/globals.d.ts +6 -0
  551. package/templates/apps/frontend-ssr-api/src/locales/de.ts +20 -0
  552. package/templates/apps/frontend-ssr-api/src/locales/en.ts +31 -0
  553. package/templates/apps/frontend-ssr-api/src/locales/index.ts +16 -0
  554. package/templates/apps/frontend-ssr-api/src/pages/index.test.tsx +105 -0
  555. package/templates/apps/frontend-ssr-api/src/pages/index.tsx +83 -0
  556. package/templates/apps/frontend-ssr-api/src/pages/layout.tsx +28 -0
  557. package/templates/apps/frontend-ssr-api/template.json +6 -0
  558. package/templates/apps/frontend-ssr-api/tsconfig.json +11 -0
  559. package/templates/apps/frontend-static-blog/README.md +49 -0
  560. package/templates/apps/frontend-static-blog/app.config.ts +34 -0
  561. package/templates/apps/frontend-static-blog/package.json +28 -0
  562. package/templates/apps/frontend-static-blog/src/components/ReadingProgress.island.test.tsx +65 -0
  563. package/templates/apps/frontend-static-blog/src/components/ReadingProgress.island.tsx +35 -0
  564. package/templates/apps/frontend-static-blog/src/content/posts.ts +64 -0
  565. package/templates/apps/frontend-static-blog/src/globals.css +75 -0
  566. package/templates/apps/frontend-static-blog/src/lib/locale.test.ts +72 -0
  567. package/templates/apps/frontend-static-blog/src/lib/locale.ts +55 -0
  568. package/templates/apps/frontend-static-blog/src/locales/de.ts +19 -0
  569. package/templates/apps/frontend-static-blog/src/locales/en.ts +26 -0
  570. package/templates/apps/frontend-static-blog/src/pages/[locale]/blog/[slug].tsx +20 -0
  571. package/templates/apps/frontend-static-blog/src/pages/[locale]/index.tsx +13 -0
  572. package/templates/apps/frontend-static-blog/src/pages/[locale]/mirrors.test.tsx +62 -0
  573. package/templates/apps/frontend-static-blog/src/pages/blog/[slug].test.tsx +116 -0
  574. package/templates/apps/frontend-static-blog/src/pages/blog/[slug].tsx +69 -0
  575. package/templates/apps/frontend-static-blog/src/pages/index.test.tsx +100 -0
  576. package/templates/apps/frontend-static-blog/src/pages/index.tsx +58 -0
  577. package/templates/apps/frontend-static-blog/src/pages/layout.tsx +63 -0
  578. package/templates/apps/frontend-static-blog/template.json +6 -0
  579. package/templates/apps/frontend-static-blog/tsconfig.json +11 -0
  580. package/templates/baselines/bare/.env.example +37 -0
  581. package/templates/baselines/bare/README.md +47 -0
  582. package/templates/baselines/bare/baseline.json +31 -0
  583. package/templates/baselines/bare/deploy/README.md +43 -0
  584. package/templates/baselines/bare/deploy/voltro.service.example +36 -0
  585. package/templates/baselines/compose/.env.example +46 -0
  586. package/templates/baselines/compose/README.md +69 -0
  587. package/templates/baselines/compose/baseline.json +51 -0
  588. package/templates/baselines/compose/docker/.dockerignore +38 -0
  589. package/templates/baselines/compose/docker/api.Dockerfile +57 -0
  590. package/templates/baselines/compose/docker/dev.Dockerfile +35 -0
  591. package/templates/baselines/compose/docker/web.Dockerfile +59 -0
  592. package/templates/baselines/compose/docker-compose.dev.yml +89 -0
  593. package/templates/baselines/compose/docker-compose.prod.yml +87 -0
  594. package/templates/baselines/compose/docker-compose.yml +41 -0
  595. package/templates/baselines/compose-mariadb/.env.example +57 -0
  596. package/templates/baselines/compose-mariadb/README.md +78 -0
  597. package/templates/baselines/compose-mariadb/baseline.json +51 -0
  598. package/templates/baselines/compose-mariadb/docker/.dockerignore +38 -0
  599. package/templates/baselines/compose-mariadb/docker/api.Dockerfile +57 -0
  600. package/templates/baselines/compose-mariadb/docker/dev.Dockerfile +35 -0
  601. package/templates/baselines/compose-mariadb/docker/mariadb-init.sql +6 -0
  602. package/templates/baselines/compose-mariadb/docker/web.Dockerfile +59 -0
  603. package/templates/baselines/compose-mariadb/docker-compose.dev.yml +117 -0
  604. package/templates/baselines/compose-mariadb/docker-compose.prod.yml +114 -0
  605. package/templates/baselines/compose-mariadb/docker-compose.yml +79 -0
  606. package/templates/baselines/helm/.env.example +39 -0
  607. package/templates/baselines/helm/README.md +98 -0
  608. package/templates/baselines/helm/baseline.json +53 -0
  609. package/templates/baselines/helm/charts/voltro-app/.helmignore +10 -0
  610. package/templates/baselines/helm/charts/voltro-app/Chart.yaml +10 -0
  611. package/templates/baselines/helm/charts/voltro-app/templates/_helpers.tpl +36 -0
  612. package/templates/baselines/helm/charts/voltro-app/templates/configmap.yaml +13 -0
  613. package/templates/baselines/helm/charts/voltro-app/templates/deployment-api.yaml +120 -0
  614. package/templates/baselines/helm/charts/voltro-app/templates/deployment-web.yaml +45 -0
  615. package/templates/baselines/helm/charts/voltro-app/templates/ingress.yaml +37 -0
  616. package/templates/baselines/helm/charts/voltro-app/templates/postgres-service.yaml +19 -0
  617. package/templates/baselines/helm/charts/voltro-app/templates/postgres-statefulset.yaml +73 -0
  618. package/templates/baselines/helm/charts/voltro-app/templates/secret.yaml +33 -0
  619. package/templates/baselines/helm/charts/voltro-app/templates/service-api.yaml +19 -0
  620. package/templates/baselines/helm/charts/voltro-app/templates/service-web.yaml +19 -0
  621. package/templates/baselines/helm/charts/voltro-app/values-dev.yaml +21 -0
  622. package/templates/baselines/helm/charts/voltro-app/values-prod.yaml +58 -0
  623. package/templates/baselines/helm/charts/voltro-app/values-staging.yaml +25 -0
  624. package/templates/baselines/helm/charts/voltro-app/values.yaml +109 -0
  625. package/templates/baselines/helm/deploy/README.md +94 -0
  626. package/templates/patches/@effect__cluster@0.59.0.patch +262 -0
@@ -0,0 +1,1650 @@
1
+ # CLI
2
+
3
+ > The voltro CLI — every command, grouped by purpose, with the flags that actually matter.
4
+
5
+
6
+
7
+ ---
8
+
9
+ <!-- source: en/cli/overview.md -->
10
+ ## Overview
11
+
12
+ _The voltro CLI — every command, grouped by purpose, with the flags that actually matter._
13
+
14
+ The Voltro CLI is the single entry point for scaffolding, dev, build, and run. It also seeds the framework agent guide into every project — written under BOTH `AGENTS.md` and `CLAUDE.md` — so AI coding agents have your framework conventions on tap.
15
+
16
+ ## Command quick-reference
17
+
18
+ The dispatcher routes `voltro <command> [args]` to the matching subcommand and passes the rest through. The full set:
19
+
20
+ | Group | Commands |
21
+ |---|---|
22
+ | [Scaffolding](/docs/cli/scaffolding) | `create-project`, `add-app`, `list-templates` |
23
+ | Packages | `package` (`create` / `publishable` / `private` / `status`), `create-package` |
24
+ | [Dev](/docs/cli/dev) | `dev`, `codegen`, `agents-md` |
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`) |
27
+ | [Database](/docs/cli/migrate) | `migrate`, `db` (`plan` / `apply` / `plans` / `drift` / `squash` / `restore-snapshot` / `migrate` / `rollback` / `status` / `seed`) |
28
+ | [Data transfer](/docs/cli/data) | `data` (`export` / `import` / `unpack` / `inspect` / `backup` / `restore`) — directory + single-file `.vbundle` bundles, streaming assets, masking, at-rest encryption |
29
+ | Ops / infra | `cache` (`status` / `flush` / `invalidate`), `add` (`redis`), `baseline` (`list` / `status` / `set`), `schedule-manifest`, [`storage`](/docs/plugins/storage) (`doctor` / `cors`) |
30
+ | 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 |
31
+ | [Inspect & debug](/docs/cli/inspect) | `inspect`, `logs`, `traces`, `workflows`, `cluster`, `check` |
32
+ | Harness | `test`, `e2e` |
33
+ | Cloud | `cloud` (`login` / `whoami` / `projects` / `env` / `import`) |
34
+ | Secrets | `secret` (`generate [purpose]` — the right var+format per secret; `generate` alone → a generic secret; `list`) |
35
+ | Meta | `agents-md`, `version`, `help` |
36
+
37
+ Each command takes an optional path argument (the app directory) — defaults to `.` when run from inside an app. `voltro init` scaffolds a project (it delegates to the `create-project` scaffolder, forwarding `--api`/`--web`/`--baseline`/`--cache`). `voltro secret generate [purpose]` prints a cryptographically strong secret — with a purpose (`data-transfer`, `session`, `bundle-key`, `field-encryption`, `storage`, `inspect`) it emits the correct env var + length/format as a paste-ready `NAME=value` (`voltro secret list` shows them all); with no purpose, a generic base64url secret. `voltro telemetry` reports that Voltro collects none. `voltro deploy` shows the deploy paths — self-host via a [baseline](/docs/deployment/baselines) + CI, managed via the control-plane client `voltro cloud` (managed cloud deploy is coming soon — see [Voltro Cloud](/docs/deployment/voltro-cloud)), individual [serverless functions](/docs/deployment/serverless-functions) (`voltro serverless` → self-hosted Node, or Cloudflare / Scaleway), or a [static site](/docs/deployment/static-sites) (`voltro static` → Cloudflare Pages / S3 / Netlify).
38
+
39
+ ## Help + version
40
+
41
+ `voltro help` prints the command list; `voltro version` prints the CLI version. These are subcommands, not global flags — there is no global `--version` / `--help` parsing in the dispatcher.
42
+
43
+ ## Common per-command flags
44
+
45
+ There is no universal global-flag layer; flags are per-command. The ones that recur:
46
+
47
+ | Flag | Where it applies |
48
+ |---|---|
49
+ | `--format pretty\|json` | `logs`, `traces`, `inspect`, `cluster`, `workflows` — machine-readable output. |
50
+ | `--no-color` | `logs`, `traces` — strip ANSI codes when piping to a file. |
51
+ | `--process <name>` | `logs`, `traces`, `inspect`, `cluster` — narrow to one running process. |
52
+ | `--force` | `agents-md` — overwrite existing files. |
53
+
54
+ Commands like `build` / `start` / `migrate` / `codegen` parse no flags at all — only an optional path. Check each command's page for its real surface.
55
+
56
+ ## Common env vars
57
+
58
+ The CLI reads:
59
+
60
+ | Var | Effect |
61
+ |---|---|
62
+ | `NODE_ENV` | `production` / `development` / `test`. Affects defaults across many commands. |
63
+ | `DB_DIALECT` | `postgres` *(default)* / `mysql` / `mariadb` / `mssql` / `sqlite` / `turso` / `memory`. Picks the SQL backend. |
64
+ | `STORE` | `memory` / `postgres` — alternate data-store selector (resolution order: `DB_DIALECT` → `STORE` → `app.config.ts`). |
65
+ | `WATCH` | `0` / `1`. Toggle filesystem watch in `voltro dev`. |
66
+ | `VOLTRO_DASHBOARD` | `off` to disable the auto-launched dashboard. |
67
+ | `VOLTRO_LOG_FORMAT` | `pretty` / `json`. Force the logger's output format. |
68
+ | `VOLTRO_LOG_LEVEL` | `trace` / `debug` / `info` / `warn` / `error` / `fatal`. |
69
+ | `VOLTRO_INSPECT` | `off` to disable the `/_voltro/inspect/*` HTTP endpoints. |
70
+ | `VOLTRO_INSPECT_TOKEN` | Bearer token for guarded inspect endpoints. |
71
+ | `DB_URL` | Database connection string (falls back to `DB_PRIMARY_URL`; or the discrete `DB_*` / `PG_*` fields). |
72
+ | `VOLTRO_SESSION_SECRET` | Session-cookie signing secret (`@voltro/plugin-auth`). Rotate with zero downtime: move the old value to `VOLTRO_SESSION_SECRET_PREVIOUS` for one session lifetime — cookies signed with either secret keep verifying, and previous-key cookies are re-issued under the new one. |
73
+ | `VOLTRO_DATA_TRANSFER_SECRET` | Gates the prod data-transfer endpoints (`POST /_voltro/admin/{export,import}`); ≥16 chars or the routes don't mount. |
74
+ | `VOLTRO_BUNDLE_KEY` | Passphrase for `.vbundle` export encryption (`voltro data export --encrypt`). A DEDICATED key, not the transfer secret. |
75
+ | `VOLTRO_FIELD_ENCRYPTION_KEY` | Key for `.encrypted()` columns (`governancePlugin({ fieldEncryption: true })`) — a passphrase or a raw 64-hex AES-256 key. |
76
+ | `VOLTRO_STORAGE_SECRET` | Signs storage grant tokens (private files); falls back to the session secret if unset. |
77
+ | `AI_PROVIDER` / `AI_MODEL` / `AI_API_KEY` | Per-provider AI config. |
78
+
79
+ Generate any of the secret vars above with `voltro secret generate <purpose>` (see [`secret`](#command-quick-reference)) — it picks the right length and format. A lower environment's secrets must always differ from production's.
80
+
81
+ ## Workflow patterns
82
+
83
+ ### "I'm starting a new project"
84
+
85
+ ```bash
86
+ pnpx voltro create-project acme --api api-backend --web frontend-landing
87
+ cd acme
88
+ pnpm install
89
+ pnpm dev # turbo runs voltro dev across every app in the project
90
+ ```
91
+
92
+ ### "I want to add a docs site to my existing project"
93
+
94
+ ```bash
95
+ voltro add-app docs --template frontend-docs --to acme
96
+ pnpm install # picks up the new app's deps
97
+ pnpm dev # turbo adds the new app to its parallel boot
98
+ ```
99
+
100
+ ### "I changed my schema and want to apply it"
101
+
102
+ ```bash
103
+ voltro db plan # diff declared schema vs live, color-coded
104
+ # review the plan
105
+ voltro db apply # execute it (dev)
106
+ # or, for the flagless idempotent apply (CI / ops):
107
+ voltro migrate
108
+ ```
109
+
110
+ ### "I want a clean rebuild"
111
+
112
+ ```bash
113
+ voltro codegen # rewrite the generated rpc group + .framework/*
114
+ voltro build # vite build + SSG pre-render
115
+ voltro start # production server
116
+ ```
117
+
118
+ ### "Something's wrong — inspect what's running"
119
+
120
+ ```bash
121
+ voltro inspect rpc # discovered procedures + workflows
122
+ voltro inspect routes # web page tree (web apps)
123
+ voltro inspect metrics # request rates, p95 latencies
124
+ voltro logs --tail 100 # recent structured logs
125
+ voltro traces --errors # traces with an errored span
126
+ voltro workflows list # recent workflow runs
127
+ ```
128
+
129
+ ### "Did my edit break a binding?"
130
+
131
+ `voltro check` runs blast-radius checks against the **running** api's live
132
+ capability manifest — dangling `source` / `target` tables, unknown RBAC scopes,
133
+ broken route bindings, orphan tables — at edit time instead of at runtime
134
+ (needs `voltro dev` up). `--json` emits LLM-shaped diagnostics so a coding
135
+ agent can fix-and-repeat; `--diff` previews the blast radius of a removal
136
+ BEFORE you apply it.
137
+
138
+ ```bash
139
+ voltro check # blast-radius checks vs the live manifest
140
+ voltro check --json # { ok, diagnostics: [{ rule, node, breaks, fix }] }
141
+ voltro check --diff removeTable:todos # what a proposed removal would break, before applying
142
+ ```
143
+
144
+ 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).
145
+
146
+ ## Where to read next
147
+
148
+ - [Scaffolding](/docs/cli/scaffolding) — start + grow a project
149
+ - [Dev](/docs/cli/dev) — what happens during `voltro dev`
150
+ - [Build & start](/docs/cli/build-and-start) — production paths
151
+ - [Migrate](/docs/cli/migrate) — schema changes end-to-end
152
+ - [Inspect & test](/docs/cli/inspect) — debugging + harness
153
+
154
+
155
+
156
+ ---
157
+
158
+ <!-- source: en/cli/scaffolding.md -->
159
+ ## Scaffolding
160
+
161
+ _create-project, add-app, list-templates — boot new code with the framework's conventions baked in._
162
+
163
+ The scaffolder generates new projects + new apps from templates. Each template is a dogfooded reference; what you scaffold is the same shape the Voltro Cloud team uses.
164
+
165
+ ## `create-project`
166
+
167
+ ```bash
168
+ voltro create-project <name> [flags]
169
+ ```
170
+
171
+ Bootstraps a new project under `apps/<name>/` with selected templates.
172
+
173
+ | Flag | Default | Notes |
174
+ |---|---|---|
175
+ | `--api <templateId>` | prompts, then `api-backend` | API template. Use `none` for web-only projects. Also accepts `--api=<id>`. |
176
+ | `--web <templateId>` | prompts, then `frontend-blank` | Web template. Use `none` for api-only. Also accepts `--web=<id>`. |
177
+ | `--cache=redis` | off | Wire the Redis cache backend at scaffold time — sets `cache: 'redis'` in `app.config.ts` and injects the `redis` service + `CACHE_*` env into the active baseline's infra. |
178
+ | `--baseline=<bare\|compose\|helm>` | prompts (or skip) | Deploy baseline to scaffold (`bare` / `compose` / `helm`). Without it, the interactive prompt lists the available ids. |
179
+ | `--port-range <start>-<end>` | `5190-5199` | Port range for web apps in this project. Persisted in `project.json`. Also accepts `:` / `..` separators. |
180
+ | `--no-input` | false | Skip prompts; suitable for CI / scripted scaffolding. |
181
+
182
+ What it does:
183
+
184
+ 1. Validates the name (camelCase or kebab-case, no `_`, no leading digits).
185
+ 2. Picks the next free port from `--port-range`.
186
+ 3. Renders templates into `apps/<name>/api/` + `apps/<name>/web/`, substituting `{{appName}}`, `{{projectName}}`, `{{port}}` placeholders.
187
+ 4. Writes `apps/<name>/project.json` recording the project's port range + app list.
188
+ 5. Updates `pnpm-workspace.yaml` to include the new project's apps.
189
+ 6. Seeds the **agent guide** for each app (see below).
190
+
191
+ After scaffolding:
192
+
193
+ ```bash
194
+ cd <repo-root>
195
+ pnpm install
196
+ pnpm dev
197
+ ```
198
+
199
+ ### The seeded agent guide
200
+
201
+ `create-project` / `add-app` (and `voltro dev` on first boot) seed a guide that
202
+ teaches AI coding agents the framework's conventions. It's generated, not a
203
+ monolith:
204
+
205
+ - **Root `AGENTS.md` + `CLAUDE.md`** — a slim always-loaded core (mental model,
206
+ the primitive rubric, file conventions, the browser/server boundary, naming,
207
+ anti-patterns) plus an **index** listing the workspace's installed plugins and
208
+ linking the deep, on-demand topic docs.
209
+ - **Nested `AGENTS.md` + `CLAUDE.md` per app area** (`api/`, `api/database/`,
210
+ `web/`) — type-specific notes an agent loads only when working there; a
211
+ scaffolded app's file points at its template's doc.
212
+ - **`.claude/skills/*`** — Claude Code skills for the common how-tos.
213
+
214
+ Existing files are never overwritten; `voltro agents-md --force` refreshes them.
215
+ Protect a hand-maintained file from `--force` with a `voltro:agents-md:keep`
216
+ HTML comment at the top.
217
+
218
+ ## `add-app`
219
+
220
+ ```bash
221
+ voltro add-app <appName> --template <templateId> [--to <projectName>]
222
+ ```
223
+
224
+ Adds another app to an existing project. Works for any kind — `api`, `web`, or
225
+ `serverless`. A `web` app gets the next free port from the project's range; a
226
+ `serverless` app has no server, so it gets no port (run it with `voltro
227
+ serverless`).
228
+
229
+ | Flag | Default | Notes |
230
+ |---|---|---|
231
+ | `--template <templateId>` | *(required)* | Template to scaffold. `voltro list-templates` for the catalogue. |
232
+ | `--to <projectName>` | auto-detected | Target project. Required when >1 project exists. |
233
+
234
+ What it does:
235
+
236
+ 1. Reads the target project's `project.json` for the port range.
237
+ 2. Picks the next free port from the range (rejects if the range is exhausted).
238
+ 3. Renders the template into `apps/<project>/<appName>/`.
239
+ 4. Updates `project.json` to record the new app.
240
+
241
+ Example:
242
+
243
+ ```bash
244
+ voltro add-app docs --template frontend-docs --to acme
245
+ voltro add-app admin --template frontend-blank --to acme
246
+ ```
247
+
248
+ ## `list-templates`
249
+
250
+ ```bash
251
+ voltro list-templates
252
+ ```
253
+
254
+ Prints the catalogue. Templates come in three **kinds** — `api`, `web`, and
255
+ `serverless` (a bundle of [`*.serverless.ts`](/docs/deployment/serverless-functions)
256
+ functions deployed on their own):
257
+
258
+ ```
259
+ id kind summary
260
+ -------------------- ---------- --------------------------------------------
261
+ api-backend api Minimal Voltro backend (schema + query + mutation).
262
+ api-backend-mail api + @voltro/plugin-mail + a React-Email template.
263
+ api-backend-storage api + @voltro/plugin-storage (public + private objects).
264
+ api-backend-mariadb api MariaDB binlog CDC + storage, tenant-aware.
265
+ api-durable api Durable + reactive: workflow, trigger, cron, subscriber, aggregate.
266
+ api-ai api RAG agent: vectorEmbedding + search tool + model loop.
267
+ api-data-advanced api Advanced schema: relations, FTS, dbEnum, encrypted, caching.
268
+ api-auth api Real user auth: plugin-auth sessions + cookie strategy.
269
+ api-rest api Public REST API: defineRestRoute + plugin-openapi (Swagger).
270
+ api-saas api SaaS bundle: billing + notifications + analytics + presence.
271
+ api-observability api Metrics + errors + tracing + a @voltro/testing unit test.
272
+ api-webhooks api First-class webhooks: signature-verified incoming + outgoing emit.
273
+ frontend-blank web Empty React + layout shell.
274
+ frontend-app web Fullstack reactive loop — wired to an api (useSubscription + useMutation).
275
+ frontend-landing web Static marketing page — zero JS on the wire.
276
+ frontend-static-blog web SSG blog: getStaticPaths + islands + per-post meta.
277
+ frontend-spa web Pure client-rendered SPA (no backend, no SSR).
278
+ frontend-ssr web Server-rendered pages: ssr + isr (revalidate, swr).
279
+ frontend-contact web Static page + a serverless email form.
280
+ frontend-docs web Catch-all docs site with i18n.
281
+ changelog web Release-notes site (MDX + RSS).
282
+ edge-functions serverless A library of *.serverless.ts functions (8 types).
283
+ ```
284
+
285
+ See the [App templates catalogue](/docs/reference/templates) for what each
286
+ demonstrates + a picks-for table. For programmatic use, add `--json`:
287
+
288
+ ```bash
289
+ voltro list-templates --json
290
+ ```
291
+
292
+ ## `generate` (AI app-builder)
293
+
294
+ `voltro generate "<prompt>"` turns a natural-language prompt into framework
295
+ artifacts — queries, mutations, tables — **constrained to what your app can
296
+ actually express**. It reads the committed capability manifest
297
+ (`app.manifest.generated.json`, emitted by `voltro dev`) as the grammar, asks the
298
+ model for an app graph + the files that realise it, and gates every candidate
299
+ through the real `voltro check` before anything is written. A structurally-invalid
300
+ proposal is re-prompted with its diagnostics (the errors-as-LLM-API loop), never
301
+ written.
302
+
303
+ ```bash
304
+ voltro generate "add a comments table with a list + create" # dry-run: prints the proposal
305
+ voltro generate "add a comments table with a list + create" --write # applies the accepted artifacts
306
+ ```
307
+
308
+ **Dry-run by default** — artifacts hit disk only with `--write`, and only ever a
309
+ proposal that passed `voltro check`. Generation is also capped (file count + total
310
+ bytes). Run `voltro dev` once first so the manifest exists.
311
+
312
+ > Requires a model provider (`AI_PROVIDER` / `AI_MODEL` + the provider key, same
313
+ > as [agents](/docs/ai/agents)). The cloud dashboard exposes the same builder for
314
+ > proposal review (`apps.generateAppGraph`), behind the `aiBuilder` flag (off by
315
+ > default → typed `FlagDisabled`).
316
+
317
+ ## Template tokens
318
+
319
+ Templates contain `{{token}}` placeholders that the scaffolder substitutes:
320
+
321
+ | Token | Example value |
322
+ |---|---|
323
+ | `{{appName}}` | `dashboard` |
324
+ | `{{capAppName}}` | `Dashboard` |
325
+ | `{{projectName}}` | `acme` |
326
+ | `{{capProjectName}}` | `Acme` |
327
+ | `{{port}}` | `5191` |
328
+
329
+ Apply across both file content AND file/directory names — a template file named `{{appName}}.entity.ts` or a dir `pages/{{appNameSnake}}/` renders to your project's names. When writing your own template, use these freely; the substitution is global.
330
+
331
+ ## Writing a custom template
332
+
333
+ Drop a directory into `voltro-templates/apps/<your-id>/`:
334
+
335
+ ```
336
+ voltro-templates/apps/my-template/
337
+ ├── template.json
338
+ ├── package.json
339
+ ├── app.config.ts
340
+ ├── src/
341
+ │ ├── pages/
342
+ │ │ └── index.tsx
343
+ │ └── …
344
+ └── tsconfig.json
345
+ ```
346
+
347
+ `template.json` declares the metadata:
348
+
349
+ ```json
350
+ {
351
+ "id": "my-template",
352
+ "kind": "web",
353
+ "summary": "My custom template — does X.",
354
+ "tags": ["marketing", "minimal"]
355
+ }
356
+ ```
357
+
358
+ `kind` is one of `api` / `web` / `serverless`. A `serverless` template ships a
359
+ `functions/` dir of `*.serverless.ts` files (no `app.config.ts`, no pages) and
360
+ is added with `voltro add-app`. Now `voltro list-templates` shows it; `voltro
361
+ create-project --web my-template` (or `--api`) uses it, and any kind is added
362
+ with `voltro add-app <name> --template my-template`.
363
+
364
+ For private templates (in your own repo), set `VOLTRO_TEMPLATES_PATH=/path/to/my/templates` and the CLI walks there instead of the default location.
365
+
366
+ ## Idempotency
367
+
368
+ The scaffolder refuses to overwrite an existing directory:
369
+
370
+ ```bash
371
+ voltro create-project acme # fails if apps/acme/ exists
372
+ ```
373
+
374
+ To force, delete the directory first. The framework intentionally doesn't have a `--force` flag — accidental data loss is the kind of thing that happens once + ruins your day.
375
+
376
+ ## Anti-patterns
377
+
378
+ - **Scaffolding into a non-Voltro repo.** The scaffolder writes to `apps/<project>/` + expects a `pnpm-workspace.yaml`. Without one, `pnpm install` doesn't link workspace packages.
379
+ - **Renaming the scaffolded app directory afterwards.** `project.json` records the path; rename invalidates discovery. Either re-scaffold with the right name, or update `project.json` + every cross-package import by hand.
380
+ - **Editing the templates dir directly to "fix" a scaffolded app.** Templates are starting points. After scaffolding, the app is yours — edit IT, not the template (unless the template itself has a bug).
381
+
382
+ ## See also
383
+
384
+ - [App templates](/docs/reference/templates) — the catalogue with picks-for table
385
+ - [Dev](/docs/cli/dev) — what `voltro dev` does with the scaffolded app
386
+
387
+
388
+
389
+ ---
390
+
391
+ <!-- source: en/cli/dev.md -->
392
+ ## Dev
393
+
394
+ _voltro dev, codegen, agents-md — what runs during local development and the env flags that shape it._
395
+
396
+ `voltro dev` is the day-to-day command. It runs different machinery for api vs web apps but the contract is the same: edit a file, the right thing happens.
397
+
398
+ ## `voltro dev <appDir>`
399
+
400
+ ```bash
401
+ voltro dev . # current dir
402
+ voltro dev apps/acme/api # explicit path
403
+ ```
404
+
405
+ ### What it does for an api app
406
+
407
+ 1. Reads `app.config.ts`. Bails if not `type: 'api'`.
408
+ 2. Walks `queries/`, `mutations/`, `workflows/`, etc. for the discovery patterns.
409
+ 3. Generates `.framework/rpcGroup.generated.ts` exporting a typed client.
410
+ 4. Starts the RPC over WebSocket server on `:4000` (override with `PORT`, or set `port:` in `app.config.ts`).
411
+ 5. Watches every discovery-matching file. On save:
412
+ - File added/removed → regen the discovery → restart the api process.
413
+ - File modified → reload the module → fire `hmr update` to connected clients.
414
+ 6. Resolves `STORE=memory` (in-memory store) or `STORE=postgres` (real DB).
415
+ 7. Auto-launches the inspect dashboard on `:5179` (unless `VOLTRO_DASHBOARD=off`).
416
+
417
+ ### What it does for a web app
418
+
419
+ 1. Reads `app.config.ts`. Bails if not `type: 'web'`.
420
+ 2. Walks `src/pages/` for queries + special files.
421
+ 3. Generates `.framework/main.tsx`, `.framework/app.tsx`, `index.html`.
422
+ 4. Starts Vite with the framework's plugin chain (React, Tailwind v4, page discovery, inspect, dashboard registry).
423
+ 5. Binds to the configured port from `app.config.ts.port` (strict — fails on conflict).
424
+ 6. Watches `src/`. On save, Vite HMR fires:
425
+ - TSX changes → React Refresh updates the live component.
426
+ - CSS changes → swap stylesheets in place.
427
+ - New page file → regen `.framework/app.tsx` → reload the query tree.
428
+
429
+ ### The in-page devtools overlay
430
+
431
+ The generated web entry auto-mounts the `@voltro/devtools` overlay — a floating button that expands into live panels (subscriptions, mutations, indexes, webhooks, traces, routes, runtimes, logs; **Alt+V** toggles it). The component AND its stylesheet load dynamically under `import.meta.env.DEV` only; production builds strip the import entirely, so it needs zero code and ships zero bytes to prod. Opt out per app in `app.config.ts`:
432
+
433
+ ```ts
434
+ // app.config.ts (web app)
435
+ export default {
436
+ type: 'web' as const,
437
+ name: 'web',
438
+ disableDevtools: true, // no overlay import, no mount, no stylesheet
439
+ }
440
+ ```
441
+
442
+ #### Inspect token (token-gated deploys)
443
+
444
+ The overlay's webhooks / traces / indexes panels poll each api's `/_voltro/inspect/*` endpoints. Under `voltro dev` that surface is open, so no auth is needed. A token-gated deploy (`voltro start` with `VOLTRO_INSPECT_TOKEN` set) requires the same `Authorization: Bearer <token>` the CLI sends — otherwise the panels 401 to their empty state.
445
+
446
+ The overlay reads that token from `VITE_VOLTRO_INSPECT_TOKEN` (only `VITE_`-prefixed vars reach the browser bundle). A foreign-host mount can also pass it explicitly:
447
+
448
+ ```tsx
449
+ import { VoltroDevtools } from '@voltro/devtools'
450
+
451
+ <VoltroDevtools inspectToken={import.meta.env.VITE_VOLTRO_INSPECT_TOKEN} />
452
+ ```
453
+
454
+ When neither the prop nor the env var is set, no `Authorization` header is sent — local dev is unaffected. (The indexes panel's live SSE stream can't carry a header; a token-gated deploy falls back to token-carrying HTTP polling for that panel.)
455
+
456
+ #### Overriding the overlay's labels
457
+
458
+ Every user-facing label the overlay renders (tab labels, empty states, the FAB tooltip, …) routes through an overridable strings seam — the same pattern as `@voltro/ui`'s `UiStringsProvider`. Localize or rebrand by passing `strings` (deep-merged onto the English defaults — supply only what you change):
459
+
460
+ ```tsx
461
+ import { VoltroDevtools } from '@voltro/devtools'
462
+
463
+ <VoltroDevtools
464
+ strings={{
465
+ tabs: { subscriptions: 'Abos', logs: 'Protokolle' },
466
+ shell: { openLabel: 'Voltro Devtools öffnen' },
467
+ }}
468
+ />
469
+ ```
470
+
471
+ A `<DevtoolsStringsProvider strings={…}>` mounted above the overlay works too; nested providers compose.
472
+
473
+ ### Common flags + env vars
474
+
475
+ | Var / flag | Notes |
476
+ |---|---|
477
+ | `STORE=memory` | In-memory data store (default). Restart = state gone. |
478
+ | `STORE=postgres` | Real Postgres via `DB_URL` (or the discrete `DB_*` / `PG_*` fields). Survives restarts. `DB_DIALECT` picks the SQL backend. |
479
+ | `WATCH=0` | Disable filesystem watch. Useful under a parent watcher (Docker volume, devcontainer). |
480
+ | `VOLTRO_DASHBOARD=off` | Don't auto-launch the dashboard. |
481
+ | `VOLTRO_INSPECT=off` | Don't expose `/_voltro/inspect/*` endpoints. |
482
+ | `PORT=4001` | Override the listen port (api or web). Both kinds read `PORT`, then `app.config.ts` `port:`, then the default. |
483
+ | `VOLTRO_DASHBOARD_PORT=5180` | Override the auto-launched dashboard port (default `5179`). |
484
+ | `VOLTRO_LOG_LEVEL=debug` | Verbose framework logs. |
485
+
486
+ ## Multi-app dev (turbo)
487
+
488
+ The `pnpm dev` at the project root delegates to `turbo run dev` which boots every app in parallel:
489
+
490
+ ```bash
491
+ pnpm dev # ↳ turbo runs `voltro dev` for every workspace app
492
+ ```
493
+
494
+ Turbo's TUI mode shows one pane per app with live output. Set `"ui": "tui"` in `turbo.json` (the scaffolder does this for you).
495
+
496
+ ## `voltro codegen <appDir>`
497
+
498
+ ```bash
499
+ voltro codegen apps/acme/api # regenerate the codegen for one app
500
+ voltro codegen . # current dir
501
+ ```
502
+
503
+ Regenerates `rpcGroup.generated.ts` (+ the web `.framework/*` entry) from the discovered descriptors. It takes only an optional app-directory path — no flags. You rarely need this; `voltro dev` does it on every save. Useful for:
504
+
505
+ - **CI environments** where you want the typed client baked into a tarball before tests run.
506
+ - **Editor LSP confused** after a discovery pattern changed and the generated file went out of sync.
507
+
508
+ ## `voltro agents-md`
509
+
510
+ ```bash
511
+ voltro agents-md # seed AGENTS.md if it doesn't exist
512
+ voltro agents-md --force # overwrite existing file
513
+ ```
514
+
515
+ Seeds the framework agent guide into the repo root under **both** filenames — `AGENTS.md` (the universal convention) and `CLAUDE.md` (project-pinned Claude Code setups) — written atomically from one template so they can't drift. The file teaches AI coding agents (Claude Code, Cursor, GitHub Copilot Chat) Voltro's conventions — file suffixes, schema DSL, query shape, layout contract, anti-patterns.
516
+
517
+ When the framework's template gains new sections (new file convention, plugin shape change), run `voltro agents-md --force` to pull them in — `--force` overwrites BOTH files. The CLI doesn't auto-overwrite on boot, so apps that customised their guide keep their changes until they ask for a refresh.
518
+
519
+ ## File watch internals
520
+
521
+ For api apps, Voltro applies its own discovery walker on every save. The patterns that trigger a re-discovery (and the matching `.server.ts` executors):
522
+
523
+ - `**/*.query.ts`, `**/*.mutation.ts`, `**/*.action.ts`, `**/*.stream.ts` (+ their `.server.ts` siblings)
524
+ - `**/*.workflow.tsx`, `**/*.trigger.tsx`
525
+ - `**/*.cron.tsx`
526
+ - `**/*.webhook.tsx`
527
+ - `**/*.subscribe.ts`
528
+ - `**/*.aggregate.ts`
529
+ - `**/*.agent.tsx`
530
+ - `**/*.email.tsx`
531
+ - `**/*.seed.ts`, `**/*.startup.tsx`
532
+ - `**/*.entity.ts`, `**/*.schema.ts`, `schema.ts`
533
+ - `app.config.ts`
534
+
535
+ `*.tool.tsx` files are not discovered on their own — a tool is imported by the agent that uses it, so it's picked up through the agent file. Hidden dirs, `node_modules`, `dist`, and `.framework` are skipped.
536
+
537
+ For web apps, Vite's built-in HMR handles the watch.
538
+
539
+ ## Restart triggers
540
+
541
+ API apps restart (full process kill) on a change to **any source file**
542
+ under the api project dir — every `.ts` / `.tsx` / `.mts` / `.cts` /
543
+ `.js` / `.jsx` / `.mjs` / `.cjs` / `.json`, excluding `*.generated.*`
544
+ (the codegen rewrites those every boot, so watching them would
545
+ self-respawn forever). Concretely that includes:
546
+
547
+ - `app.config.ts` / `package.json` change
548
+ - A primitive descriptor / executor, or a new/removed primitive file
549
+ - **A shared `lib/` / `services/` helper** that a descriptor or executor
550
+ imports — editing one respawns the api, because the whole module graph
551
+ is re-imported on restart (it is NOT enough to watch only the
552
+ convention files)
553
+ - A change to any `.env` / `.env.local` file the process loaded at boot
554
+
555
+ The restart is a full re-exec — there is no in-process hot-reload of a
556
+ handler body; editing a query's executor respawns the child (debounced
557
+ 80ms, so a burst of saves collapses into one restart).
558
+
559
+ The `.env` trigger applies to **both api and web** apps — `process.env` is
560
+ parsed once at boot, so editing a `.env` (or re-pulling secrets, e.g.
561
+ `doppler secrets download > .env`) needs a full re-exec. `voltro dev` watches
562
+ the env-file chain (app dir → ancestors) and hard-restarts, logging
563
+ `.env changed (<file>) — hard-restarting…`. The api releases its port and the
564
+ web closes Vite before the replacement spawns, so the restart can't hit a
565
+ port-in-use race.
566
+
567
+ ## Multiple instances on one machine
568
+
569
+ Run two api apps + two web apps in parallel? Every app reads the same `PORT` env, so prefer setting each app's `port:` in its own `app.config.ts` and `--cwd`-ing into each — that avoids one shared `PORT` clobbering them all. The dashboard auto-launches once on `:5179`; later instances see it's already up and skip it.
570
+
571
+ ```bash
572
+ voltro dev apps/acme/api & # port from apps/acme/api/app.config.ts
573
+ voltro dev apps/orbit/api & # port from apps/orbit/api/app.config.ts
574
+ voltro dev apps/acme/web &
575
+ voltro dev apps/acme/docs &
576
+ voltro dev apps/orbit/web &
577
+ ```
578
+
579
+ If you must override per-process from the shell, set `PORT` inline on each one (`PORT=4001 voltro dev apps/acme/api`) — but the config-file port is the cleaner path. `pnpm dev` at the repo root handles all of this for you via turbo.
580
+
581
+ ## Anti-patterns
582
+
583
+ - **Running `voltro dev` against a `voltro start` build directory.** The dev server expects source files; pointing it at `dist/` confuses it. Use `voltro start` for that.
584
+ - **Ignoring `app.config.ts` changes.** They require a process restart (the discovery walker re-reads them at boot only). Save, watch the process die + come back up.
585
+ - **Expecting a `.env` edit to hot-reload.** It can't — `process.env` is read once at boot. `voltro dev` hard-restarts the server on a `.env` change (you'll see `.env changed … — hard-restarting`); wait for the process to come back before testing, rather than assuming the new value is already live.
586
+ - **Disabling the dashboard "to save resources".** It's a few MB of RAM + the inspect endpoints fail gracefully. Keep it on; it's the best debugging tool you have.
587
+
588
+
589
+
590
+ ---
591
+
592
+ <!-- source: en/cli/build-and-start.md -->
593
+ ## Build & start
594
+
595
+ _voltro build and voltro start — production builds, SSG pre-render, the SSR bundle, ISR cache._
596
+
597
+ `voltro build` produces a production artefact; `voltro start` serves it. Two commands, clean separation.
598
+
599
+ ## `voltro build <appDir>`
600
+
601
+ ```bash
602
+ voltro build apps/acme/web
603
+ voltro build . # current dir
604
+ ```
605
+
606
+ What it does for a web app:
607
+
608
+ 1. **vite build** against `.framework/` — produces `dist/` with chunked client bundle.
609
+ 2. **Pre-renders static pages** — every page with `renderMode: 'static'` is rendered to HTML once + lands at `dist/<path>/index.html`.
610
+ 3. **Pre-builds the SSR bundle** — every page module compiled to `dist/server/ssrEntry.js` so `voltro start` doesn't need a Vite middleware loader at runtime.
611
+ 4. **Copies `public/`** into `dist/`.
612
+
613
+ For api apps, `voltro build` bundles the discovery-walked sources + dependencies into a single Node ESM entry at `dist/api.js`. Production servers run it via `node dist/api.js`.
614
+
615
+ `voltro build` takes a single optional app directory and parses no flags — the SSR bundle is always attempted, and the SSG pre-render always runs for `static`-mode pages.
616
+
617
+ ### Output layout
618
+
619
+ ```
620
+ apps/acme/web/.framework/dist/
621
+ ├── index.html # SPA shell fallback
622
+ ├── about/index.html # pre-rendered static page
623
+ ├── blog/first-post/index.html # SSG via getStaticPaths
624
+ ├── assets/
625
+ │ ├── index-abc.js # main client bundle
626
+ │ ├── index-abc.css
627
+ │ └── island-LikeButton-def.js # per-island chunks (interactive: 'islands' pages)
628
+ └── server/
629
+ └── ssrEntry.js # SSR bundle for voltro start
630
+ ```
631
+
632
+ ## `voltro start <appDir>`
633
+
634
+ ```bash
635
+ voltro start apps/acme/web # serves the build output
636
+ PORT=8080 voltro start apps/acme/web
637
+ ```
638
+
639
+ What it does:
640
+
641
+ 1. Reads `app.config.ts.port` (or `PORT` env var) for the listen port.
642
+ 2. Walks `dist/` to discover pre-rendered HTML files.
643
+ 3. Loads the SSR bundle from `dist/server/ssrEntry.js`. Falls back to Vite middleware mode if absent.
644
+ 4. Starts an `http.Server` that:
645
+ - Serves pre-rendered HTML for matched URLs.
646
+ - Serves static assets from `dist/assets/`, `dist/_voltro/`.
647
+ - Renders SSR pages per request via the SSR bundle.
648
+ - Reads / writes ISR cache for `renderMode: 'isr'` pages.
649
+
650
+ ### Flags + env vars
651
+
652
+ | Flag / env | Notes |
653
+ |---|---|
654
+ | `PORT=8080` | Override the listen port. |
655
+ | `SSR_CACHE=postgres` | Use the Postgres-backed ISR cache. Requires the `PG_*` connection env (`PG_HOST` etc.) to also be set — without it, `voltro start` stays on the in-memory cache. Default is in-memory. |
656
+ | `VOLTRO_INSPECT=off` | Disable the inspect HTTP endpoints in production. |
657
+ | `VOLTRO_INSPECT_TOKEN=…` | Bearer token guard on the inspect endpoints. |
658
+
659
+ ### Per-request routing logic
660
+
661
+ For a request to `/foo`:
662
+
663
+ ```text
664
+ 1. dist/foo/index.html exists? → serve it.
665
+ 2. URL matches a static asset? → serve from disk.
666
+ 3. URL matches a registered query?
667
+ - renderMode 'ssr' → render fresh via SSR bundle.
668
+ - renderMode 'isr' →
669
+ - cache HIT (fresh) → serve cached.
670
+ - cache HIT (stale) + staleWhileRevalidate → serve cached + bg refresh.
671
+ - cache MISS → render, store, serve.
672
+ - renderMode 'static' (no pre-render found) → serve SPA shell.
673
+ 4. None of the above → 404 via not-found.tsx.
674
+ ```
675
+
676
+ The response includes a `x-voltro-rendered-by` header (`prerender` / `ssr` / `isr`) + cache state.
677
+
678
+ ## ISR cache backends
679
+
680
+ ```bash
681
+ SSR_CACHE=memory voltro start # default — per-process, doesn't survive restart
682
+ # Postgres-backed cache — needs the PG_* connection env too:
683
+ SSR_CACHE=postgres PG_HOST=… PG_PORT=… PG_USER=… PG_PASSWORD=… PG_DATABASE=… voltro start
684
+ ```
685
+
686
+ For multi-instance + horizontal scale → Postgres. The cache table is auto-created on first boot.
687
+
688
+ ## Tenant-aware ISR
689
+
690
+ Pages with `tenantAware: true` get separate cache entries per tenant. The cache key becomes `<pathname>|tenant=<tenantId>`. See [Render modes](/docs/routing/render-modes).
691
+
692
+ ## CDC invalidation
693
+
694
+ For pages with `cacheInvalidatesOn: ['table', …]`, `voltro start` reads Postgres logical replication. Writes to listed tables invalidate every matching cache entry. Requires `SSR_CACHE=postgres` + `wal_level=logical`.
695
+
696
+ ## Graceful shutdown
697
+
698
+ `voltro start` handles SIGTERM:
699
+
700
+ 1. Stop accepting new connections.
701
+ 2. Wait up to `SHUTDOWN_GRACE_MS` (default 30s) for in-flight requests to finish.
702
+ 3. Close active WebSocket connections (the client auto-reconnects).
703
+ 4. Exit.
704
+
705
+ For container orchestrators, set `terminationGracePeriodSeconds: 60` to match.
706
+
707
+ ## Health checks
708
+
709
+ `voltro start` exposes two unauthenticated probe routes:
710
+
711
+ - `GET /internal/liveness` → `200 ok` — the process is up at all (restart the pod if it stops answering).
712
+ - `GET /internal/readiness` → `200 ready` once boot completes, `503 not-ready` before (pulls the pod from Service endpoints until ready).
713
+
714
+ Point the Kubernetes liveness probe at `/internal/liveness` and the readiness probe at `/internal/readiness`.
715
+
716
+ ## Multi-instance + sticky sessions
717
+
718
+ For WebSocket connections to land on the same backend (required for in-process subscription state):
719
+
720
+ - Reverse proxy: `lb_policy ip_hash` (Caddy) / `ip_hash` (nginx).
721
+ - Or use `@voltro/plugin-cluster` to share subscription state across instances → any-load-balancer-works.
722
+
723
+ ## Anti-patterns
724
+
725
+ - **Running `voltro start` against a directory without `dist/`.** It exits 1 with a clear `no built dist found — run voltro build first` (checked against `.framework/dist/index.html` before any heavy work). Run `voltro build` first.
726
+ - **`voltro start` in dev to "test prod".** Use `voltro build && voltro start`. The dev server has different behaviour; serving dev artefacts via start is undefined.
727
+ - **Skipping the SSR bundle build.** Middleware mode is slower (cold-start cost on every render). For production deploys with `renderMode: 'ssr'` pages, build the bundle.
728
+
729
+ ## See also
730
+
731
+ - [Render modes](/docs/routing/render-modes) — which mode produces which output
732
+ - [Voltro Cloud](/docs/deployment/voltro-cloud) — managed `voltro start` + autoscale (coming soon)
733
+ - [Self-hosting](/docs/deployment/self-hosting) — Docker + reverse proxy patterns
734
+
735
+
736
+
737
+ ---
738
+
739
+ <!-- source: en/cli/migrate.md -->
740
+ ## Migrate
741
+
742
+ _voltro migrate — the idempotent schema apply; voltro db plan/apply for the declarative diff workflow._
743
+
744
+ `voltro migrate` applies your declared schema (`*.entity.ts` / `*.schema.ts` / `schema.ts`) to the configured database. It's a **flagless, idempotent apply**: it walks the project for table descriptors, emits `CREATE TABLE IF NOT EXISTS` + `CREATE INDEX IF NOT EXISTS`, and runs the DDL in one transaction. Re-running is a no-op.
745
+
746
+ ```bash
747
+ voltro migrate # apply the discovered schema to the configured store
748
+ voltro migrate apps/api # explicit app directory (defaults to cwd)
749
+ ```
750
+
751
+ There are no `--plan` / `--dry` / `--reset` / `--advisory-lock` flags on `voltro migrate` — the only argument it reads is an optional path. The diff / plan / apply / drift / squash workflow lives under `voltro db` (see below).
752
+
753
+ For the deep dive on the schema DSL + day-to-day patterns, see [Database / Migrations](/docs/database/migrations).
754
+
755
+ ## How discovery works
756
+
757
+ The migrator walks the project root (skipping `node_modules`, `dist`, `.framework`, etc.) for:
758
+
759
+ - `*.entity.ts` / `*.schema.ts` / `schema.ts` — your table descriptors.
760
+ - Feature files (`*.workflow.tsx`, `*.cron.tsx`, `*.webhook.tsx`) — their presence decides which framework-internal `_voltro_*` tables get created (workflow-run tables, schedule ledgers, etc.). **`_voltro_traces` is the exception — env-gated, not feature-gated:** it's created under `voltro dev` (durable trace history on by default) but NOT under `voltro serve` / `voltro start` (prod default off → set `VOLTRO_TRACING_PERSIST=interesting` to opt in). See [Distributed tracing](/docs/observability/distributed-tracing).
761
+
762
+ The core `actors` table is injected automatically when you didn't declare your own, and `tenants` is registered when present, so the audit / soft-delete / tenant mixins resolve their FK targets.
763
+
764
+ ## `voltro dev` already auto-migrates
765
+
766
+ On a SQL-backed store, `voltro dev` AUTO-APPLIES the discovered schema before any handler boots — so a standalone `voltro migrate` is rarely needed in the inner loop. It's the CI / ops-pipeline entry point.
767
+
768
+ Opt out of the dev auto-migrate when you ship schema through a separate reviewed pipeline:
769
+
770
+ ```bash
771
+ VOLTRO_AUTO_MIGRATE=0 voltro dev .
772
+ ```
773
+
774
+ The boot log then says `auto-migrate: skipped (VOLTRO_AUTO_MIGRATE=0)`, and you run `voltro migrate` (or `voltro db apply`) explicitly.
775
+
776
+ ## Store + dialect selection
777
+
778
+ The dialect is resolved from `DB_DIALECT` (default `postgres`); the connection comes from `DB_URL` / `DB_PRIMARY_URL`, falling back to the discrete `DB_*` / `PG_*` fields (`DB_HOST` / `PG_HOST`, `DB_PORT` / `PG_PORT`, etc.).
779
+
780
+ ```bash
781
+ DB_DIALECT=postgres DB_URL=postgres://app:app@localhost:5432/app voltro migrate
782
+ ```
783
+
784
+ Migrating against `DB_DIALECT=memory` is rejected — there's nothing to migrate. In dev with the memory store, the in-memory store learns its shape from the schema declaration directly; switch to a SQL dialect (`postgres`, `sqlite`, `mysql`, `mariadb`, `mssql`, `turso`) to test the real migration path.
785
+
786
+ ## The declarative diff workflow — `voltro db`
787
+
788
+ The plan / apply / drift / squash machinery — diffing the declared schema against a live database, generating reviewable DDL, applying a pre-reviewed plan in production — lives under `voltro db`:
789
+
790
+ ```bash
791
+ voltro db plan # diff declared schema vs live, color-coded
792
+ voltro db plan --json # machine-readable for CI / PR comments
793
+ voltro db plan --sql # raw DDL preview
794
+ voltro db plan --against <url> # diff vs a REMOTE env via /_voltro/inspect/migrations
795
+ voltro db apply # execute the plan (dev only — refuses NODE_ENV=production)
796
+ voltro db apply --plan plan.json # prod: apply a pre-reviewed plan from CI/CD
797
+ voltro db plans [--limit 20] # plan history from _voltro_migration_plans
798
+ voltro db drift # alert if live diverged from the latest applied fingerprint
799
+ voltro db squash --before <iso-date> # consolidate history into one snapshot
800
+ voltro db restore-snapshot <plan-id> # restore soft-dropped columns from a plan
801
+ ```
802
+
803
+ A separate file-based migration surface (the offline escape hatch) lives alongside it:
804
+
805
+ ```bash
806
+ voltro db migrate # apply pending migration files
807
+ voltro db rollback [--to <id>] # undo migrations
808
+ voltro db status # list applied / pending migrations
809
+ voltro db seed # run boot-lifecycle seeds against the configured store
810
+ voltro db seed --id <name> # run one seed by id
811
+ voltro db seed --store memory # explicit override; default = the app's configured store
812
+ ```
813
+
814
+ `voltro db seed` defaults to the same store the app runs on (`DB_DIALECT` → `STORE` → `app.config.ts` `store:` → `postgres`) — seeding to memory was a silent data-loss footgun, so you opt into it explicitly.
815
+
816
+ ## Production safety
817
+
818
+ For production deploys:
819
+
820
+ 1. **Always apply schema BEFORE app version bumps.** App N+1 expects schema N+1; app N should still tolerate schema N+1.
821
+ 2. **Review the plan first.** `voltro db plan --json` in CI, apply the reviewed plan with `voltro db apply --plan plan.json`.
822
+ 3. **Check for drift.** `voltro db drift` flags a live database that diverged from the last applied fingerprint.
823
+ 4. **Verify backups before destructive changes.** Restoring is the actually-tested rollback path.
824
+
825
+ For zero-downtime deploys with breaking schema changes:
826
+
827
+ - Add new columns / tables → apply first, then deploy.
828
+ - Remove columns → deploy code that doesn't read them, then apply the drop.
829
+ - Rename → add new column + dual-write, deploy, drop old column later.
830
+
831
+ The framework doesn't enforce these — that's an SRE responsibility. See [Migrations](/docs/database/migrations) for the playbook.
832
+
833
+ ## See also
834
+
835
+ - [Database / Migrations](/docs/database/migrations) — the schema DSL deep dive
836
+ - [Self-hosting](/docs/deployment/self-hosting) — production migration patterns
837
+
838
+
839
+
840
+ ---
841
+
842
+ <!-- source: en/cli/inspect.md -->
843
+ ## Inspect & test
844
+
845
+ _The HTTP inspect surface, the dashboard, voltro logs / voltro traces, voltro test, voltro e2e — the debugging + harness tools._
846
+
847
+ When something's wrong, these are the tools. Live inspection of a running app happens over an HTTP surface (and the dashboard that consumes it), not a dedicated CLI verb. The shell-facing debugging commands are `voltro logs` and `voltro traces`; the harness commands are `voltro test` and `voltro e2e`.
848
+
849
+ ## `voltro inspect`
850
+
851
+ `voltro inspect <subcommand>` is the ergonomic CLI wrapper over the HTTP surface below. It discovers every running api via `~/.voltro/runtime-registry.json`, fans the matching GET/POST out to each, and renders the merged result — no `curl` + `jq` needed.
852
+
853
+ ```bash
854
+ voltro inspect app # manifest meta (kind, name, store, …)
855
+ voltro inspect routes # web page tree (web apps only)
856
+ voltro inspect rpc # procedures + workflow descriptors (api only)
857
+ voltro inspect metrics # rolling rpc latency buckets
858
+ voltro inspect cache # web data-cache stats (web apps only)
859
+ voltro inspect schedules # cron registrations + coordination mode
860
+ voltro inspect aggregates # materialised aggregate views
861
+ voltro inspect invoke --tag users.list --input '{}' # call a procedure over HTTP
862
+ ```
863
+
864
+ 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.
865
+
866
+ ## The inspect HTTP surface
867
+
868
+ 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).
869
+
870
+ ```bash
871
+ PORT=4000 # from the app's app.config.ts
872
+
873
+ curl -s localhost:$PORT/_voltro/inspect/routes | jq # web: page tree + render-mode flags
874
+ curl -s localhost:$PORT/_voltro/inspect/rpc | jq # api: every query / mutation / action / workflow
875
+ curl -s localhost:$PORT/_voltro/inspect/metrics | jq # rolling per-tag latency + invocation count
876
+ ```
877
+
878
+ There is no `/_voltro/inspect/queries` endpoint. The registered GET surface is `app`, `routes` (web-only), `cache` (web-only), `rpc` (api-only), `metrics`, and `subscriptions` (api-only) — `rpc` is the procedure list, `routes` is the web page tree.
879
+
880
+ What the surface reads:
881
+
882
+ - The endpoints serve in-process state from the running instance — no separate daemon.
883
+ - The default target is `http://localhost:<port>` based on the cwd's `app.config.ts`.
884
+ - GET responses are JSON; pipe into `jq`.
885
+
886
+ ### Routes & RPC
887
+
888
+ ```bash
889
+ curl -s localhost:$PORT/_voltro/inspect/routes | jq # web: page tree + render-mode flags
890
+ curl -s localhost:$PORT/_voltro/inspect/rpc | jq # api: query / mutation / action / workflow list
891
+ ```
892
+
893
+ ### Subscriptions panel
894
+
895
+ ```bash
896
+ curl -s localhost:$PORT/_voltro/inspect/subscriptions | jq
897
+ ```
898
+
899
+ Shows every active subscriber with:
900
+
901
+ - Query name + input
902
+ - Subject (who's subscribed)
903
+ - Read set (which tables / rows are tracked)
904
+ - Frame buffer depth (backpressure indicator)
905
+ - Connection age
906
+
907
+ For "why isn't this updating?" — check the read set. If your mutation writes to a table not in the read set, the subscription doesn't invalidate.
908
+
909
+ ### Workflow runs — `voltro workflows`
910
+
911
+ `voltro workflows` is the primary surface for inspecting + operating runs:
912
+
913
+ ```bash
914
+ voltro workflows list # recent runs
915
+ voltro workflows show <runId> # one run's steps + events
916
+ voltro workflows retry <runId>
917
+ voltro workflows cancel <runId>
918
+ voltro workflows suspend <runId>
919
+ voltro workflows resume <runId>
920
+ voltro workflows signal <runId> --name approval # inject a named signal
921
+ voltro workflows update <runId> --name …
922
+ voltro workflows children <parentExecutionId>
923
+ ```
924
+
925
+ Underneath, workflow state lives in the `_voltro_workflow_runs` + `_voltro_workflow_run_steps` tables and is surfaced live by the dashboard's Workflows panel. The same data is reachable over HTTP:
926
+
927
+ ```bash
928
+ curl -s localhost:$PORT/_voltro/inspect/workflows/runs | jq # recent runs
929
+ curl -s "localhost:$PORT/_voltro/inspect/workflows/runs/<runId>/steps" | jq # step-by-step
930
+ curl -s "localhost:$PORT/_voltro/inspect/workflows/runs/<runId>/events" | jq # the run's event log
931
+ ```
932
+
933
+ Each run row carries ID, name, status (running / succeeded / failed / dead), step count, last completed step, and duration. The per-run action endpoint matches `…/workflows/runs/<runId>/<action>` for `cancel` / `retry` / `suspend` / `resume` / `signal` — the `voltro workflows` subcommands and the dashboard's run-detail buttons both POST to these:
934
+
935
+ ```bash
936
+ curl -s -X POST localhost:$PORT/_voltro/inspect/workflows/runs/<runId>/retry
937
+ curl -s -X POST localhost:$PORT/_voltro/inspect/workflows/runs/<runId>/cancel
938
+ ```
939
+
940
+ For dead-letter triage, filter runs by `status`, or open the dashboard's Workflows tab. See [Workflows / Debugging](/docs/workflows/debugging).
941
+
942
+ ## `voltro logs`
943
+
944
+ The fastest way to see what a running instance is doing. Buffers the last 2000 server-side log records plus every browser console line the dev console bridge forwarded.
945
+
946
+ ```bash
947
+ voltro logs # last 100 from every running process
948
+ voltro logs --tail 50 --level error # only errors, last 50
949
+ voltro logs --since 30s # last 30 seconds
950
+ voltro logs --filter 'notes.summarise' # message substring
951
+ voltro logs --trace <traceId> # the WHOLE causal chain for one request
952
+ voltro logs --format json | jq '.records' # machine-parseable
953
+ ```
954
+
955
+ Run this BEFORE grepping source — the buffer carries the real error, stack, and rpc tag. The full flag set (`--scope`, `--source`, `--process`, `--no-color`, …) is documented in [Traces & logs from the shell](/docs/observability/cli) — the canonical reference for both commands.
956
+
957
+ ## `voltro traces`
958
+
959
+ Mirror of `voltro logs` for the distributed-trace buffer.
960
+
961
+ ```bash
962
+ voltro traces # 20 newest traces, pretty
963
+ voltro traces --errors # only traces containing an errored span
964
+ voltro traces --id <traceId> # one trace, span waterfall
965
+ voltro traces --errors --format json | jq '.traces[]'
966
+ ```
967
+
968
+ 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).
969
+
970
+ ## `voltro test`
971
+
972
+ ```bash
973
+ voltro test
974
+ voltro test path/to/file.test.ts
975
+ voltro test --filter notes
976
+ ```
977
+
978
+ Runs Vitest with the framework's preset:
979
+
980
+ - Auto-loaded global setup (test context, mock providers).
981
+ - `STORE=memory` by default for fast isolation.
982
+ - Plays well with `@voltro/testing` helpers (mock stores).
983
+
984
+ 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:
985
+
986
+ ```bash
987
+ pnpm vitest
988
+ ```
989
+
990
+ ## `voltro e2e`
991
+
992
+ ```bash
993
+ voltro e2e # current dir
994
+ voltro e2e apps/web # explicit web app directory
995
+ ```
996
+
997
+ `voltro e2e` takes only an optional path to the web app; it parses no other flags. Boots:
998
+
999
+ 1. `voltro dev` for the api app.
1000
+ 2. `voltro dev` for the web app.
1001
+ 3. Playwright runs `*.e2e.ts` files.
1002
+ 4. Tear down: stops the boot processes.
1003
+
1004
+ The e2e files use Playwright's API directly:
1005
+
1006
+ ```ts
1007
+ // apps/web/tests/signup.e2e.ts
1008
+ import { test, expect } from '@playwright/test'
1009
+
1010
+ test('signup creates a user', async ({ page }) => {
1011
+ await page.goto('/signup')
1012
+ await page.fill('[name=email]', 'a@b.com')
1013
+ await page.fill('[name=password]', 'correct horse battery staple')
1014
+ await page.click('button[type=submit]')
1015
+ await expect(page).toHaveURL(/\/dashboard/)
1016
+ })
1017
+ ```
1018
+
1019
+ The framework's `voltro e2e` handles the lifecycle for you: it boots the api + web siblings, runs Playwright against them, and tears the processes down on exit. Per-test isolation, fixtures, reporters, and sharding are configured in your Playwright config — `voltro e2e` itself forwards no flags to Playwright.
1020
+
1021
+ ## Securing the inspect surface
1022
+
1023
+ The inspect endpoints expose framework internals (queries, subscription state, workflow runs). In production, lock them down with a bearer token:
1024
+
1025
+ ```bash
1026
+ VOLTRO_INSPECT_TOKEN=$(openssl rand -hex 32)
1027
+ voltro start apps/api
1028
+ ```
1029
+
1030
+ Then every request must carry the token:
1031
+
1032
+ ```bash
1033
+ curl -s -H "Authorization: Bearer $VOLTRO_INSPECT_TOKEN" \
1034
+ localhost:4000/_voltro/inspect/rpc | jq
1035
+ ```
1036
+
1037
+ Or disable the surface entirely: `VOLTRO_INSPECT=off`. The dashboard then can't introspect the production instance — that's intentional.
1038
+
1039
+ ## Anti-patterns
1040
+
1041
+ - **Inspect surface open in production.** Either disable (`VOLTRO_INSPECT=off`) or lock behind `VOLTRO_INSPECT_TOKEN`. Anyone hitting `/_voltro/inspect/*` can read rpc shapes + live subscription data.
1042
+ - **`voltro test` against `STORE=postgres` by default.** Slower + flaky (test isolation harder). Use postgres only for integration tests that NEED it.
1043
+ - **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.
1044
+
1045
+ ## See also
1046
+
1047
+ - [Workflows / Debugging](/docs/workflows/debugging) — the dashboard's workflow panel
1048
+ - [Self-hosting](/docs/deployment/self-hosting) — production observability setup
1049
+
1050
+
1051
+
1052
+ ---
1053
+
1054
+ <!-- source: en/cli/data.md -->
1055
+ ## Data (export / import / backup / restore)
1056
+
1057
+ _voltro data — portable, resumable export/import of your app's data + assets (typed-NDJSON, content-addressed blobs), plus native backup/restore via the vendor tools._
1058
+
1059
+ `voltro data` moves your app's data and assets in and out. It has **two families**, because backup and portability are different jobs with different right answers:
1060
+
1061
+ - **Logical** — `export` / `import`: a portable, resumable, dialect-agnostic bundle. Use it for data takeout (GDPR), seeding staging from prod, or migrating across SQL dialects.
1062
+ - **Native** — `backup` / `restore`: orchestrates the vendor tools (`pg_dump`, `mysqldump`, …) for a lossless, point-in-time same-dialect backup. Use it for disaster recovery.
1063
+
1064
+ Both stream — a table or a blob is never fully held in memory — and both survive interruptions.
1065
+
1066
+ ## Logical export
1067
+
1068
+ ```bash
1069
+ voltro data export ./backup-2026-07-01
1070
+ voltro data export ./out --tenant org_abc # one tenant + its FK closure
1071
+ voltro data export ./out --tables users,posts # an explicit set
1072
+ voltro data export ./out --assets # include stored blobs
1073
+ voltro data export ./out --compression gzip # zstd (default) | gzip | none
1074
+ voltro data export ./out --consistency snapshot # point-in-time (see below)
1075
+ voltro data export ./out --target api --api-url https://api.example.com --token $SECRET # export FROM a live instance (in-process)
1076
+ voltro data export --target api --api-url https://api.example.com --token $SECRET --bundle-key backups/2026-07-01.vbundle # instance exports straight to storage (scale)
1077
+ ```
1078
+
1079
+ The output is a **directory bundle**, not a zip — each table is an independent file, so an interrupted run resumes cleanly and per-table compression stays effective (a single zip would fight both). Over `--target api` this same bundle is streamed as one framed `.vbundle` archive (a zero-dependency concatenation, not a zip) and unpacked back into the directory on the other end.
1080
+
1081
+ ```
1082
+ backup-2026-07-01/
1083
+ README.md # human summary: date, source host, dialect, scope,
1084
+ # table/row counts, and a LOUD real-vs-masked banner
1085
+ manifest.json # format version, source dialect + schema fingerprint,
1086
+ # per-table column types + row counts + checksums
1087
+ data/
1088
+ users.ndjson.zst # one typed-encoded row per line, framed-compressed
1089
+ posts.ndjson.zst
1090
+ assets/
1091
+ index.ndjson # key → sha256 → size → content-type
1092
+ 9f86d081… # blobs, content-addressed by sha256 (auto-deduped)
1093
+ .ledger.json # resume checkpoint (which tables/assets are done)
1094
+ ```
1095
+
1096
+ `README.md` is a human-readable sidecar written on every export — provenance (when, from which host, which dialect, scope) plus a prominent **data-sensitivity banner**: it says outright whether the bundle holds real (unmasked) data or was masked at the source (and which columns). It's ignored on import (the importer only reads files listed in `manifest.json`) and never contains a connection string or credentials. It rides inside the `.vbundle` archive too.
1097
+
1098
+ ### Single-file bundles (`.vbundle`) — one artifact, any size
1099
+
1100
+ Give the export/import a path ending in **`.vbundle`** and you get (or read) **one self-contained file** instead of a directory — everything (tables + blobs + manifest + README) in a single artifact you can copy A→B:
1101
+
1102
+ ```bash
1103
+ voltro data export ./snapshot.vbundle --assets # one file, blobs included
1104
+ voltro data import ./snapshot.vbundle --assets # stream it back in
1105
+ voltro data inspect ./snapshot.vbundle # peek at the metadata WITHOUT extracting
1106
+ voltro data unpack ./snapshot.vbundle ./out # explode a file to a dir to look inside
1107
+ ```
1108
+
1109
+ `voltro data inspect` prints the bundle's `README.md` (date, source host, dialect, scope, table/row counts, and the real-vs-masked banner) — read via an **early-stop peek** that stops after the metadata, so it stays fast even on a 200 GB bundle (add `--json` for the machine-readable `manifest.json`, `--passphrase` for an encrypted bundle). `voltro data unpack` fully extracts a file to a directory (blobs materialised under `assets/`), decrypting with `--passphrase` when needed.
1110
+
1111
+ It's built to work at **any size (200 GB+) without needing that much scratch space**. The pipeline **streams end-to-end**: on export, blobs are pulled from object storage straight into the file (nothing staged locally — peak local disk is just the tables); on import, blobs stream straight from the file to the destination's object storage (never unpacked to a temp dir first). The file is a framed archive (`.vbundle`, a zero-dependency streamable format — not a zip, whose central-directory-at-the-end design would force downloading the whole thing before reading entry one). Blobs are named by storage key with a per-blob sha footer, so **integrity is per-entry** and a resumed transfer **skips blobs already present** at the destination.
1112
+
1113
+ ### Encryption at rest (`--encrypt`)
1114
+
1115
+ A `.vbundle` is plaintext by default. For a copy that must be confidential at rest (a backup in a bucket, a file on a laptop/CI, an **unmasked** dump — which contains your `.encrypted()` columns as plaintext), encrypt it:
1116
+
1117
+ ```bash
1118
+ voltro data export ./backup.vbundle --assets --encrypt --passphrase "$BUNDLE_KEY"
1119
+ voltro data import ./backup.vbundle --assets --passphrase "$BUNDLE_KEY"
1120
+ voltro data unpack ./backup.vbundle ./inspect --passphrase "$BUNDLE_KEY"
1121
+ ```
1122
+
1123
+ - **A dedicated key, NOT the transfer secret.** The passphrase comes from `--passphrase` or `VOLTRO_BUNDLE_KEY` and is independent of `VOLTRO_DATA_TRANSFER_SECRET` (auth ≠ encryption; different instances have different transfer secrets; a local export has none). It's stretched with **scrypt** (salt + params in the file header).
1124
+ - **Streaming, authenticated AES-256-GCM.** The bundle is encrypted in chunks (the age/Tink STREAM construction), so it stays streaming + resumable at 200 GB, and it's **tamper- and truncation-evident**: a wrong passphrase, a flipped byte, or a dropped final chunk all fail decryption loudly — never a silent partial import.
1125
+ - **Complementary to masking.** Masking makes the *data* safe for a lower environment (strips PII); encryption makes the *artifact* confidential. A DR backup wants encryption (full real data); a prod→dev copy wants masking (and can add encryption too).
1126
+
1127
+ ### Why NDJSON, and why it's exact
1128
+
1129
+ Rows are written as newline-delimited JSON — streamable, resumable by line, and human-inspectable. A naïve `JSON.stringify` would corrupt data, so a **typed codec** (driven by the column types in the manifest) handles the values JSON can't:
1130
+
1131
+ - `bigint` → preserved exactly (never truncated to a float)
1132
+ - `NaN` / `Infinity` → preserved (JSON would turn them into `null`)
1133
+ - `bytes` → base64; `timestamp`/`date` → ISO-8601 → `Date` on import
1134
+ - `json` / `array` / `vector` → structured, verbatim
1135
+
1136
+ ### Scope
1137
+
1138
+ | Flag | Selects |
1139
+ |---|---|
1140
+ | *(none)* | Every table (the default). |
1141
+ | `--tenant <id>` | Every `tenant()`-scoped table filtered to that tenant, **plus** the FK closure in BOTH directions: (1) the transitive **FK-parent** closure of those rows — closure-pulled shared tables (a global `users` / reference table) are **row-subset to the ids the tenant's rows actually reference**, never exported in full; and (2) the **child** closure — rows that *reference* the tenant's rows (the comments on the tenant's projects) come along too, each scoped to the ids that actually point into the tenant set. The child walk is anchored on the `tenant()` tables, so a row that references **only a shared parent** (a global `users` another tenant also references) is **not** pulled — that would be a cross-tenant leak. A `--tenant` bundle therefore carries the tenant's parents AND children and **no other tenant's rows** — that's what makes it safe as a GDPR / per-tenant takeout. |
1142
+ | `--tables a,b` | An explicit set (you own referential integrity; the importer's deferred-FK resolution covers load-order dangles, see below). |
1143
+
1144
+ Tenant-scope details:
1145
+
1146
+ - **A tenant scope without a tenant id refuses loudly** (`ScopeError`): pass `--tenant <id>` (CLI) or `scope.tenantId` (API/profile). It never falls back to an unfiltered export.
1147
+ - **Which tables count as tenant-scoped** comes from the `tenant()` mixin metadata when the CLI / admin endpoint can read the declared schema (authoritative — a table can carry a `tenantId` column *without* being tenant-scoped, e.g. a global `users` table's active-org pointer). Without that metadata the exporter falls back to a documented heuristic: any table with a `tenantId` column.
1148
+ - A **cross-tenant reference** (a tenant-A row pointing at a tenant-B row) is never followed — the bundle stays tenant-clean and the reference dangles; importing such a bundle reports it loudly (`DanglingReferenceError`) unless the target already has the row.
1149
+
1150
+ ### Consistency: `live` vs `snapshot`
1151
+
1152
+ - **`live`** (default) — each table is read in short keyset-paginated chunks. Resilient and easy on the database, but the tables are read at slightly different instants (a concurrent write can leave a child whose parent you already passed; the importer's deferred-FK resolution handles the dangling reference — see below).
1153
+ - **`snapshot`** — every table is read inside **one transaction pinned to a single MVCC snapshot** (per-dialect isolation prelude), so the whole export is a consistent instant. The trade: that transaction is held open for the export's duration.
1154
+
1155
+ ## Logical import
1156
+
1157
+ ```bash
1158
+ voltro data import ./backup-2026-07-01
1159
+ voltro data import ./out --mode append --on-conflict skip # insert-only
1160
+ voltro data import ./out --mode replace --atomic # full refresh, all-or-nothing
1161
+ voltro data import ./out --target api --api-url https://api.example.com --token $SECRET # upload to a live instance
1162
+ voltro data import --target api --api-url https://api.example.com --token $SECRET --bundle-key backups/2026-07-01.vbundle # instance pulls from storage (scale)
1163
+ voltro data import ./out --assets # also restore blobs
1164
+ voltro data import ./out --no-verify # skip checksum/row-count verification
1165
+ voltro data import ./out --force # import despite schema drift AND cross-dialect warnings
1166
+ ```
1167
+
1168
+ Import is **integrity-checked** (each table's checksum + row count verified as it decodes; each asset re-hashed against its content address), applies tables in **FK-parent-first order**, and a resumed run skips already-applied tables via the ledger.
1169
+
1170
+ ### Schema-drift pre-flight
1171
+
1172
+ Every bundle records the **schema fingerprint** of the source it was exported from (a stable hash of the schema shape — the same fingerprint prod boot uses to detect drift). Before touching the target, `import` **recomputes the target app's fingerprint the same way** and compares it to the bundle's. On a mismatch it **refuses, fail-closed, before any row lands** — a drifted target (a missing column, a renamed table) would otherwise fail mid-load with a raw database error after some rows already committed:
1173
+
1174
+ ```text
1175
+ ✗ schema drift: the target schema (fingerprint d6ba94b0…) differs from the bundle's
1176
+ source (f67942f6…). Refusing before the table phase.
1177
+ items.extra: in the target, not in the bundle
1178
+ Re-export against the current schema, or pass --force to import anyway.
1179
+ ```
1180
+
1181
+ The error names the drift — the tables/columns that moved (down to per-column granularity for the tables the bundle carries) — so you can see exactly what changed. **`--force`** downgrades the refusal to a **loud warning** and proceeds (the operator has accepted that the shapes differ). Over the `--target api` path the same check runs **on the instance** against its declared schema and returns **`409` schema drift** with the fingerprints + diff; `--force` sends `x-import-force: 1` to override.
1182
+
1183
+ The pre-flight only runs when the importer has a target schema (the CLI introspects it; the API endpoint uses the instance's declared schema). Importing into a fresh/empty database with no comparable schema simply skips the check.
1184
+
1185
+ ### Deferred-FK resolution
1186
+
1187
+ A row whose write fails on a foreign-key constraint — a forward reference from a `live`-consistency export, a genuine FK **cycle** between tables (which the exporter orders by breaking the closing edge), or an intra-table self-reference to a later row — does **not** fail the import. It is held and resolved after every table has streamed:
1188
+
1189
+ 1. **Retry to a fixpoint** — forward references resolve once the later tables landed.
1190
+ 2. **FK-shedding** — rows still stuck are written with their FK-bearing columns set to `NULL` (possible wherever those columns are nullable), which breaks row cycles on every dialect without session-level constraint toggles.
1191
+ 3. **Patch pass** — shed rows are re-written with the full bundle row, restoring the FK values.
1192
+
1193
+ Anything still unresolvable — the parent row exists in **neither the bundle nor the target**, or a `NOT NULL` FK cycle — fails with a typed `DanglingReferenceError` naming each offending table + primary key + the database's own reason. Held rows are the exception set, not the data set: memory is bounded by how many rows dangle at load time.
1194
+
1195
+ ### Write modes (`--mode`)
1196
+
1197
+ | Mode | Behaviour | Use it for | Conflict |
1198
+ |---|---|---|---|
1199
+ | `upsert` (default) | INSERT-or-UPDATE by primary key | sync / idempotent re-import | overwrites per row |
1200
+ | `append` | INSERT only | additive data (event log, new seed) | `--on-conflict skip` (default) or `fail` |
1201
+ | `replace` | TRUNCATE the target tables (children-first) then INSERT | full refresh — target ends up **exactly** the bundle | — |
1202
+
1203
+ `replace` **refuses a partial bundle** (a subset / tenant / table scope): truncating would delete rows the bundle never carried. Re-export with full scope, or use `upsert`. (`append --on-conflict fail` throws on a duplicate primary key only where the store enforces the constraint — every SQL dialect does; the in-memory dev store overwrites.)
1204
+
1205
+ ### Importing into a LIVE instance
1206
+
1207
+ A plain import connects **straight to the database** (not through the running app), so it is an uncoordinated concurrent writer — readers can see partial state, the reactive layer either storms (CDC dialects) or goes stale (others), and rows race with live writes. So `import` / `restore` **refuse by default when a live instance is detected** (via the local runtime registry, or an explicit `--api-url` probe). Two ways forward:
1208
+
1209
+ | Target | How | Guarantees | Use for |
1210
+ |---|---|---|---|
1211
+ | **direct** (default) | writes straight to the DB | none while live — **guarded**; pass `--allow-live` to override | a stopped target, a replica, a dev/staging DB not serving traffic |
1212
+ | **direct + `--atomic`** | wraps truncate+load in ONE transaction | MVCC readers see the import **all-or-nothing** (old until commit, new after) — the live-safe `replace` | replacing a live target's data with no partial-state window |
1213
+ | **in-process (`--target api`)** | `voltro data import <dir> --target api --api-url <url> --token <secret>` **uploads** the packed bundle (or, with `--bundle-key`, has the instance **pull it from storage**) to the instance's secret-gated admin endpoint, which imports it **in its own process** through its store | full app pipeline — validation, mixins, **field encryption**, hooks — AND automatic reactivity (in-process writes emit change events, so subscriptions update; no separate resync) | a live merge (incl. PROD) that must honour app invariants |
1214
+
1215
+ `--atomic` is the recommended live `replace`: one transaction, so live reads never see a half-loaded table. (It holds a write transaction for the load duration — writes to those tables block, readers don't. A shadow-table rename would shorten that lock window, but for a full replace concurrent writes are discarded on swap anyway, so it isn't the default.)
1216
+
1217
+ ### The `--target api` data-transfer endpoints (prod-safe)
1218
+
1219
+ `--target api` moves data in/out of a **running production** instance without direct DB access. It does NOT reuse the dev data-viewer surface — two dedicated endpoints, `POST /_voltro/admin/export` and `POST /_voltro/admin/import`, both built to be safe on prod and both gated by the **same** secret:
1220
+
1221
+ - **Secure by default — no secret, no endpoints.** The instance mounts BOTH routes **only** when a data-transfer secret is configured: `VOLTRO_DATA_TRANSFER_SECRET=<≥16 chars>` (or `serveApi({ dataTransferSecret })`). Unset (or shorter than 16 chars) → the routes return `404`. There is no "on" switch that leaves them open.
1222
+ - **Secret gate, constant-time.** Every request must present that secret as a **Bearer token** (`Authorization: Bearer <secret>`), compared in constant time over SHA-256 digests (neither length nor content leaks via timing). The gate is a framework-owned secret **independent of app RBAC** — enabling data transfer can never accidentally ride on a user role. The CLI sends it via `--token` / `VOLTRO_DATA_TRANSFER_SECRET` (it must equal the server's secret). Wrong/absent token → `401`.
1223
+ - **One secret, both directions.** The same secret gates export (read) and import (write). If you need to grant export without import (e.g. a backup job that must never overwrite), split it into a per-operation credential deliberately — the default is one credential for the whole surface.
1224
+
1225
+ **Import** (`POST /_voltro/admin/import`) applies a bundle **in the instance's own process** through its store — full app pipeline (validation, mixins, **field encryption**, hooks) AND automatic reactivity. Two transports, neither needs a server-readable path:
1226
+ - **upload** (default) — the CLI **packs the bundle and uploads the bytes**; the server unpacks to its own temp dir, imports in-process, cleans up. Buffered → small/moderate bundles.
1227
+ - **storage-pull** (`--bundle-key <key>`) — the CLI sends only `{ "bundleKey": "<key>" }`; the instance **streams that archive from its configured object storage** (S3 / Azure / MinIO / filesystem, resolved from the storage env — `STORAGE_PROVIDER`, `S3_*`, `AZURE_*`, …), no body buffering → arbitrarily large bundles.
1228
+
1229
+ **Export** (`POST /_voltro/admin/export`) reads the instance's data **in-process** (so field-decryption + hooks apply) and packs a bundle. ⚠️ This is a **data-exfiltration surface** — it can read *all* prod data — which is exactly why it sits behind the same off-by-default secret. It is also the right place for **source-side masking on a live box**: name a **server-side** profile and its scope/subset/masking apply *before any byte leaves the instance* (fail-closed — see the prod→dev section below). Two transports:
1230
+ - **download** (default) — the response body **is** the packed bundle; the CLI writes it to `<outDir>`. Buffered → small/moderate exports.
1231
+ - **storage-push** (`--bundle-key <key>`) — the instance **exports straight to its object storage** under that key and returns `{ bundleKey, tables, rows }`, no response buffering → the scale path. Then pull it elsewhere (e.g. `import --bundle-key`).
1232
+
1233
+ The masking policy for a `--target api` export is named by `--profile <name>` and loaded **on the server** (`data-profiles/<name>.ts`) — version-controlled on the instance, never supplied by the client. No profile → a raw export (the secret-holder is trusted to read prod).
1234
+
1235
+ **Assets over the API — full parity with the file path.** With `--assets`, an API export **streams the instance's blobs from its own object storage into the bundle** (single-pass, nothing staged); an API import **streams the bundle's blobs straight to the destination instance's storage** as the archive arrives. So `voltro data export prod.vbundle --assets` (from a running instance via `--target api`) → `voltro data import prod.vbundle --target api --api-url <dev>` moves data *and* blobs at any size. (Uploading a *directory* bundle that has materialised assets over `--target api` transfers data only — pack a single-file `.vbundle` for assets; the CLI warns if you try.)
1236
+
1237
+ - **Audited** — every export/import logs the transport, table + row counts, and (export) how many columns were masked.
1238
+
1239
+ For a live **merge** (import) that must run app invariants + drive reactivity, this in-process path needs no quiesce/resync — in-process writes are reactive automatically.
1240
+
1241
+ ### Cross-dialect
1242
+
1243
+ You can import a bundle into a **different** dialect than it came from — but only for the framework's portable DSL types. A cross-dialect import runs a **portability lint** first:
1244
+
1245
+ - **Blocked** (refused unless `--force`): `raw()` columns (verbatim source-dialect SQL), and `vector` columns targeting a dialect with no vector type.
1246
+ - **Warned** (imported, represented differently): `array` / `interval` on a non-postgres target.
1247
+ - **Portable everywhere**: text, integer, real, boolean, timestamp, date, json, bytes, reference, enum, id.
1248
+
1249
+ The lint **refuses loudly** rather than silently coercing — a blocked import tells you exactly which columns are the problem.
1250
+
1251
+ ## Reliability: chunking, retry, resume
1252
+
1253
+ A production export can run for hours over millions of rows and gigabytes of blobs. It is built so a dropped connection, a pool blip, or an outright crash never means starting over — and so a huge table never blows up memory or knocks the source database over.
1254
+
1255
+ ### Chunking — flat memory, gentle on the DB
1256
+
1257
+ Every table is read with a **keyset cursor**, not `OFFSET`: `WHERE pk > :last ORDER BY pk LIMIT n` (default **1000 rows/page**, tune with `chunkSize`). Consequences:
1258
+
1259
+ - **Flat memory** — one bounded page is in memory at a time, regardless of table size (a 20 M-row table streams in 1000-row pages).
1260
+ - **O(1) per page** on the pk index — `OFFSET n` re-scans and skips `n` rows every page (O(n²) over a full walk); keyset reads each row exactly once.
1261
+ - **Backpressure** — a page is fetched only when the sink (encode → compress → disk) is ready to take it, so a slow disk throttles the DB reads instead of overrunning memory or hammering the server.
1262
+
1263
+ `live` consistency keeps each read short (no long-held transaction → vacuum-friendly); `snapshot` trades that for one pinned transaction held for the export's duration (see above).
1264
+
1265
+ ### Retry — a blip doesn't kill the run
1266
+
1267
+ Each page read is retried on a transient failure (dropped connection, pooled-backend hiccup) with **exponential backoff + jitter**, up to **5 attempts** by default (`retryTimes`). The retry is **per page**, and the keyset cursor is preserved — so a reconnected page resumes at exactly the row it stopped on, never re-emitting or skipping. A genuinely-broken read still surfaces after the attempts are exhausted rather than hanging.
1268
+
1269
+ ### Resume — re-run and it continues
1270
+
1271
+ Both export and import checkpoint into a small **`.ledger.json`** and can be re-run to continue where they stopped:
1272
+
1273
+ - **Export** writes each table with an atomic temp-file + rename, and records the table (and each asset) in the ledger only once it's fully written. A crash mid-table leaves **no half-written file** (the temp is discarded); re-running skips the completed tables/assets and redoes only the unfinished one. Assets are **content-addressed**, so resume is per-asset — a blob whose hash is already in the bundle is skipped.
1274
+ - **Import** keeps its own ledger (`.import.ledger.json`) and skips already-applied tables on a re-run. Correctness never depends on the ledger, though: every row is **upserted by primary key**, so redoing an in-flight table is always safe, and each table's checksum + row count and each asset's hash are **verified** as they load — a truncated or corrupted bundle fails loudly instead of importing garbage.
1275
+
1276
+ Net: interrupt an export or import at any point — network drop, `Ctrl-C`, OOM-killed pod — and re-running the same command finishes the job without duplicating work or corrupting the target.
1277
+
1278
+ ### Progress & observability
1279
+
1280
+ A multi-hour job is not a black box. Both pipelines signal per **table** — never per row, so the reporting never slows the hot path:
1281
+
1282
+ - **`voltro data export|import` prints a line per finished table** — `export [7/23] users · 1.2k rows` — so you watch the run progress instead of staring at a silent terminal. A ledger-resumed table prints once with its recorded count.
1283
+ - **Programmatically**, `runExport` / `runImport` take an optional **`onProgress`** callback. It fires a `start` then a `done` event per table, in FK-parent-first order, carrying the table name, its 0-based `index`, the `tableCount`, `rowsDone`, and `total` (the table's row count when known ahead of time — import reads it from the manifest; a live export learns it only once the table drains). A throwing progress renderer never aborts the job.
1284
+ - **Traces + metrics are always on**, no wiring. Each table runs inside an `Effect.withSpan('data-transfer.export.table' | 'data-transfer.import.table')` (attributes `table` / `index` / `phase`), so if the app has tracing enabled the export/import shows per-table phase timing in the trace. Two metrics record throughput: `voltro_data_transfer_rows` (counter, tagged by `phase` + `table`) and `voltro_data_transfer_table_seconds` (histogram, tagged by `phase`).
1285
+
1286
+ ```ts
1287
+ import { runImport, type ProgressEvent } from '@voltro/data-transfer'
1288
+
1289
+ yield* runImport({
1290
+ store: target,
1291
+ bundleDir: './backup',
1292
+ onProgress: (e: ProgressEvent) => {
1293
+ if (e.event === 'done') console.log(`[${e.index + 1}/${e.tableCount}] ${e.table}: ${e.rowsDone} rows`)
1294
+ },
1295
+ })
1296
+ ```
1297
+
1298
+ ### Typed errors — CLI-catchable AND rpc-declarable
1299
+
1300
+ Every pipeline failure is a **tagged error**, caught by tag with `Effect.catchTag(...)`. The errors that appear on the `runExport` / `runImport` error channels — the **wire errors**, and therefore exactly what the `--target api` admin endpoints surface — are `Schema.TaggedError`, so a handler can declare them on an rpc procedure's `error:` schema and the rpc encoder marshals them across the wire round-trip-safely (no hand-rolled JSON per tag):
1301
+
1302
+ `BundleError`, `CodecError`, `IntegrityError`, `CrossDialectError`, `ImportModeError`, `DanglingReferenceError`, `SchemaDriftError`, `MaskingError`, `ScopeError`.
1303
+
1304
+ The two internal errors — `NativeToolError` (native `backup`/`restore`) and `CompressionError` (folded into `BundleError` by the pipelines) — never cross the wire, so they stay plain `Data.TaggedError`: still catchable by tag, just no Schema surface.
1305
+
1306
+ ```ts
1307
+ import { Effect } from 'effect'
1308
+ import { runImport, type SchemaDriftError } from '@voltro/data-transfer'
1309
+
1310
+ yield* runImport({ store: target, bundleDir: './backup', targetSnapshot }).pipe(
1311
+ Effect.catchTag('SchemaDriftError', (e: SchemaDriftError) => Effect.log(`refusing: schema drifted — ${e.diff.join('; ')}`)),
1312
+ )
1313
+ ```
1314
+
1315
+ ## Native backup / restore
1316
+
1317
+ ```bash
1318
+ voltro data backup ./backups/2026-07-01 # pg_dump --format=custom / mysqldump --single-transaction / …
1319
+ voltro data restore ./backups/2026-07-01 # pg_restore / mysql / …
1320
+ ```
1321
+
1322
+ These shell out to the vendor tools resolved from your `DB_DIALECT` + connection env. They produce a dialect-native artifact (`db.dump`, `db.sql`, `db.sqlite`, `db.bacpac`) that is lossless and point-in-time consistent for **same-dialect** restore — the right tool for disaster recovery. Secrets are passed via the tools' environment variables (`PGPASSWORD`, `MYSQL_PWD`), never on the command line, where the tool supports it. The named tool must be installed and on `PATH`.
1323
+
1324
+ ## Masking (prod → dev/stage safely)
1325
+
1326
+ Cloning prod into a lower environment must not carry real user data. `voltro data export`
1327
+ does this with **masking**: PII is replaced by realistic, referentially-consistent fakes
1328
+ **at the source** — before a row is ever written — so real values never reach the bundle,
1329
+ transit, or a developer's machine.
1330
+
1331
+ ```bash
1332
+ voltro data export ./out --profile dev
1333
+ ```
1334
+
1335
+ Masking is driven by **two layers**:
1336
+
1337
+ 1. **Classification in the schema** — [`.sensitive(class)` / `.safe()`](/docs/database/sensitivity)
1338
+ on each column. This says *what kind* of data a column holds. It lives in the schema
1339
+ because the data's meaning is a property of the schema, not of one export.
1340
+ 2. **A per-environment masking POLICY** — how each class/column is transformed for *this*
1341
+ target. It lives in a profile so a target environment's whole recipe is one reviewable,
1342
+ version-controlled file.
1343
+
1344
+ ```ts
1345
+ // data-profiles/dev.profile.ts
1346
+ import { defineDataProfile } from '@voltro/data-transfer'
1347
+ export default defineDataProfile({
1348
+ subset: { seeds: { users: undefined } },
1349
+ masking: {
1350
+ seed: process.env.MASK_SEED!,
1351
+ // classes override the built-in defaults; columns override per-column
1352
+ classes: { freeText: 'redact' },
1353
+ columns: { '_voltro_mail_outbox.to': { fake: 'email' } },
1354
+ onUnclassified: 'error', // fail-closed (default)
1355
+ },
1356
+ consistency: 'snapshot',
1357
+ assets: true,
1358
+ })
1359
+ ```
1360
+
1361
+ ### Deterministic + seed-keyed
1362
+
1363
+ Every transform is deterministic and keyed on a secret **`seed`**:
1364
+
1365
+ - **Same input → same fake, everywhere.** One email becomes the SAME fake in every table
1366
+ it appears in, so joins survive; and it stays stable across re-runs, so dev data doesn't
1367
+ churn. Keeping the seed stable is **pseudonymisation**.
1368
+ - **Rotate or discard the seed → the mapping is irrecoverable.** That makes the result
1369
+ **anonymisation**.
1370
+
1371
+ `seed` is REQUIRED — masking without one is a bug (pass it from the environment, never
1372
+ commit it). Transforms are also **format-preserving** (a fake email is a valid email) and
1373
+ **null-preserving** (a null stays null — nullability holds).
1374
+
1375
+ ### Actions
1376
+
1377
+ An action is what a column's value becomes. Set them per class (`classes`) or per column
1378
+ (`columns`, which wins):
1379
+
1380
+ | Action | Effect |
1381
+ |---|---|
1382
+ | `keep` | copy verbatim |
1383
+ | `null` | set to `null` |
1384
+ | `redact` | fixed placeholder (`[redacted]` for text, `null` otherwise) |
1385
+ | `hash` | deterministic opaque hex (stable, non-reversible without the seed) |
1386
+ | `dateShift` | shift a date by a seed-derived offset (relative intervals + ordering preserved) |
1387
+ | `{ fake: '<class>' }` | a format-preserving fake of that class |
1388
+ | `{ custom: (input) => … }` | your own transform (`input` = `{ value, table, column, columnType, seed }`) |
1389
+
1390
+ ### Class → action defaults
1391
+
1392
+ A minimal policy is just a `seed` — every known class has a default action:
1393
+
1394
+ | Class | Default action |
1395
+ |---|---|
1396
+ | `email` `fullName` `firstName` `lastName` `username` `phone` `address` `company` `url` `ip` `creditCard` | `{ fake: '<class>' }` |
1397
+ | `date` | `dateShift` |
1398
+ | `secret` | `null` |
1399
+ | `freeText` | `redact` |
1400
+
1401
+ A custom class with no entry in `classes` falls back to `redact`. Resolution order for any
1402
+ column is: **`columns[table.column]` → the class's action (`classes` → built-in default) →
1403
+ `.safe()` keeps → PK/FK keeps → `onUnclassified`.**
1404
+
1405
+ ## Fail-closed
1406
+
1407
+ Masking is **fail-closed**. A column that is neither `.sensitive()` nor `.safe()` (and isn't
1408
+ a PK/FK, and has no `columns` override) **refuses the export** and is named in the error —
1409
+ so a newly-added column can never silently leak PII to dev. Classify it, or override it in
1410
+ the policy.
1411
+
1412
+ The opt-out is `onUnclassified: 'keep'` (or `'null'`), which makes masking **fail-open** for
1413
+ unclassified columns. Discouraged — it defeats the guarantee; prefer classifying the column.
1414
+ See [why fail-closed](/docs/database/sensitivity#why-fail-closed).
1415
+
1416
+ ## `--dry-run` — preview without writing
1417
+
1418
+ Preview a masking export **without writing a bundle** — the trust surface before real data
1419
+ moves. It reads a small sample per table and reports, per masked column, the before→after;
1420
+ a **leak scan** that warns when a KEPT column still LOOKS like PII (catches a
1421
+ misclassification — a `.safe()` on something that isn't); and the fail-closed list.
1422
+
1423
+ ```bash
1424
+ voltro data export ./out --profile dev --dry-run
1425
+ ```
1426
+
1427
+ ```text
1428
+ users
1429
+ email [fake:email] "ada@corp.com" → "grace.hopper1847@example.com"
1430
+ name [fake:fullName] "Ada Byron" → "Linus Torvalds"
1431
+ ssn [null] "078-05-1120" → null
1432
+
1433
+ ⚠ possible leaks in KEPT columns:
1434
+ users.nickname looks like email: "ada@corp.com"
1435
+
1436
+ ✗ unclassified (would BLOCK a real export — add .sensitive()/.safe() or a policy override):
1437
+ users.bio
1438
+
1439
+ ✓ every exported column is classified — safe to run.
1440
+ ```
1441
+
1442
+ Nothing is written. `--dry-run` exits non-zero when the unclassified list is non-empty, so
1443
+ it doubles as a CI gate for classification coverage. (It requires a profile with a masking
1444
+ policy — there is nothing to preview otherwise.)
1445
+
1446
+ ## Audit
1447
+
1448
+ A masking export records what it changed in the bundle's `manifest.json`, under `masking`:
1449
+
1450
+ ```json
1451
+ "masking": {
1452
+ "transformed": ["users.email", "users.name", "users.ssn"]
1453
+ }
1454
+ ```
1455
+
1456
+ `masking.transformed` lists every `table.column` that was pseudonymised/anonymised (i.e.
1457
+ every column whose action was not `keep`), so a reviewer can verify the copy was masked as
1458
+ intended — without diffing the data. A policy `id` is recorded alongside it when the policy
1459
+ sets one.
1460
+
1461
+ ## Subsetting
1462
+
1463
+ `subset` exports a referentially-correct SLICE instead of whole tables. You give **seed
1464
+ rows** per table; the exporter adds their **transitive FK-parent closure** — every row the
1465
+ seeds (and their parents, recursively) point at — so the slice imports with **no dangling
1466
+ references**.
1467
+
1468
+ ```ts
1469
+ subset: {
1470
+ seeds: {
1471
+ users: undefined, // every user (no predicate)
1472
+ orders: eq('status', 'open'), // only open orders (a Predicate)
1473
+ },
1474
+ }
1475
+ // exports those rows PLUS every parent row they reference (users an order points at, etc.)
1476
+ ```
1477
+
1478
+ A `subset` **replaces** `scope` when both are set — only the seeded tables and their parent
1479
+ closure are exported. Contrast the two:
1480
+
1481
+ - **`scope`** (`--tenant` / `--tables` / all) selects **tables** (a tenant's rows plus the
1482
+ row-subset FK parents they reference, or an explicit table set). Use it to move a tenant
1483
+ or named tables.
1484
+ - **`subset`** selects **specific rows** and pulls in exactly the parents they need. Use it
1485
+ to carve a small, self-consistent slice ("these 1000 users and everything they reference").
1486
+
1487
+ **Child closure.** By default the closure follows **parents only** — a seeded user brings the
1488
+ org it belongs to, but not that user's posts. Set `children` to also pull in the rows that
1489
+ **reference** the seeded set, each scoped to the ids that actually point into it (never a whole
1490
+ child table). The child rows' own parents fold back through the parent closure, so the slice
1491
+ stays referentially complete:
1492
+
1493
+ ```ts
1494
+ subset: {
1495
+ seeds: { orgs: eq('id', 'org_abc') },
1496
+ children: { roots: ['orgs'] }, // pull the org's users → their posts → those posts' comments
1497
+ }
1498
+ ```
1499
+
1500
+ `children.roots` **anchors** the walk: a child comes along only when it references a `roots`
1501
+ row (or a child already pulled in this walk), so the walk stays tight — a row that references
1502
+ only a *shared* parent outside the anchor is not dragged in. (This is exactly what keeps a
1503
+ `--tenant` export tenant-tight: its child walk is anchored on the `tenant()` tables.) Use
1504
+ `children: true` for an unanchored org-slice takeout where there is no tenant boundary to
1505
+ respect.
1506
+
1507
+ Subsetting reads the selected rows into memory to collect id sets — it's built for small
1508
+ slices, not for halving a huge table (use `scope`/`--tenant` for that).
1509
+
1510
+ ## Profiles
1511
+
1512
+ A **profile** bundles a target environment's whole recipe — scope/subset, masking,
1513
+ consistency, compression, assets — into one default-exported object, so
1514
+ `voltro data export --profile dev` is a single, reviewable, version-controlled description
1515
+ of *how prod becomes dev*.
1516
+
1517
+ ```ts
1518
+ import { defineDataProfile } from '@voltro/data-transfer'
1519
+
1520
+ export default defineDataProfile({
1521
+ scope: { kind: 'all' }, // or omit and use --tenant/--tables
1522
+ subset: { seeds: { users: undefined } }, // replaces scope when set
1523
+ masking: { seed: process.env.MASK_SEED! },
1524
+ consistency: 'snapshot', // 'live' | 'snapshot'
1525
+ compression: 'zstd', // 'zstd' | 'gzip' | 'none'
1526
+ assets: true, // include stored blobs
1527
+ })
1528
+ ```
1529
+
1530
+ `--profile <name>` resolves, in order: `./<name>`, `./<name>.profile.ts`,
1531
+ `./data-profiles/<name>.ts`, `./data-profiles/<name>.profile.ts`. So `--profile dev` finds
1532
+ `./dev.profile.ts` or `./data-profiles/dev.ts`. The module's **default export** must be the
1533
+ profile (use `defineDataProfile` for full type-checking).
1534
+
1535
+ **CLI flags override profile values.** `--assets`, `--compression`, `--consistency`,
1536
+ `--tenant`, `--tables` all win over what the profile sets — so a profile is the default and a
1537
+ flag is the one-off override.
1538
+
1539
+ ## Which do I use?
1540
+
1541
+ | Goal | Command |
1542
+ |---|---|
1543
+ | Disaster recovery / scheduled backups | `voltro data backup` (native) |
1544
+ | Clone prod → staging (masked, no real PII) | `voltro data export --profile <env>` (masking + subset) |
1545
+ | Preview a masked export before running it | `voltro data export --profile <env> --dry-run` |
1546
+ | Clone prod → staging (same dialect, unmasked) | `voltro data export` then `import` (or native backup/restore) |
1547
+ | GDPR / per-tenant takeout | `voltro data export --tenant <id> --assets` |
1548
+ | A small self-consistent slice of the data | `voltro data export --profile <env>` with a `subset` |
1549
+ | Move Postgres → MySQL | `voltro data export` then `import` (cross-dialect lint applies) |
1550
+
1551
+
1552
+
1553
+ ---
1554
+
1555
+ <!-- source: en/cli/mcp.md -->
1556
+ ## MCP server (voltro-mcp)
1557
+
1558
+ _Wire a running Voltro api into Claude Code / Cursor as an MCP server — read-only tools, resources and prompts over the app's procedures, tables, workflows and their JSON Schemas, over stdio or Streamable HTTP._
1559
+
1560
+ `@voltro/mcp` ships two standalone bins — **`voltro-mcp`** (stdio) and **`voltro-mcp-http`** (Streamable HTTP) — that serve a running api's capability manifest to a coding agent over the Model Context Protocol. The agent can then discover what the backend exposes — every rpc procedure with its input/output JSON Schema, the user tables, the workflows, the schema-driven-UI widget kinds — before writing UI or agent code.
1561
+
1562
+ It is **read-only metadata**: nothing here can execute a procedure or return row data. The bins talk to the same `GET /_voltro/inspect/manifest` endpoint the [inspect surface](/docs/cli/inspect) exposes, and honour its token gate. The server advertises three MCP capabilities — **tools**, **resources**, and **prompts**.
1563
+
1564
+ ## Setup
1565
+
1566
+ Two environment variables, both optional:
1567
+
1568
+ | Var | Default | Notes |
1569
+ |---|---|---|
1570
+ | `VOLTRO_INSPECT_URL` | `http://localhost:4000` | Base URL of the running api. |
1571
+ | `VOLTRO_INSPECT_TOKEN` | _(unset)_ | Sent as `Authorization: Bearer <token>` when the inspect surface is gated. |
1572
+
1573
+ ### Claude Code
1574
+
1575
+ ```bash
1576
+ claude mcp add voltro -- npx -y @voltro/mcp
1577
+ # an api on a non-default port:
1578
+ claude mcp add voltro --env VOLTRO_INSPECT_URL=http://localhost:4001 -- npx -y @voltro/mcp
1579
+ ```
1580
+
1581
+ ### Cursor / generic MCP config
1582
+
1583
+ ```json
1584
+ {
1585
+ "mcpServers": {
1586
+ "voltro": {
1587
+ "command": "npx",
1588
+ "args": ["-y", "@voltro/mcp"],
1589
+ "env": { "VOLTRO_INSPECT_URL": "http://localhost:4000" }
1590
+ }
1591
+ }
1592
+ }
1593
+ ```
1594
+
1595
+ ## The tools
1596
+
1597
+ | Tool | Returns |
1598
+ |---|---|
1599
+ | `voltro_list_procedures` | Every rpc procedure with its kind; `[public-rest]` / `[agent-tool]` markers for projected descriptors. |
1600
+ | `voltro_get_procedure` | One procedure's kind, input/output JSON Schema, source file(s), table targets, and projections. |
1601
+ | `voltro_search_procedures` | Procedures whose tag contains a substring. |
1602
+ | `voltro_list_tables` | The app's USER tables (column count, reactivity). |
1603
+ | `voltro_get_table` | One table's full column list (types, nullability, FK targets, enums). |
1604
+ | `voltro_list_workflows` | The registered durable workflows. |
1605
+ | `voltro_list_widgets` | The schema-driven-UI widget kinds. |
1606
+
1607
+ ## The resources
1608
+
1609
+ The server also exposes the manifest as MCP **resources** — stable, addressable `voltro://` URIs an agent reads. `resources/list` enumerates them (fresh from the manifest each call); `resources/read` returns the metadata as JSON.
1610
+
1611
+ | URI | Contents |
1612
+ |---|---|
1613
+ | `voltro://manifest` | The whole capability manifest as JSON. |
1614
+ | `voltro://procedure/<tag>` | One procedure's kind, input/output JSON Schema, source, and table targets. |
1615
+ | `voltro://table/<name>` | One table's full column list. |
1616
+
1617
+ ## The prompts
1618
+
1619
+ Reusable MCP **prompt templates** that render against the _live_ manifest, so the returned messages carry the app's real schema rather than a generic stub. `prompts/list` advertises them; `prompts/get` renders one.
1620
+
1621
+ | Prompt | Arguments | Renders |
1622
+ |---|---|---|
1623
+ | `scaffold_procedure` | `kind`, `purpose` | A brief to draft a new query/mutation/action, listing the app's real tables + sibling procedures of that kind. |
1624
+ | `explain_table` | `table` | The table's schema + the procedures that read/write it. |
1625
+ | `wire_ui_for_procedure` | `tag` | A brief to call one procedure and render its result, embedding its real input/output schema. |
1626
+
1627
+ ## Streamable HTTP transport
1628
+
1629
+ For clients that speak MCP over HTTP, **`voltro-mcp-http`** serves the same surface over the current **Streamable HTTP** transport (the single-endpoint POST/GET model that replaced the old HTTP+SSE dual-endpoint). One endpoint handles:
1630
+
1631
+ - **POST** a JSON-RPC message → a JSON response, or an SSE stream (`text/event-stream`) carrying the response(s) when the client's `Accept` allows it. An `initialize` POST mints a session and returns it in the `Mcp-Session-Id` header; every later POST must echo that header.
1632
+ - **GET** (with `Accept: text/event-stream`) → opens the server→client SSE channel.
1633
+ - **DELETE** → ends the session.
1634
+
1635
+ ```bash
1636
+ VOLTRO_MCP_HTTP_PORT=4100 VOLTRO_INSPECT_URL=http://localhost:4000 npx -y @voltro/mcp voltro-mcp-http
1637
+ ```
1638
+
1639
+ | Var | Default | Notes |
1640
+ |---|---|---|
1641
+ | `VOLTRO_MCP_HTTP_PORT` | `4100` | Listen port. |
1642
+ | `VOLTRO_MCP_HTTP_PATH` | `/mcp` | The single MCP endpoint path. |
1643
+
1644
+ ## Freshness + failure behavior
1645
+
1646
+ The manifest is read through a TTL-cached source (~10 seconds): a procedure you add during a `voltro dev` session shows up on the next tool call — no MCP-server restart. When the api is unreachable or the token is wrong, the tool output says so (`(no procedures — manifest unavailable: …)`) and the bin logs the reason to stderr at boot, instead of silently presenting an empty app.
1647
+
1648
+ ## Protocol scope
1649
+
1650
+ 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`.