@voltro/cli 0.33.0 → 0.34.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 (337) hide show
  1. package/CHANGELOG.md +1801 -0
  2. package/bin/nodeEnvironment.d.mts +30 -0
  3. package/bin/nodeEnvironment.mjs +158 -0
  4. package/bin/voltro.mjs +69 -5
  5. package/dist/addCommand-BNeoeSxe.js +124 -0
  6. package/dist/addCommand-aXSQveak.js +2 -0
  7. package/dist/agentsMd-BTchIZku.js +2 -0
  8. package/dist/agentsMd-mhQMF1bx.js +254 -0
  9. package/dist/apiBuild-BICVGnEC.js +2 -0
  10. package/dist/{apiBuild-h9VHtnlw.js → apiBuild-DZF_f0_I.js} +46 -46
  11. package/dist/appPort-B_HpJ_ck.js +48 -0
  12. package/dist/baselineCommand-C2ClWZN3.js +2 -0
  13. package/dist/baselineCommand-DIttzO8A.js +227 -0
  14. package/dist/bin.js +71 -28
  15. package/dist/build-CE7Ca9gm.js +711 -0
  16. package/dist/cacheCommand-DA4OH9xt.js +42 -0
  17. package/dist/capabilitiesCommand-nq_pz5xd.js +123 -0
  18. package/dist/checkCommand-Dv8r9tGO.js +231 -0
  19. package/dist/checkCommand-YZDmkAGd.js +2 -0
  20. package/dist/{cliArgs-qdZSElM3.js → cliArgs-D4p8n7EE.js} +12 -1
  21. package/dist/cliError-BmdYnghb.js +10 -0
  22. package/dist/cliOutput-D1tSBoRM.js +15 -0
  23. package/dist/{cliRuntime-Oh517vCV.js → cliRuntime-Dh7UDinH.js} +20 -20
  24. package/dist/cloudClient-DWL-Hw_T.js +67 -0
  25. package/dist/cloudCmd-Cvv5HGaZ.js +364 -0
  26. package/dist/clusterCmd-CNXKlcvD.js +54 -0
  27. package/dist/codegen-CYM3Zqrf.js +605 -0
  28. package/dist/codegen-ChBi_hVa.js +2 -0
  29. package/dist/codegenCommand-DC6w2tNZ.js +30 -0
  30. package/dist/codemodRunner-DRRqXR74.js +5243 -0
  31. package/dist/commandRunner-BLAEFLjp.js +47 -0
  32. package/dist/commands-gutsz-Ac.js +808 -0
  33. package/dist/connectionConfig-UFlIEiys.js +66 -0
  34. package/dist/dashboardCommand-3YG8p-UA.js +25 -0
  35. package/dist/dataCommand-qL0r7fPO.js +535 -0
  36. package/dist/dataProfile-dW-PsfLB.js +15 -0
  37. package/dist/{dbCommand-DTLKAfbA.js → dbCommand-B6X0FZbq.js} +551 -401
  38. package/dist/dbCommand-CpdKLeQq.js +2 -0
  39. package/dist/{dev-C_P8FLSx.js → dev-CerMd0mW.js} +3122 -3129
  40. package/dist/dev-CoG-ZPx8.js +3 -0
  41. package/dist/devActivity-Dx_3nnGv.js +100 -0
  42. package/dist/devActivity.js +1 -1
  43. package/dist/dialectDriver-CgXnDfec.js +39 -0
  44. package/dist/discover-C9XKJDco.js +25 -0
  45. package/dist/doctorCommand-BvqGBwNG.js +2 -0
  46. package/dist/{checkCommand-xGhRFFg2.js → doctorCommand-CnDXQxfa.js} +520 -1278
  47. package/dist/dormancyCommand-Dszo57d6.js +69 -0
  48. package/dist/e2eCmd-BRabZww-.js +147 -0
  49. package/dist/embeddingsCommand-C0sKVRo1.js +73 -0
  50. package/dist/envCommand-DPgdV1Bq.js +60 -0
  51. package/dist/evalCommand-6RUfPen4.js +118 -0
  52. package/dist/evolveCommand-DHpkgjgH.js +281 -0
  53. package/dist/fileTaxonomy-CJfgOllU.js +457 -0
  54. package/dist/frameworkTableAssembly-BGHmck-x.js +2 -0
  55. package/dist/{frameworkTableAssembly-BwIrO5nv.js → frameworkTableAssembly-DkkP6BgC.js} +184 -148
  56. package/dist/generateCommand-oibemh97.js +147 -0
  57. package/dist/index.d.ts +45 -0
  58. package/dist/index.js +4 -3
  59. package/dist/infoCommand-BJw9nLUR.js +60 -0
  60. package/dist/{inspect-BUUjt773.js → inspect-CBqFtAKA.js} +82 -40
  61. package/dist/inspect-C_T_WGvl.js +2 -0
  62. package/dist/inspectCmd-Bppy-GGw.js +224 -0
  63. package/dist/inspectFetch-Cm8_wVvp.js +151 -0
  64. package/dist/inspectMetrics-CfdKLh6t.js +72 -0
  65. package/dist/loadEnv-D9nEOClM.js +44 -0
  66. package/dist/logFileSink-C_D2wRN1.js +105 -0
  67. package/dist/logsCmd-CCca3KRZ.js +260 -0
  68. package/dist/manifestBuild-ChsKAhmn.js +2 -0
  69. package/dist/{manifestBuild-BLrVuSlM.js → manifestBuild-sxpwdKY1.js} +1 -1
  70. package/dist/metaCommands-7MJfZ5cf.js +196 -0
  71. package/dist/migrate-CBwOt_iV.js +83 -0
  72. package/dist/mssqlClusterPatch-_4cE_nun.js +44 -0
  73. package/dist/newCommand-COWOJ1_E.js +156 -0
  74. package/dist/nodeEnvironment-cGFAj1J8.js +28 -0
  75. package/dist/packageCommand-Cug_3Ogl.js +271 -0
  76. package/dist/pageConvention-cEiRxdab.js +5 -0
  77. package/dist/privacyCommand-C-Df56U_.js +146 -0
  78. package/dist/projectScaffold-DmzEKHib.js +2 -0
  79. package/dist/projectScaffold-LMMtaavR.js +814 -0
  80. package/dist/renderModeScan-D7J1B7Kw.js +105 -0
  81. package/dist/renderProfile-1OWWAAtx.js +81 -0
  82. package/dist/runtimeRegistry-DMeKfTHP.js +81 -0
  83. package/dist/runtimeTrace-CRxalXTs.js +91 -0
  84. package/dist/scheduleCmd--jksTrf6.js +69 -0
  85. package/dist/scheduleManifestCmd-D2x0CTTY.js +249 -0
  86. package/dist/schemaIr-UJybUUZW.js +103 -0
  87. package/dist/{sdkgen-C81QIkiL.js → sdkgen-CYJscZC7.js} +111 -209
  88. package/dist/seedRunner-TFHHiToI.js +329 -0
  89. package/dist/serveCommand-B_isw7q4.js +1647 -0
  90. package/dist/serveCommand-DOvbgRnQ.js +2 -0
  91. package/dist/serveEntry.js +5 -5
  92. package/dist/serverlessCommand-CfJZy6dS.js +482 -0
  93. package/dist/start-9LiUOfES.js +1087 -0
  94. package/dist/start-B-9Nsp-S.js +3 -0
  95. package/dist/startEntry.js +2 -2
  96. package/dist/staticCommand-Dr2M6tpU.js +304 -0
  97. package/dist/storageCommand-Co6NfLqN.js +42 -0
  98. package/dist/templates-De8IR5-c.js +102 -0
  99. package/dist/test-CI6iDsYc.js +115 -0
  100. package/dist/tracesCmd-CkEZQrtt.js +232 -0
  101. package/dist/tsconfigPaths-BWXBWgcl.js +107 -0
  102. package/dist/tsxLoader-EuXmSJ1K.js +51 -0
  103. package/dist/typecheckCommand-BlsWiCNq.js +61 -0
  104. package/dist/updateCommand-Bkptutss.js +585 -0
  105. package/dist/updateCommand-us1_hdIC.js +2 -0
  106. package/dist/{inspectMetrics-1xzTKAFx.js → webDev-CBYvPqQr.js} +975 -1559
  107. package/dist/webDev-Cg-fFiyd2.js +2 -0
  108. package/dist/webhookDiscovery-CrGAfhIG.js +2 -0
  109. package/dist/webhookDiscovery-D7VaeMlz.js +51 -0
  110. package/dist/webhooksCommand-CID96Rga.js +267 -0
  111. package/dist/workflowsCmd-D1VTmLMY.js +608 -0
  112. package/package.json +179 -17
  113. package/templates/AGENTS.core.md +58 -3
  114. package/templates/AGENTS.md +64 -7
  115. package/templates/agent-docs/_index.md +6 -4
  116. package/templates/agent-docs/_manifest.json +22 -5
  117. package/templates/agent-docs/ai.md +370 -0
  118. package/templates/agent-docs/authentication.md +265 -31
  119. package/templates/agent-docs/caching.md +6 -0
  120. package/templates/agent-docs/cli.md +794 -50
  121. package/templates/agent-docs/data.md +550 -11
  122. package/templates/agent-docs/database/migrations.md +174 -25
  123. package/templates/agent-docs/database/misc.md +156 -40
  124. package/templates/agent-docs/database/querying.md +19 -1
  125. package/templates/agent-docs/database/scaling.md +60 -0
  126. package/templates/agent-docs/database/schema.md +1 -0
  127. package/templates/agent-docs/database/seedsdialects.md +208 -19
  128. package/templates/agent-docs/database/transactions.md +68 -0
  129. package/templates/agent-docs/deployment.md +69 -3
  130. package/templates/agent-docs/introduction.md +87 -16
  131. package/templates/agent-docs/local-first-mobile.md +79 -4
  132. package/templates/agent-docs/multi-tenancy.md +95 -20
  133. package/templates/agent-docs/observability.md +58 -3
  134. package/templates/agent-docs/plugins/ai-flows.md +161 -2
  135. package/templates/agent-docs/plugins/analytics-postgres.md +1 -1
  136. package/templates/agent-docs/plugins/audit.md +37 -1
  137. package/templates/agent-docs/plugins/auth-social.md +143 -0
  138. package/templates/agent-docs/plugins/auth-workos.md +4 -2
  139. package/templates/agent-docs/plugins/auth.md +131 -6
  140. package/templates/agent-docs/plugins/billing.md +132 -15
  141. package/templates/agent-docs/plugins/cdc-out.md +46 -7
  142. package/templates/agent-docs/plugins/clickhouse.md +1 -1
  143. package/templates/agent-docs/plugins/duckdb.md +1 -1
  144. package/templates/agent-docs/plugins/flags.md +132 -0
  145. package/templates/agent-docs/plugins/governance.md +105 -7
  146. package/templates/agent-docs/plugins/multitenancy.md +9 -4
  147. package/templates/agent-docs/plugins/presence.md +13 -2
  148. package/templates/agent-docs/plugins/ratelimit.md +9 -0
  149. package/templates/agent-docs/plugins/search.md +157 -6
  150. package/templates/agent-docs/plugins/sso-saml.md +47 -8
  151. package/templates/agent-docs/plugins/webhooks.md +105 -0
  152. package/templates/agent-docs/plugins.md +150 -18
  153. package/templates/agent-docs/reference.md +60 -3
  154. package/templates/agent-docs/releases.md +1117 -0
  155. package/templates/agent-docs/routing.md +43 -25
  156. package/templates/agent-docs/scheduling.md +10 -0
  157. package/templates/agent-docs/schema-driven-ui.md +92 -12
  158. package/templates/agent-docs/security.md +426 -0
  159. package/templates/agent-docs/templates/apibackends.md +87 -18
  160. package/templates/agent-docs/templates/appshells.md +32 -14
  161. package/templates/agent-docs/templates/overview.md +13 -8
  162. package/templates/agent-docs/testing.md +211 -14
  163. package/templates/agent-docs/whats-new.md +1700 -104
  164. package/templates/agent-docs/workflows.md +130 -14
  165. package/templates/apps/api-ai/actions/summarize.action.ts +11 -0
  166. package/templates/apps/api-ai/package.json +8 -7
  167. package/templates/apps/api-auth/actions/me.action.ts +13 -0
  168. package/templates/apps/api-auth/package.json +9 -8
  169. package/templates/apps/api-backend/mutations/notes.create.mutation.ts +9 -0
  170. package/templates/apps/api-backend/package.json +12 -8
  171. package/templates/apps/api-backend/queries/notes.query.ts +30 -8
  172. package/templates/apps/api-backend-deactivation/actions/users.get.action.ts +14 -0
  173. package/templates/apps/api-backend-deactivation/mutations/users.create.mutation.ts +8 -0
  174. package/templates/apps/api-backend-deactivation/mutations/users.deactivate.mutation.ts +10 -0
  175. package/templates/apps/api-backend-deactivation/package.json +8 -7
  176. package/templates/apps/api-backend-mail/actions/sendWelcome.action.ts +17 -0
  177. package/templates/apps/api-backend-mail/mutations/notes.create.mutation.ts +9 -0
  178. package/templates/apps/api-backend-mail/package.json +9 -8
  179. package/templates/apps/api-backend-mail/queries/notes.query.ts +30 -8
  180. package/templates/apps/api-backend-mariadb/.env.example +14 -0
  181. package/templates/apps/api-backend-mariadb/mutations/notes.create.mutation.ts +9 -0
  182. package/templates/apps/api-backend-mariadb/package.json +10 -9
  183. package/templates/apps/api-backend-mariadb/queries/notes.query.ts +30 -8
  184. package/templates/apps/api-backend-sqlite/.env.example +14 -0
  185. package/templates/apps/api-backend-sqlite/mutations/notes.create.mutation.ts +9 -0
  186. package/templates/apps/api-backend-sqlite/package.json +9 -8
  187. package/templates/apps/api-backend-sqlite/queries/notes.query.ts +30 -8
  188. package/templates/apps/api-backend-storage/actions/uploadAvatar.action.ts +13 -0
  189. package/templates/apps/api-backend-storage/actions/uploadDocument.action.ts +12 -0
  190. package/templates/apps/api-backend-storage/mutations/notes.create.mutation.ts +9 -0
  191. package/templates/apps/api-backend-storage/package.json +9 -8
  192. package/templates/apps/api-backend-storage/queries/notes.query.ts +30 -8
  193. package/templates/apps/api-cms/actions/content.get.action.ts +7 -0
  194. package/templates/apps/api-cms/actions/content.types.action.ts +6 -0
  195. package/templates/apps/api-cms/actions/me.action.ts +13 -0
  196. package/templates/apps/api-cms/app.config.ts +19 -0
  197. package/templates/apps/api-cms/authz.ts +63 -0
  198. package/templates/apps/api-cms/mutations/content.publish.mutation.ts +10 -0
  199. package/templates/apps/api-cms/mutations/content.saveDraft.mutation.ts +10 -0
  200. package/templates/apps/api-cms/mutations/content.unpublish.mutation.ts +5 -0
  201. package/templates/apps/api-cms/package.json +11 -10
  202. package/templates/apps/api-cms/queries/content.list.query.ts +23 -7
  203. package/templates/apps/api-cms/tests/accessDecisions.test.ts +121 -0
  204. package/templates/apps/api-cms/tests/content.descriptors.test.ts +8 -4
  205. package/templates/apps/api-collab/mutations/documents.create.mutation.ts +8 -0
  206. package/templates/apps/api-collab/mutations/documents.setBody.mutation.ts +13 -0
  207. package/templates/apps/api-collab/package.json +9 -8
  208. package/templates/apps/api-collab/queries/documents.query.ts +19 -7
  209. package/templates/apps/api-data-advanced/package.json +9 -8
  210. package/templates/apps/api-data-advanced/queries/authors.withBooks.query.ts +12 -0
  211. package/templates/apps/api-data-advanced/queries/books.search.query.ts +9 -0
  212. package/templates/apps/api-durable/mutations/orders.approve.mutation.ts +17 -0
  213. package/templates/apps/api-durable/mutations/orders.place.mutation.ts +9 -0
  214. package/templates/apps/api-durable/package.json +9 -8
  215. package/templates/apps/api-feature-flags/actions/notes.export.action.ts +16 -0
  216. package/templates/apps/api-feature-flags/mutations/notes.create.mutation.ts +12 -0
  217. package/templates/apps/api-feature-flags/package.json +10 -9
  218. package/templates/apps/api-governance/README.md +30 -8
  219. package/templates/apps/api-governance/actions/profiles.get.action.server.ts +17 -1
  220. package/templates/apps/api-governance/actions/profiles.get.action.ts +29 -4
  221. package/templates/apps/api-governance/database/schema.ts +16 -4
  222. package/templates/apps/api-governance/mutations/profiles.create.mutation.ts +12 -0
  223. package/templates/apps/api-governance/package.json +9 -8
  224. package/templates/apps/api-kv/actions/sync.pull.action.ts +15 -0
  225. package/templates/apps/api-kv/actions/sync.reset.action.ts +13 -0
  226. package/templates/apps/api-kv/actions/sync.status.action.ts +7 -0
  227. package/templates/apps/api-kv/package.json +9 -8
  228. package/templates/apps/api-kv/queries/events.list.query.ts +19 -8
  229. package/templates/apps/api-moderation/mutations/comments.create.mutation.ts +11 -0
  230. package/templates/apps/api-moderation/mutations/posts.create.mutation.ts +13 -0
  231. package/templates/apps/api-moderation/package.json +9 -8
  232. package/templates/apps/api-observability/mutations/notes.create.mutation.ts +8 -0
  233. package/templates/apps/api-observability/package.json +9 -8
  234. package/templates/apps/api-observability/queries/notes.list.query.ts +13 -0
  235. package/templates/apps/api-ratelimit/mutations/notes.create.mutation.ts +14 -0
  236. package/templates/apps/api-ratelimit/package.json +9 -8
  237. package/templates/apps/api-rbac/package.json +9 -8
  238. package/templates/apps/api-rest/package.json +8 -7
  239. package/templates/apps/api-saas/mutations/projects.create.mutation.ts +13 -0
  240. package/templates/apps/api-saas/package.json +12 -11
  241. package/templates/apps/api-saas/queries/projects.list.query.ts +11 -0
  242. package/templates/apps/api-saas-starter/actions/me.action.ts +13 -0
  243. package/templates/apps/api-saas-starter/app.config.ts +19 -0
  244. package/templates/apps/api-saas-starter/authz.ts +75 -0
  245. package/templates/apps/api-saas-starter/mutations/invites.create.mutation.ts +10 -0
  246. package/templates/apps/api-saas-starter/mutations/projects.create.mutation.ts +9 -0
  247. package/templates/apps/api-saas-starter/package.json +15 -11
  248. package/templates/apps/api-saas-starter/queries/invites.list.query.ts +19 -8
  249. package/templates/apps/api-saas-starter/queries/projects.list.query.ts +18 -7
  250. package/templates/apps/api-saas-starter/tests/accessDecisions.test.ts +135 -0
  251. package/templates/apps/api-search/mutations/articles.create.mutation.ts +14 -0
  252. package/templates/apps/api-search/package.json +9 -8
  253. package/templates/apps/api-search/queries/articles.list.query.ts +21 -8
  254. package/templates/apps/api-status/README.md +10 -3
  255. package/templates/apps/api-status/app.config.ts +8 -3
  256. package/templates/apps/api-status/authz.ts +5 -3
  257. package/templates/apps/api-status/package.json +9 -8
  258. package/templates/apps/api-status/queries/components.list.query.ts +14 -6
  259. package/templates/apps/api-status/queries/incidents.live.query.ts +23 -10
  260. package/templates/apps/api-status/queries/updates.list.query.ts +16 -9
  261. package/templates/apps/api-status/tests/status.test.ts +9 -1
  262. package/templates/apps/api-versioning/actions/documents.asOf.action.ts +12 -0
  263. package/templates/apps/api-versioning/actions/documents.history.action.ts +13 -0
  264. package/templates/apps/api-versioning/mutations/documents.create.mutation.ts +9 -0
  265. package/templates/apps/api-versioning/mutations/documents.update.mutation.ts +12 -0
  266. package/templates/apps/api-versioning/package.json +9 -8
  267. package/templates/apps/api-webhooks/mutations/orders.fulfill.mutation.ts +16 -0
  268. package/templates/apps/api-webhooks/package.json +10 -9
  269. package/templates/apps/api-webhooks/queries/orders.list.query.ts +10 -0
  270. package/templates/apps/changelog/package.json +8 -6
  271. package/templates/apps/edge-functions/package.json +2 -2
  272. package/templates/apps/frontend-admin/package.json +10 -8
  273. package/templates/apps/frontend-admin/src/lib/admin.ts +20 -9
  274. package/templates/apps/frontend-admin/src/locales/de.ts +11 -1
  275. package/templates/apps/frontend-admin/src/locales/en.ts +13 -1
  276. package/templates/apps/frontend-admin/src/pages/admin/[entity]/page.tsx +65 -22
  277. package/templates/apps/frontend-admin/src/pages/admin/entity.test.tsx +130 -23
  278. package/templates/apps/frontend-admin/src/pages/admin/layout.tsx +3 -2
  279. package/templates/apps/frontend-admin/src/pages/admin/page.test.tsx +19 -2
  280. package/templates/apps/frontend-admin/src/pages/admin/page.tsx +9 -4
  281. package/templates/apps/frontend-app/app.config.ts +4 -3
  282. package/templates/apps/frontend-app/package.json +11 -8
  283. package/templates/apps/frontend-app/src/lib/api.ts +25 -0
  284. package/templates/apps/frontend-app/src/pages/page.test.tsx +130 -82
  285. package/templates/apps/frontend-app/src/pages/page.tsx +14 -18
  286. package/templates/apps/frontend-auth/package.json +10 -8
  287. package/templates/apps/frontend-blank/package.json +9 -7
  288. package/templates/apps/frontend-cms/package.json +11 -9
  289. package/templates/apps/frontend-collab/package.json +12 -9
  290. package/templates/apps/frontend-collab/src/pages/page.test.tsx +122 -78
  291. package/templates/apps/frontend-contact/package.json +9 -7
  292. package/templates/apps/frontend-dashboard/package.json +9 -7
  293. package/templates/apps/frontend-docs/package.json +9 -7
  294. package/templates/apps/frontend-i18n/package.json +8 -6
  295. package/templates/apps/frontend-landing/package.json +9 -7
  296. package/templates/apps/frontend-portal/package.json +10 -8
  297. package/templates/apps/frontend-saas/app.config.ts +10 -6
  298. package/templates/apps/frontend-saas/package.json +10 -8
  299. package/templates/apps/frontend-saas/src/lib/api.ts +27 -32
  300. package/templates/apps/frontend-saas/src/pages/dashboard/billing/page.tsx +7 -8
  301. package/templates/apps/frontend-saas/src/pages/dashboard/page.test.tsx +27 -3
  302. package/templates/apps/frontend-saas/src/pages/dashboard/page.tsx +4 -4
  303. package/templates/apps/frontend-saas/src/pages/dashboard/team/page.tsx +3 -4
  304. package/templates/apps/frontend-spa/package.json +9 -7
  305. package/templates/apps/frontend-ssr/package.json +9 -7
  306. package/templates/apps/frontend-ssr-api/package.json +10 -8
  307. package/templates/apps/frontend-static-blog/package.json +8 -6
  308. package/templates/apps/frontend-status/package.json +10 -8
  309. package/templates/apps/mobile-app/README.md +1 -0
  310. package/templates/apps/mobile-app/package.json +4 -2
  311. package/templates/apps/mobile-app/src/app/index.tsx +22 -12
  312. package/templates/apps/mobile-app/src/app/orders/[id].tsx +1 -1
  313. package/templates/apps/mobile-app/src/lib/api.ts +34 -0
  314. package/templates/apps/mobile-app/voltro.mobile.ts +4 -2
  315. package/templates/baselines/bare/.env.example +14 -0
  316. package/templates/baselines/bare/baseline.json +4 -4
  317. package/templates/baselines/compose/.env.example +14 -0
  318. package/templates/baselines/compose/README.md +1 -1
  319. package/templates/baselines/compose/baseline.json +5 -5
  320. package/templates/baselines/compose-mariadb/.env.example +14 -0
  321. package/templates/baselines/compose-mariadb/README.md +1 -1
  322. package/templates/baselines/compose-mariadb/baseline.json +5 -5
  323. package/templates/baselines/helm/.env.example +14 -0
  324. package/templates/baselines/helm/baseline.json +4 -4
  325. package/dist/apiBuild-C-x9YacA.js +0 -2
  326. package/dist/checkCommand-DRovTKza.js +0 -2
  327. package/dist/commands-CJfepbm4.js +0 -11541
  328. package/dist/dbCommand-b1gum4td.js +0 -2
  329. package/dist/dev-iiMtlkfs.js +0 -3
  330. package/dist/devActivity-BhIu6ncs.js +0 -159
  331. package/dist/frameworkTableAssembly-D-EebUQX.js +0 -2
  332. package/dist/inspect-mmBuRXmy.js +0 -2
  333. package/dist/manifestBuild-Dj8Jjoto.js +0 -2
  334. package/dist/seedRunner-Bqxgp7HZ.js +0 -230
  335. package/dist/serveCommand-DdaM4Hup.js +0 -1608
  336. package/dist/start-C0koT0UO.js +0 -1084
  337. /package/templates/apps/api-ai/actions/{summarize.action.server.tsx → summarize.action.server.ts} +0 -0
@@ -69,8 +69,9 @@ export default function AdminLayout({ children }: { readonly children: ReactNode
69
69
  )
70
70
 
71
71
  return (
72
- // PermissionProvider feeds the current subject's scopes to useCan across the
73
- // admin. DEMO scopes here swap for your session's scopes (see lib/admin.ts).
72
+ // PermissionProvider feeds the current subject's scopes to useAccessDecision
73
+ // across the admin, which judges them against each procedure's OWN declared
74
+ // guards. DEMO scopes here — swap for your session's (see lib/admin.ts).
74
75
  <PermissionProvider scopes={DEMO_SCOPES}>
75
76
  <div className="admin">
76
77
  <aside>
@@ -57,11 +57,16 @@ describe('admin overview — config', () => {
57
57
  })
58
58
 
59
59
  describe('admin overview — render', () => {
60
+ const spec = (over: Record<string, unknown>): Record<string, unknown> => ({
61
+ columns: [], serverOnlyColumns: [], sensitiveColumns: [], reactive: false, editable: true,
62
+ list: {}, create: {}, update: {}, delete: {}, ...over,
63
+ })
64
+
60
65
  test('renders a linked card per entity with its column count and ops', () => {
61
66
  capabilityManifest.mockReturnValue({ manifest: {}, loading: false })
62
67
  entityAdmins.mockReturnValue([
63
- { table: 'todos', columns: ['id', 'title'], reactive: true, listTag: 'todos.list', createTag: 'todos.create' },
64
- { table: 'notes', columns: ['id'], reactive: false, listTag: 'notes.list' },
68
+ spec({ table: 'todos', columns: ['id', 'title'], reactive: true, list: { tag: 'todos.list' }, create: { tag: 'todos.create' } }),
69
+ spec({ table: 'notes', columns: ['id'], list: { tag: 'notes.list' } }),
65
70
  ])
66
71
 
67
72
  render(createElement(AdminOverview))
@@ -72,6 +77,18 @@ describe('admin overview — render', () => {
72
77
  expect(container.textContent).toContain('list · create')
73
78
  })
74
79
 
80
+ test('says how many columns are withheld rather than just showing fewer', () => {
81
+ // `columns` already excludes `.serverOnly()`, so a card that only printed the
82
+ // count would silently under-report the table. "2 columns" and "2 columns +
83
+ // 1 hidden" are different facts about the same entity.
84
+ capabilityManifest.mockReturnValue({ manifest: {}, loading: false })
85
+ entityAdmins.mockReturnValue([
86
+ spec({ table: 'users', columns: ['id', 'email'], serverOnlyColumns: ['pwHash'], list: { tag: 'users.list' } }),
87
+ ])
88
+ render(createElement(AdminOverview))
89
+ expect(container.textContent).toContain('1 server-only column, hidden')
90
+ })
91
+
75
92
  test('shows the error banner when the capability map is unreachable', () => {
76
93
  capabilityManifest.mockReturnValue({ manifest: undefined, loading: false, error: 'boom' })
77
94
  entityAdmins.mockReturnValue([])
@@ -39,19 +39,24 @@ export default function AdminOverview(): ReactNode {
39
39
  <div className="cards" style={{ marginTop: '1.25rem' }}>
40
40
  {entities.map((e) => {
41
41
  const ops = [
42
- e.listTag && 'list',
43
- e.createTag && 'create',
44
- e.updateTag && 'edit',
45
- e.deleteTag && 'delete',
42
+ e.list.tag && 'list',
43
+ e.create.tag && 'create',
44
+ e.update.tag && 'edit',
45
+ e.delete.tag && 'delete',
46
46
  ].filter(Boolean)
47
47
  return (
48
48
  <a className="card" key={e.table} href={`/admin/${e.table}`}>
49
49
  <div className="value">{e.table}</div>
50
50
  <div className="label">
51
+ {/* `columns` already excludes `.serverOnly()` — the count is
52
+ what an admin can actually show, not what the table has. */}
51
53
  <T id="overview.columns" values={{ count: e.columns.length }} />
52
54
  {e.reactive ? <T id="overview.reactiveSuffix" /> : null}
53
55
  </div>
54
56
  <div className="meta">{ops.length > 0 ? ops.join(' · ') : <T id="overview.readonly" />}</div>
57
+ {e.serverOnlyColumns.length > 0 ? (
58
+ <div className="meta"><T id="overview.serverOnly" values={{ count: e.serverOnlyColumns.length }} /></div>
59
+ ) : null}
55
60
  </a>
56
61
  )
57
62
  })}
@@ -31,9 +31,10 @@ export default {
31
31
  // more languages by adding a code here + a `src/locales/<code>.ts`.
32
32
  locales: ['en', 'de'] as const,
33
33
  defaultLocale: 'en' as const,
34
- // The api this frontend consumes. The NAME ('app') is the lookup key the
35
- // hooks use `useSubscription('app', 'notes.list')`, `useMutation('app',
36
- // 'notes.create')`. The `package` resolves the sibling api in this project
34
+ // The api this frontend consumes. The NAME ('app') is the lookup key — spelled
35
+ // ONCE, in `src/lib/api.ts`'s `createHooks<AppProcedures>('app')`, after which
36
+ // pages just call `useSubscription('notes.list')` and
37
+ // `useMutation('notes.create')`. The `package` resolves the sibling api
37
38
  // (scaffold it with `--api=api-backend`); its port is auto-discovered from
38
39
  // its own app.config.ts, and codegen pulls its typed rpc surface so the
39
40
  // hooks are end-to-end typed. Add more entries to consume more apis.
@@ -12,21 +12,24 @@
12
12
  "typecheck": "tsc --noEmit"
13
13
  },
14
14
  "dependencies": {
15
- "@{{projectName}}/api": "0.33.0",
16
- "@voltro/cli": "0.33.0",
17
- "@voltro/client": "0.33.0",
18
- "@voltro/env": "0.33.0",
19
- "@voltro/i18n": "0.33.0",
20
- "@voltro/ui-shadcn": "0.33.0",
21
- "@voltro/web": "0.33.0",
15
+ "@{{projectName}}/api": "0.34.0",
16
+ "@voltro/cli": "0.34.0",
17
+ "@voltro/client": "0.34.0",
18
+ "@voltro/env": "0.34.0",
19
+ "@voltro/i18n": "0.34.0",
20
+ "@voltro/ui-shadcn": "0.34.0",
21
+ "@voltro/web": "0.34.0",
22
22
  "react": "^19.0.0",
23
23
  "react-dom": "^19.0.0"
24
24
  },
25
25
  "devDependencies": {
26
- "@voltro/devtools": "0.33.0",
26
+ "@voltro/devtools": "0.34.0",
27
+ "@voltro/testing": "0.34.0",
27
28
  "@types/react": "^19.0.0",
28
29
  "@types/react-dom": "^19.0.0",
30
+ "jsdom": "^30.0.0",
29
31
  "typescript": "^6.0.3",
32
+ "@vitest/coverage-v8": "^4.1.10",
30
33
  "vitest": "^4.1.10"
31
34
  }
32
35
  }
@@ -0,0 +1,25 @@
1
+ // The typed hook surface for the `app` api — bound ONCE, here, and imported by
2
+ // every page instead of `@voltro/client`.
3
+ //
4
+ // `AppProcedures` is emitted by codegen into the api's `rpcGroup.generated.ts`:
5
+ // a type-level map of every rpc tag to the descriptor behind it. Feeding it to
6
+ // `createHooks` makes the tag a literal union and the input/output types INFER:
7
+ //
8
+ // const { data } = useSubscription('notes.list') // rows, typed
9
+ // const create = useMutation('notes.create') // input + output typed
10
+ //
11
+ // So no page declares a row `interface` mirroring the api any more. The old
12
+ // `useSubscription<ReadonlyArray<Note>>('app', 'notes.list')` spelled the api
13
+ // name at every call site, left the tag an unchecked string (a typo was a
14
+ // runtime error), and the `<T>` was an ASSERTION nothing compared against the
15
+ // server. All three are compile-checked now.
16
+ //
17
+ // `'app'` is the key this web app gave the api in `app.config.ts` -> `apis`.
18
+ // `import type` is erased, so the binding costs the browser bundle nothing.
19
+ import { createHooks } from '@voltro/client'
20
+ import type { AppProcedures } from '@{{projectName}}/api/rpcGroup'
21
+
22
+ // Destructured on purpose - `react-hooks/rules-of-hooks` only treats a member
23
+ // call as a hook when the object is PascalCase, so top-level `useX` identifiers
24
+ // are what keep the React lint rules working at every call site.
25
+ export const { useSubscription, useMutation, useAction } = createHooks<AppProcedures>('app')
@@ -1,50 +1,70 @@
1
1
  // @vitest-environment jsdom
2
2
  //
3
- // `/` — the reactive notes page. It binds `useSubscription('app','notes.list')`
4
- // + `useMutation('app','notes.create')` from @voltro/client. We mock that module
5
- // so the render is deterministic and assert the observable output: loading /
6
- // empty / list states, optimistic-row styling, subscription-error banner, and
7
- // the submit path calling create.mutate with the trimmed title + body + tenant.
3
+ // `/` — the reactive notes page, driven through `makeVoltroTestClient`
4
+ // (`@voltro/testing/client`) rather than a `vi.mock` of anything.
5
+ //
6
+ // WHY THE HARNESS HERE. The old suite faked `src/lib/api` — the app's own typed
7
+ // binding with the argument that mocking `@voltro/client` underneath would be
8
+ // the wrong seam. That argument was right about the seam and reached the wrong
9
+ // conclusion, because the harness does not mock `@voltro/client` at all: it
10
+ // PROVIDES the runtime context the real hooks read. So `createHooks` runs, the
11
+ // real `useSubscription` / `useMutation` run, and the fake stops at the
12
+ // transport — one layer below the binding rather than on top of it.
13
+ //
14
+ // What that buys, concretely: the page is the framework's flagship example of
15
+ // the reactive loop, and every "different data" case here used to be a fresh
16
+ // mount with a different mock return. Nothing pushed a delta into a MOUNTED
17
+ // tree, which is the one behaviour the page exists to demonstrate. The last two
18
+ // cases do, via `setSubscription`.
8
19
 
9
- import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'
20
+ import { afterEach, beforeEach, describe, expect, test } from 'vitest'
10
21
  import { act, createElement, type ReactNode } from 'react'
11
22
  import { createRoot, type Root } from 'react-dom/client'
12
23
  import { I18nProvider } from '@voltro/i18n'
24
+ import { makeVoltroTestClient, type VoltroTestClient } from '@voltro/testing/client'
13
25
  import enCatalog from '../locales/en'
14
-
15
- interface SubState {
16
- data: unknown
17
- revision?: number
18
- emittedAt?: number
19
- error?: unknown
20
- pendingPatches?: number
21
- }
22
-
23
- const useSubscription = vi.fn<() => SubState>()
24
- const mutate = vi.fn<(input: unknown) => Promise<unknown>>()
25
- const useMutation = vi.fn(() => ({ mutate, pending: false, error: undefined, data: undefined }))
26
-
27
- vi.mock('@voltro/client', () => ({
28
- useSubscription: (...a: unknown[]) => useSubscription(...(a as [])),
29
- useMutation: (...a: unknown[]) => useMutation(...(a as [])),
30
- }))
31
-
32
- const { default: IndexPage } = await import('./page')
26
+ import IndexPage from './page'
33
27
 
34
28
  ;(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true
35
29
 
30
+ const LIST = 'notes.list'
31
+
36
32
  let container: HTMLDivElement
37
33
  let root: Root
34
+ let client: VoltroTestClient
35
+
36
+ const note = (over: Partial<{ id: string; title: string; body: string; optimistic: boolean }> = {}) => ({
37
+ id: over.id ?? 'n_1',
38
+ title: over.title ?? 'first',
39
+ body: over.body ?? '',
40
+ done: false,
41
+ tenantId: 'acme',
42
+ createdAt: new Date(0),
43
+ ...(over.optimistic === true ? { optimistic: true } : {}),
44
+ })
45
+
46
+ /** `notes.create` resolves by default; a case that needs the failure path
47
+ * overrides it. The harness records every call on `client.calls`. */
48
+ const harness = (
49
+ subscriptions: Readonly<Record<string, unknown>> = {},
50
+ create: (input: unknown) => unknown = () => undefined,
51
+ ): VoltroTestClient =>
52
+ makeVoltroTestClient({ subscriptions, mutations: { 'notes.create': create } })
38
53
 
39
54
  // The page uses <T>/useTFn, so it needs an <I18nProvider>. Wrap with the real
40
55
  // English catalog (the framework auto-wires this provider in the running app).
41
- const render = (node: ReactNode): void => {
56
+ const render = (): void => {
42
57
  container = document.createElement('div')
43
58
  document.body.appendChild(container)
44
59
  act(() => {
45
60
  root = createRoot(container)
46
61
  root.render(
47
- createElement(I18nProvider, { locale: 'en', messages: enCatalog, defaultLocale: 'en', children: node }),
62
+ createElement(I18nProvider, {
63
+ locale: 'en',
64
+ messages: enCatalog,
65
+ defaultLocale: 'en',
66
+ children: createElement(client.Provider, null, createElement(IndexPage) as ReactNode),
67
+ }),
48
68
  )
49
69
  })
50
70
  }
@@ -61,13 +81,14 @@ const setInput = (el: HTMLInputElement | HTMLTextAreaElement, value: string): vo
61
81
  })
62
82
  }
63
83
 
64
- beforeEach(() => {
65
- useSubscription.mockReset()
66
- mutate.mockReset()
67
- mutate.mockResolvedValue(undefined)
68
- useMutation.mockClear()
69
- useMutation.mockReturnValue({ mutate, pending: false, error: undefined, data: undefined })
70
- })
84
+ const submit = async (): Promise<void> => {
85
+ const form = container.querySelector('form') as HTMLFormElement
86
+ await act(async () => {
87
+ form.dispatchEvent(new Event('submit', { bubbles: true, cancelable: true }))
88
+ })
89
+ }
90
+
91
+ beforeEach(() => { client = harness() })
71
92
 
72
93
  afterEach(() => {
73
94
  if (root) act(() => root.unmount())
@@ -76,53 +97,43 @@ afterEach(() => {
76
97
  })
77
98
 
78
99
  describe('index — subscription states', () => {
79
- test('shows the loading hint before the first snapshot (data undefined)', () => {
80
- useSubscription.mockReturnValue({ data: undefined, revision: -1 })
81
- render(createElement(IndexPage))
100
+ test('shows the loading hint before the first snapshot', () => {
101
+ // A tag with no fixture IS the cold state — no stub had to invent one.
102
+ render()
82
103
  expect(container.textContent).toContain('loading…')
83
104
  expect(container.querySelectorAll('li')).toHaveLength(0)
84
105
  })
85
106
 
86
107
  test('shows the empty state once a snapshot arrives with no rows', () => {
87
- useSubscription.mockReturnValue({ data: [], revision: 0 })
88
- render(createElement(IndexPage))
108
+ client = harness({ [LIST]: [] })
109
+ render()
89
110
  expect(container.textContent).toContain('No notes yet')
90
111
  })
91
112
 
92
113
  test('renders each note with its title and body', () => {
93
- useSubscription.mockReturnValue({
94
- data: [
95
- { id: 'n_1', title: 'first', body: 'body one', done: false, tenantId: 'acme', createdAt: new Date(0) },
96
- { id: 'n_2', title: 'second', body: '', done: false, tenantId: 'acme', createdAt: new Date(0) },
97
- ],
98
- revision: 3,
99
- })
100
- render(createElement(IndexPage))
101
- const items = Array.from(container.querySelectorAll('li'))
102
- expect(items).toHaveLength(2)
114
+ client = harness({ [LIST]: [note({ id: 'n_1', title: 'first', body: 'body one' }), note({ id: 'n_2', title: 'second' })] })
115
+ render()
116
+ expect(container.querySelectorAll('li')).toHaveLength(2)
103
117
  expect(container.textContent).toContain('first')
104
118
  expect(container.textContent).toContain('body one')
105
119
  expect(container.textContent).toContain('second')
106
120
  })
107
121
 
108
- test('renders optimistic rows faintly + italicised', () => {
109
- useSubscription.mockReturnValue({
110
- data: [
111
- { id: 'n_opt', title: 'pending', body: '', done: false, tenantId: 'acme', createdAt: new Date(0), optimistic: true },
112
- ],
113
- revision: 1,
114
- pendingPatches: 1,
122
+ test('renders optimistic rows faintly + italicised, with the pending count', () => {
123
+ client = harness({ [LIST]: [] })
124
+ render()
125
+ act(() => {
126
+ client.setSubscription(LIST, [note({ id: 'n_opt', title: 'pending', optimistic: true })], { pendingPatches: 1 })
115
127
  })
116
- render(createElement(IndexPage))
117
128
  const li = container.querySelector('li')!
118
129
  expect(li.style.opacity).toBe('0.5')
119
130
  expect(li.style.fontStyle).toBe('italic')
120
131
  expect(container.textContent).toContain('1 pending')
121
132
  })
122
133
 
123
- test('renders the subscription error banner when error is set', () => {
124
- useSubscription.mockReturnValue({ data: undefined, error: 'boom', revision: -1 })
125
- render(createElement(IndexPage))
134
+ test('renders the subscription error banner when the stream fails', () => {
135
+ render()
136
+ act(() => { client.failSubscription(LIST, 'boom') })
126
137
  expect(container.textContent).toContain('subscription error')
127
138
  expect(container.textContent).toContain('boom')
128
139
  })
@@ -130,38 +141,75 @@ describe('index — subscription states', () => {
130
141
 
131
142
  describe('index — create form', () => {
132
143
  test('the Add button is disabled until a non-empty title is typed', () => {
133
- useSubscription.mockReturnValue({ data: [], revision: 0 })
134
- render(createElement(IndexPage))
144
+ client = harness({ [LIST]: [] })
145
+ render()
135
146
  const button = container.querySelector('button[type="submit"]') as HTMLButtonElement
136
147
  expect(button.disabled).toBe(true)
137
148
  setInput(container.querySelector('input') as HTMLInputElement, 'a note')
138
149
  expect(button.disabled).toBe(false)
139
150
  })
140
151
 
141
- test('submitting calls create.mutate with the trimmed title + body + tenant, then clears', async () => {
142
- useSubscription.mockReturnValue({ data: [], revision: 0 })
143
- render(createElement(IndexPage))
144
- const input = container.querySelector('input') as HTMLInputElement
145
- const textarea = container.querySelector('textarea') as HTMLTextAreaElement
146
- setInput(input, ' my title ')
147
- setInput(textarea, ' my body ')
148
- const form = container.querySelector('form') as HTMLFormElement
149
- await act(async () => {
150
- form.dispatchEvent(new Event('submit', { bubbles: true, cancelable: true }))
151
- })
152
- expect(mutate).toHaveBeenCalledWith({ tenantId: 'acme', title: 'my title', body: 'my body' })
153
- expect(input.value).toBe('')
154
- expect(textarea.value).toBe('')
152
+ test('submitting calls notes.create with the trimmed title + body + tenant, then clears', async () => {
153
+ client = harness({ [LIST]: [] })
154
+ render()
155
+ setInput(container.querySelector('input') as HTMLInputElement, ' my title ')
156
+ setInput(container.querySelector('textarea') as HTMLTextAreaElement, ' my body ')
157
+ await submit()
158
+
159
+ expect(client.calls).toEqual([
160
+ { kind: 'mutation', tag: 'notes.create', input: { tenantId: 'acme', title: 'my title', body: 'my body' } },
161
+ ])
162
+ expect((container.querySelector('input') as HTMLInputElement).value).toBe('')
163
+ expect((container.querySelector('textarea') as HTMLTextAreaElement).value).toBe('')
155
164
  })
156
165
 
157
166
  test('a whitespace-only title does not fire a mutation', async () => {
158
- useSubscription.mockReturnValue({ data: [], revision: 0 })
159
- render(createElement(IndexPage))
167
+ client = harness({ [LIST]: [] })
168
+ render()
160
169
  setInput(container.querySelector('input') as HTMLInputElement, ' ')
161
- const form = container.querySelector('form') as HTMLFormElement
162
- await act(async () => {
163
- form.dispatchEvent(new Event('submit', { bubbles: true, cancelable: true }))
170
+ await submit()
171
+ expect(client.calls).toEqual([])
172
+ })
173
+
174
+ test('a REJECTED create leaves the form cleared and the list untouched', async () => {
175
+ client = harness({ [LIST]: [] }, () => { throw new Error('TenantMismatch') })
176
+ render()
177
+ setInput(container.querySelector('input') as HTMLInputElement, 'nope')
178
+ await submit()
179
+ expect(client.calls).toHaveLength(1)
180
+ expect(container.textContent).toContain('No notes yet')
181
+ })
182
+ })
183
+
184
+ // The reactive loop itself — a server delta into a MOUNTED tree. This is what
185
+ // the page is a demonstration of, and what a per-mount mock cannot express.
186
+ describe('index — the reactive loop', () => {
187
+ test('a delta pushed by the server re-renders the list in place', () => {
188
+ client = harness({ [LIST]: [note({ id: 'n_1', title: 'first' })] })
189
+ render()
190
+ expect(container.querySelectorAll('li')).toHaveLength(1)
191
+
192
+ act(() => {
193
+ client.setSubscription(LIST, [note({ id: 'n_1', title: 'first' }), note({ id: 'n_2', title: 'second' })])
164
194
  })
165
- expect(mutate).not.toHaveBeenCalled()
195
+
196
+ expect(container.querySelectorAll('li')).toHaveLength(2)
197
+ expect(container.textContent).toContain('second')
198
+ })
199
+
200
+ test('the confirming delta clears the optimistic styling AND the pending count', () => {
201
+ client = harness({ [LIST]: [] })
202
+ render()
203
+ act(() => {
204
+ client.setSubscription(LIST, [note({ id: 'n_tmp', title: 'mine', optimistic: true })], { pendingPatches: 1 })
205
+ })
206
+ expect(container.querySelector('li')!.style.opacity).toBe('0.5')
207
+ expect(container.textContent).toContain('1 pending')
208
+
209
+ // The server confirms: the real row replaces the optimistic one.
210
+ act(() => { client.setSubscription(LIST, [note({ id: 'n_1', title: 'mine' })]) })
211
+
212
+ expect(container.querySelector('li')!.style.opacity).toBe('1')
213
+ expect(container.textContent).not.toContain('1 pending')
166
214
  })
167
215
  })
@@ -1,11 +1,15 @@
1
1
  // `/` — the notes page. Auto-mapped from src/pages/index.tsx by the
2
2
  // framework's file-convention page discovery.
3
3
  //
4
- // THIS is the reactive end-to-end loop. `useSubscription('app',
5
- // 'notes.list')` opens a live WebSocket subscription to the api's
6
- // `notes.list` query; `useMutation('app', 'notes.create')` invokes the
7
- // `notes.create` mutation. When the mutation commits, the server pushes a
8
- // delta and this list re-renders — no refetch, no polling.
4
+ // THIS is the reactive end-to-end loop. `useSubscription('notes.list')`
5
+ // opens a live WebSocket subscription to the api's `notes.list` query;
6
+ // `useMutation('notes.create')` invokes the `notes.create` mutation. When the
7
+ // mutation commits, the server pushes a delta and this list re-renders — no
8
+ // refetch, no polling.
9
+ //
10
+ // Both come from `src/lib/api.ts`, which binds them ONCE to this api's
11
+ // generated procedure map — so the tag autocompletes, a typo is a compile
12
+ // error, and the row type is inferred rather than declared here.
9
13
  //
10
14
  // Auto-optimistic: `notes.create` declares `target: { table: 'notes', op:
11
15
  // 'insert' }` server-side, so the framework prepends an optimistic row to
@@ -13,11 +17,11 @@
13
17
  // row (or reverts it) when the rpc resolves. There is no `.withOptimistic`,
14
18
  // no `useOptimistic`, no `startTransition` — just `create.mutate(input)`.
15
19
 
16
- import { useMutation, useSubscription } from '@voltro/client'
17
20
  import { useState, type FormEvent, type ReactNode } from 'react'
18
21
  import type { PageMeta } from '@voltro/web'
19
22
  import { T, useTFn } from '@voltro/i18n'
20
23
  import { getCatalog } from '../locales'
24
+ import { useMutation, useSubscription } from '../lib/api'
21
25
 
22
26
  // Locale-aware tab title: @voltro/web resolves meta({ locale }) from the
23
27
  // active locale (the `voltro:locale` cookie in this cookie-i18n app).
@@ -31,21 +35,13 @@ export const meta = ({ locale }: { readonly locale: string }): PageMeta => ({
31
35
  // submitted tenantId matches the caller's (a typed TenantMismatch otherwise).
32
36
  const TENANT = 'acme'
33
37
 
34
- interface Note {
35
- readonly id: string
36
- readonly title: string
37
- readonly body: string
38
- readonly done: boolean
39
- readonly tenantId: string
40
- readonly createdAt: Date
41
- readonly optimistic?: boolean
42
- }
43
-
44
38
  const IndexPage = (): ReactNode => {
39
+ // No row `interface` and no `<T>`: the shape comes from `notes.list`'s own
40
+ // output schema, including the client's `optimistic` marker.
45
41
  const { data, revision, emittedAt, error, pendingPatches } =
46
- useSubscription<ReadonlyArray<Note>>('app', 'notes.list')
42
+ useSubscription('notes.list')
47
43
 
48
- const create = useMutation<{ tenantId: string; title: string; body: string }>('app', 'notes.create')
44
+ const create = useMutation('notes.create')
49
45
  const t = useTFn()
50
46
 
51
47
  const notes = data ?? []
@@ -12,21 +12,23 @@
12
12
  "test": "voltro test"
13
13
  },
14
14
  "dependencies": {
15
- "@{{projectName}}/api": "0.33.0",
16
- "@voltro/cli": "0.33.0",
17
- "@voltro/client": "0.33.0",
18
- "@voltro/env": "0.33.0",
19
- "@voltro/i18n": "0.33.0",
20
- "@voltro/ui-shadcn": "0.33.0",
21
- "@voltro/web": "0.33.0",
15
+ "@{{projectName}}/api": "0.34.0",
16
+ "@voltro/cli": "0.34.0",
17
+ "@voltro/client": "0.34.0",
18
+ "@voltro/env": "0.34.0",
19
+ "@voltro/i18n": "0.34.0",
20
+ "@voltro/ui-shadcn": "0.34.0",
21
+ "@voltro/web": "0.34.0",
22
22
  "react": "^19.0.0",
23
23
  "react-dom": "^19.0.0"
24
24
  },
25
25
  "devDependencies": {
26
- "@voltro/devtools": "0.33.0",
26
+ "@voltro/devtools": "0.34.0",
27
27
  "@types/react": "^19.0.0",
28
28
  "@types/react-dom": "^19.0.0",
29
+ "jsdom": "^30.0.0",
29
30
  "typescript": "^6.0.3",
31
+ "@vitest/coverage-v8": "^4.1.10",
30
32
  "vitest": "^4.1.10"
31
33
  }
32
34
  }
@@ -12,20 +12,22 @@
12
12
  "typecheck": "tsc --noEmit"
13
13
  },
14
14
  "dependencies": {
15
- "@voltro/cli": "0.33.0",
16
- "@voltro/client": "0.33.0",
17
- "@voltro/env": "0.33.0",
18
- "@voltro/i18n": "0.33.0",
19
- "@voltro/ui-shadcn": "0.33.0",
20
- "@voltro/web": "0.33.0",
15
+ "@voltro/cli": "0.34.0",
16
+ "@voltro/client": "0.34.0",
17
+ "@voltro/env": "0.34.0",
18
+ "@voltro/i18n": "0.34.0",
19
+ "@voltro/ui-shadcn": "0.34.0",
20
+ "@voltro/web": "0.34.0",
21
21
  "react": "^19.0.0",
22
22
  "react-dom": "^19.0.0"
23
23
  },
24
24
  "devDependencies": {
25
- "@voltro/devtools": "0.33.0",
25
+ "@voltro/devtools": "0.34.0",
26
26
  "@types/react": "^19.0.0",
27
27
  "@types/react-dom": "^19.0.0",
28
+ "jsdom": "^30.0.0",
28
29
  "typescript": "^6.0.3",
30
+ "@vitest/coverage-v8": "^4.1.10",
29
31
  "vitest": "^4.1.10"
30
32
  }
31
33
  }
@@ -12,22 +12,24 @@
12
12
  "test": "voltro test"
13
13
  },
14
14
  "dependencies": {
15
- "@{{projectName}}/api": "0.33.0",
16
- "@voltro/cli": "0.33.0",
17
- "@voltro/client": "0.33.0",
18
- "@voltro/cms": "0.33.0",
19
- "@voltro/env": "0.33.0",
20
- "@voltro/i18n": "0.33.0",
21
- "@voltro/ui-shadcn": "0.33.0",
22
- "@voltro/web": "0.33.0",
15
+ "@{{projectName}}/api": "0.34.0",
16
+ "@voltro/cli": "0.34.0",
17
+ "@voltro/client": "0.34.0",
18
+ "@voltro/cms": "0.34.0",
19
+ "@voltro/env": "0.34.0",
20
+ "@voltro/i18n": "0.34.0",
21
+ "@voltro/ui-shadcn": "0.34.0",
22
+ "@voltro/web": "0.34.0",
23
23
  "react": "^19.0.0",
24
24
  "react-dom": "^19.0.0"
25
25
  },
26
26
  "devDependencies": {
27
- "@voltro/devtools": "0.33.0",
27
+ "@voltro/devtools": "0.34.0",
28
28
  "@types/react": "^19.0.0",
29
29
  "@types/react-dom": "^19.0.0",
30
+ "jsdom": "^30.0.0",
30
31
  "typescript": "^6.0.3",
32
+ "@vitest/coverage-v8": "^4.1.10",
31
33
  "vitest": "^4.1.10"
32
34
  }
33
35
  }
@@ -12,22 +12,25 @@
12
12
  "typecheck": "tsc --noEmit"
13
13
  },
14
14
  "dependencies": {
15
- "@{{projectName}}/api": "0.33.0",
16
- "@voltro/cli": "0.33.0",
17
- "@voltro/client": "0.33.0",
18
- "@voltro/env": "0.33.0",
19
- "@voltro/i18n": "0.33.0",
20
- "@voltro/local-first": "0.33.0",
21
- "@voltro/ui-shadcn": "0.33.0",
22
- "@voltro/web": "0.33.0",
15
+ "@{{projectName}}/api": "0.34.0",
16
+ "@voltro/cli": "0.34.0",
17
+ "@voltro/client": "0.34.0",
18
+ "@voltro/env": "0.34.0",
19
+ "@voltro/i18n": "0.34.0",
20
+ "@voltro/local-first": "0.34.0",
21
+ "@voltro/ui-shadcn": "0.34.0",
22
+ "@voltro/web": "0.34.0",
23
23
  "react": "^19.0.0",
24
24
  "react-dom": "^19.0.0"
25
25
  },
26
26
  "devDependencies": {
27
- "@voltro/devtools": "0.33.0",
27
+ "@voltro/devtools": "0.34.0",
28
+ "@voltro/testing": "0.34.0",
28
29
  "@types/react": "^19.0.0",
29
30
  "@types/react-dom": "^19.0.0",
31
+ "jsdom": "^30.0.0",
30
32
  "typescript": "^6.0.3",
33
+ "@vitest/coverage-v8": "^4.1.10",
31
34
  "vitest": "^4.1.10"
32
35
  }
33
36
  }