@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
@@ -1,55 +1,69 @@
1
1
  // @vitest-environment jsdom
2
2
  //
3
- // `/` — the collaborative editor. It binds `useSubscription('app',
4
- // 'documents.list')` + two `useMutation`s ('documents.create',
5
- // 'documents.setBody') from @voltro/client, which we mock so the render is
6
- // deterministic. `@voltro/local-first` is NOT mocked the editor's real local
7
- // CRDT handle decodes the seeded body and turns keystrokes into encoded updates,
8
- // which is the behaviour worth asserting.
9
-
10
- import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'
3
+ // `/` — the collaborative editor, driven through `makeVoltroTestClient`
4
+ // (`@voltro/testing/client`) rather than a hand-rolled `vi.mock('@voltro/client')`.
5
+ //
6
+ // WHY THE HARNESS HERE and not a mock. This page is stateful: a local CRDT
7
+ // handle in a ref, seeded from the server body, folding every INCOMING body
8
+ // back in. A `vi.mock` can return one snapshot per mount, so the fold
9
+ // the whole point of the page — was untestable, and the suite that replaced
10
+ // this one never pushed a second value into a mounted tree. `setSubscription`
11
+ // is a real server delta, so the last case below asserts convergence.
12
+ //
13
+ // Two other things fall out of it: the real `@voltro/client` hooks run (so a
14
+ // change in their loading/error semantics is caught here instead of being
15
+ // re-implemented by the mock), and the page can be imported statically —
16
+ // `await import('./page')` only existed to sequence around `vi.mock`'s hoist.
17
+ //
18
+ // `@voltro/local-first` is NOT faked either: the editor's real CRDT handle
19
+ // decodes the seeded body and turns keystrokes into encoded updates, which is
20
+ // the behaviour worth asserting.
21
+
22
+ import { afterEach, beforeEach, describe, expect, test } from 'vitest'
11
23
  import { act, createElement, type ReactNode } from 'react'
12
24
  import { createRoot, type Root } from 'react-dom/client'
13
25
  import { I18nProvider } from '@voltro/i18n'
14
- import { crdtText } from '@voltro/local-first'
26
+ import { crdtText, type CrdtState } from '@voltro/local-first'
27
+ import { makeVoltroTestClient, type VoltroTestClient } from '@voltro/testing/client'
15
28
  import enCatalog from '../locales/en'
29
+ import IndexPage from './page'
16
30
 
17
- interface SubState {
18
- data: unknown
19
- revision?: number
20
- error?: unknown
21
- }
22
-
23
- const useSubscription = vi.fn<() => SubState>()
24
- const createMutate = vi.fn<(input: unknown) => Promise<unknown>>()
25
- const setBodyMutate = vi.fn<(input: unknown) => Promise<unknown>>()
31
+ ;(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true
26
32
 
27
- // useMutation is called once per tag — dispatch to the right mock by tag.
28
- const useMutation = vi.fn((_api: unknown, tag: unknown) =>
29
- tag === 'documents.create'
30
- ? { mutate: createMutate, pending: false, error: undefined, data: undefined }
31
- : { mutate: setBodyMutate, pending: false, error: undefined, data: undefined },
32
- )
33
+ const LIST = 'documents.list'
33
34
 
34
- vi.mock('@voltro/client', () => ({
35
- useSubscription: (...a: unknown[]) => useSubscription(...(a as [])),
36
- useMutation: (...a: unknown[]) => useMutation(...(a as Parameters<typeof useMutation>)),
37
- }))
35
+ let container: HTMLDivElement
36
+ let root: Root
37
+ let client: VoltroTestClient
38
38
 
39
- const { default: IndexPage } = await import('./page')
39
+ /** A CRDT state holding `text`, from a fresh document. */
40
+ const bodyWith = (text: string): CrdtState => crdtText().insert(0, text).encode()
40
41
 
41
- ;(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true
42
+ /** A REMOTE edit derived from `base`, so merging it back converges instead of
43
+ * interleaving two independently-created runs of the same characters. */
44
+ const remoteEditOf = (base: CrdtState, at: number, insert: string): CrdtState =>
45
+ crdtText().merge(base).insert(at, insert).encode()
42
46
 
43
- let container: HTMLDivElement
44
- let root: Root
47
+ const docRow = (over: Partial<{ id: string; title: string; body: CrdtState | null }> = {}) => ({
48
+ id: over.id ?? 'doc_1',
49
+ title: over.title ?? 'Shared document',
50
+ body: over.body ?? null,
51
+ tenantId: 'acme',
52
+ createdAt: new Date(0),
53
+ })
45
54
 
46
- const render = (node: ReactNode): void => {
55
+ const render = (): void => {
47
56
  container = document.createElement('div')
48
57
  document.body.appendChild(container)
49
58
  act(() => {
50
59
  root = createRoot(container)
51
60
  root.render(
52
- createElement(I18nProvider, { locale: 'en', messages: enCatalog, defaultLocale: 'en', children: node }),
61
+ createElement(I18nProvider, {
62
+ locale: 'en',
63
+ messages: enCatalog,
64
+ defaultLocale: 'en',
65
+ children: createElement(client.Provider, null, createElement(IndexPage) as ReactNode),
66
+ }),
53
67
  )
54
68
  })
55
69
  }
@@ -62,26 +76,20 @@ const setTextarea = (el: HTMLTextAreaElement, value: string): void => {
62
76
  })
63
77
  }
64
78
 
65
- // A real encoded CRDT body seeded with `text`, exactly what the subscription
66
- // streams for a document that already has content.
67
- const bodyWith = (text: string): Uint8Array => crdtText().insert(0, text).encode()
68
-
69
- const docRow = (over: Partial<{ id: string; title: string; body: Uint8Array | null }> = {}) => ({
70
- id: over.id ?? 'doc_1',
71
- title: over.title ?? 'Shared document',
72
- body: over.body ?? null,
73
- tenantId: 'acme',
74
- createdAt: new Date(0),
75
- })
79
+ const textarea = (): HTMLTextAreaElement => container.querySelector('textarea') as HTMLTextAreaElement
80
+
81
+ /** The harness with both writes wired. Handlers return void; the assertions
82
+ * read `client.calls`, which records tag + input in order. */
83
+ const harness = (subscriptions: Readonly<Record<string, unknown>> = {}): VoltroTestClient =>
84
+ makeVoltroTestClient({
85
+ subscriptions,
86
+ mutations: {
87
+ 'documents.create': () => undefined,
88
+ 'documents.setBody': () => undefined,
89
+ },
90
+ })
76
91
 
77
- beforeEach(() => {
78
- useSubscription.mockReset()
79
- createMutate.mockReset()
80
- createMutate.mockResolvedValue(undefined)
81
- setBodyMutate.mockReset()
82
- setBodyMutate.mockResolvedValue(undefined)
83
- useMutation.mockClear()
84
- })
92
+ beforeEach(() => { client = harness() })
85
93
 
86
94
  afterEach(() => {
87
95
  if (root) act(() => root.unmount())
@@ -90,24 +98,25 @@ afterEach(() => {
90
98
  })
91
99
 
92
100
  describe('collab editor — subscription states', () => {
93
- test('shows the loading hint before the first snapshot (data undefined)', () => {
94
- useSubscription.mockReturnValue({ data: undefined, revision: -1 })
95
- render(createElement(IndexPage))
101
+ test('shows the loading hint before the first snapshot', () => {
102
+ // A tag with NO fixture is the harness's cold state — the real thing a
103
+ // component sees before the first delta arrives.
104
+ render()
96
105
  expect(container.textContent).toContain('loading…')
97
106
  expect(container.querySelector('textarea')).toBeNull()
98
107
  })
99
108
 
100
109
  test('shows the empty state + create button once a snapshot arrives with no documents', () => {
101
- useSubscription.mockReturnValue({ data: [], revision: 0 })
102
- render(createElement(IndexPage))
110
+ client = harness({ [LIST]: [] })
111
+ render()
103
112
  expect(container.textContent).toContain('No document yet')
104
113
  expect(container.querySelector('button')).not.toBeNull()
105
114
  expect(container.querySelector('textarea')).toBeNull()
106
115
  })
107
116
 
108
117
  test('renders the error banner when the subscription errors', () => {
109
- useSubscription.mockReturnValue({ data: undefined, error: 'boom', revision: -1 })
110
- render(createElement(IndexPage))
118
+ render()
119
+ act(() => { client.failSubscription(LIST, 'boom') })
111
120
  expect(container.textContent).toContain('subscription error')
112
121
  expect(container.textContent).toContain('boom')
113
122
  })
@@ -115,36 +124,71 @@ describe('collab editor — subscription states', () => {
115
124
 
116
125
  describe('collab editor — create', () => {
117
126
  test('clicking Create document calls documents.create with the tenant + a title', async () => {
118
- useSubscription.mockReturnValue({ data: [], revision: 0 })
119
- render(createElement(IndexPage))
127
+ client = harness({ [LIST]: [] })
128
+ render()
120
129
  const button = container.querySelector('button') as HTMLButtonElement
121
130
  await act(async () => {
122
131
  button.dispatchEvent(new MouseEvent('click', { bubbles: true }))
123
132
  })
124
- expect(createMutate).toHaveBeenCalledTimes(1)
125
- expect(createMutate).toHaveBeenCalledWith({ tenantId: 'acme', title: 'Shared document' })
133
+ expect(client.calls).toEqual([
134
+ { kind: 'mutation', tag: 'documents.create', input: { tenantId: 'acme', title: 'Shared document' } },
135
+ ])
126
136
  })
127
137
  })
128
138
 
129
139
  describe('collab editor — editing a document', () => {
130
140
  test('binds the textarea to the decoded CRDT body', () => {
131
- useSubscription.mockReturnValue({ data: [docRow({ body: bodyWith('Hello') })], revision: 3 })
132
- render(createElement(IndexPage))
133
- const textarea = container.querySelector('textarea') as HTMLTextAreaElement
134
- expect(textarea).not.toBeNull()
135
- expect(textarea.value).toBe('Hello')
141
+ client = harness({ [LIST]: [docRow({ body: bodyWith('Hello') })] })
142
+ render()
143
+ expect(textarea()).not.toBeNull()
144
+ expect(textarea().value).toBe('Hello')
136
145
  expect(container.textContent).toContain('Shared document')
137
146
  })
138
147
 
139
148
  test('typing sends an encoded CRDT update via documents.setBody', () => {
140
- useSubscription.mockReturnValue({ data: [docRow({ body: bodyWith('Hello') })], revision: 3 })
141
- render(createElement(IndexPage))
142
- const textarea = container.querySelector('textarea') as HTMLTextAreaElement
143
- setTextarea(textarea, 'Hello!')
144
-
145
- expect(setBodyMutate).toHaveBeenCalledTimes(1)
146
- const arg = setBodyMutate.mock.calls[0]![0] as { id: string; update: unknown }
147
- expect(arg.id).toBe('doc_1')
148
- expect(arg.update).toBeInstanceOf(Uint8Array)
149
+ client = harness({ [LIST]: [docRow({ body: bodyWith('Hello') })] })
150
+ render()
151
+ setTextarea(textarea(), 'Hello!')
152
+
153
+ expect(client.calls).toHaveLength(1)
154
+ const call = client.calls[0]!
155
+ expect(call.tag).toBe('documents.setBody')
156
+ const input = call.input as { id: string; update: unknown }
157
+ expect(input.id).toBe('doc_1')
158
+ expect(input.update).toBeInstanceOf(Uint8Array)
159
+ })
160
+
161
+ // The case the hand-rolled mock could not express, and the page's whole
162
+ // reason to exist: a body edited in ANOTHER tab arrives as a delta, and the
163
+ // local handle folds it in without remounting the editor.
164
+ test('a remote edit pushed by the server converges into the open editor', () => {
165
+ const base = bodyWith('Hello')
166
+ client = harness({ [LIST]: [docRow({ body: base })] })
167
+ render()
168
+ expect(textarea().value).toBe('Hello')
169
+
170
+ act(() => {
171
+ client.setSubscription(LIST, [docRow({ body: remoteEditOf(base, 5, ' world') })])
172
+ })
173
+
174
+ expect(textarea().value).toBe('Hello world')
175
+ // A fold is not a write — nothing is echoed back to the server.
176
+ expect(client.calls).toEqual([])
177
+ })
178
+
179
+ test('a local edit survives a concurrent remote edit — both are in the merged text', () => {
180
+ const base = bodyWith('Hello')
181
+ client = harness({ [LIST]: [docRow({ body: base })] })
182
+ render()
183
+ setTextarea(textarea(), 'Hello!')
184
+ expect(textarea().value).toBe('Hello!')
185
+
186
+ // The other tab branched from the SAME base and appended elsewhere.
187
+ act(() => {
188
+ client.setSubscription(LIST, [docRow({ body: remoteEditOf(base, 0, 'Oh, ') })])
189
+ })
190
+
191
+ expect(textarea().value).toContain('Oh, ')
192
+ expect(textarea().value).toContain('!')
149
193
  })
150
194
  })
@@ -15,21 +15,23 @@
15
15
  },
16
16
  "dependencies": {
17
17
  "@effect/platform": "^0.97.0",
18
- "@voltro/cli": "0.33.0",
19
- "@voltro/client": "0.33.0",
20
- "@voltro/env": "0.33.0",
21
- "@voltro/i18n": "0.33.0",
22
- "@voltro/serverless": "0.33.0",
23
- "@voltro/web": "0.33.0",
18
+ "@voltro/cli": "0.34.0",
19
+ "@voltro/client": "0.34.0",
20
+ "@voltro/env": "0.34.0",
21
+ "@voltro/i18n": "0.34.0",
22
+ "@voltro/serverless": "0.34.0",
23
+ "@voltro/web": "0.34.0",
24
24
  "effect": "^3.22.0",
25
25
  "react": "^19.0.0",
26
26
  "react-dom": "^19.0.0"
27
27
  },
28
28
  "devDependencies": {
29
- "@voltro/devtools": "0.33.0",
29
+ "@voltro/devtools": "0.34.0",
30
30
  "@types/react": "^19.0.0",
31
31
  "@types/react-dom": "^19.0.0",
32
+ "jsdom": "^30.0.0",
32
33
  "typescript": "^6.0.3",
34
+ "@vitest/coverage-v8": "^4.1.10",
33
35
  "vitest": "^4.1.10"
34
36
  }
35
37
  }
@@ -12,20 +12,22 @@
12
12
  "test": "voltro test"
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,20 +12,22 @@
12
12
  "test": "voltro test"
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,19 +12,21 @@
12
12
  "test": "voltro test"
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/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/web": "0.34.0",
20
20
  "react": "^19.0.0",
21
21
  "react-dom": "^19.0.0"
22
22
  },
23
23
  "devDependencies": {
24
- "@voltro/devtools": "0.33.0",
24
+ "@voltro/devtools": "0.34.0",
25
25
  "@types/react": "^19.0.0",
26
26
  "@types/react-dom": "^19.0.0",
27
+ "jsdom": "^30.0.0",
27
28
  "typescript": "^6.0.3",
29
+ "@vitest/coverage-v8": "^4.1.10",
28
30
  "vitest": "^4.1.10"
29
31
  }
30
32
  }
@@ -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,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
  }
@@ -11,7 +11,9 @@
11
11
  // `renderMode: 'ssr'` so the gate runs per request.
12
12
  //
13
13
  // The pages read the api live with `useSubscription` and write with
14
- // `useMutation` / `useAction`; the auth forms POST to the api's `/auth/*`.
14
+ // `useMutation` / `useAction` imported from `src/lib/api.ts`, which binds them
15
+ // ONCE to this api's generated procedure map so every tag and every input/output
16
+ // type is checked by the compiler. The auth forms POST to the api's `/auth/*`.
15
17
  import { defineEnv, envVar } from '@voltro/env'
16
18
 
17
19
  // Public, build-time env (baked into the bundle; read via `publicEnv`).
@@ -35,11 +37,13 @@ export default {
35
37
  // grow the app to more languages.
36
38
  locales: ['en', 'de'] as const,
37
39
  defaultLocale: 'en' as const,
38
- // The api this dashboard consumes. The NAME ('app') is the lookup key the
39
- // hooks + the loader's `query` use: `useSubscription('app', 'projects.list')`,
40
- // `useMutation('app', 'projects.create')`, `query('session.me', {})`. Its
41
- // port is auto-discovered from the api's own app.config.ts, and codegen pulls
42
- // its typed rpc surface (including the plugins' `billing.*` / `/auth`).
40
+ // The api this dashboard consumes. The NAME ('app') is the lookup key — spelled
41
+ // ONCE, in `src/lib/api.ts`'s `createHooks<AppProcedures>('app')`, and then
42
+ // never again: pages call `useSubscription('projects.list')` /
43
+ // `useMutation('projects.create')`. (A loader still names the tag itself:
44
+ // `query('session.me', {})`.) The api's port is auto-discovered from its own
45
+ // app.config.ts, and codegen pulls its typed rpc surface (including the
46
+ // plugins' `billing.*` / `/auth`) into that same map.
43
47
  apis: {
44
48
  app: { package: '@{{projectName}}/api' },
45
49
  },
@@ -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
  }
@@ -1,37 +1,32 @@
1
- // The row + payload shapes this dashboard reads off the api, kept LOCAL so the
2
- // pages are self-contained. They mirror the api's descriptor outputs
3
- // (`projects.list`, `invites.list`, `billing.subscription`); you can also
4
- // `import type` them from the api package a type-only cross-app import is
5
- // browser-safe.
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 (the app's own AND the plugins', e.g.
6
+ // `billing.*`) to the descriptor behind it. Feeding it to `createHooks` makes
7
+ // the tag a literal union and the input/output types INFER:
8
+ //
9
+ // const { data } = useSubscription('projects.list') // rows, typed
10
+ // const create = useMutation('projects.create') // input + output typed
11
+ //
12
+ // So the row shapes this file used to declare by hand are gone. They were a
13
+ // MIRROR of the api's descriptor outputs, kept in sync by nobody: the old
14
+ // `useSubscription<ReadonlyArray<Project>>('app', 'projects.list')` asserted a
15
+ // type nothing checked against the server, and a typo'd tag only showed up at
16
+ // runtime. Both are compile errors now.
17
+ //
18
+ // `'app'` is the key this web app gave the api in `app.config.ts` → `apis`.
19
+ // `import type` is erased, so the binding costs the browser bundle nothing.
20
+ import { createHooks } from '@voltro/client'
21
+ import type { AppProcedures } from '@{{projectName}}/api/rpcGroup'
6
22
 
7
- export interface Project {
8
- readonly id: string
9
- readonly name: string
10
- readonly tenantId: string
11
- readonly createdAt: string | Date
12
- }
13
-
14
- export interface Invite {
15
- readonly id: string
16
- readonly email: string
17
- readonly status: string
18
- readonly tenantId: string
19
- readonly createdAt: string | Date
20
- }
21
-
22
- // `billing.subscription` streams `{ subscription: … | null }` — null before the
23
- // tenant has any subscription row (they're on the implicit free plan).
24
- export interface SubscriptionState {
25
- readonly subscription: {
26
- readonly plan: string
27
- readonly status: string
28
- readonly quantity: number
29
- readonly currentPeriodEnd: string | null
30
- readonly cancelAt: string | null
31
- } | null
32
- }
23
+ // Destructured on purpose — `react-hooks/rules-of-hooks` only treats a member
24
+ // call as a hook when the object is PascalCase, so top-level `useX` identifiers
25
+ // are what keep the React lint rules working at every call site.
26
+ export const { useSubscription, useMutation, useAction } = createHooks<AppProcedures>('app')
33
27
 
34
- // The caller's resolved identity, from `session.me`. The SSR gate reads it.
28
+ // The caller's resolved identity, from `session.me`. The SSR gate reads it, and
29
+ // a LOADER is not a hook — it calls `ctx.query` — so this shape stays hand-written.
35
30
  export interface Identity {
36
31
  readonly type: string
37
32
  readonly id: string | null
@@ -6,10 +6,9 @@
6
6
  // row yet means the tenant is on the implicit free plan.
7
7
  import type { ReactNode } from 'react'
8
8
  import type { PageMeta } from '@voltro/web'
9
- import { useSubscription, useAction } from '@voltro/client'
10
9
  import { T, useT } from '@voltro/i18n'
11
10
  import { getCatalog } from '../../../locales/index'
12
- import type { SubscriptionState } from '../../../lib/api'
11
+ import { useAction, useSubscription } from '../../../lib/api'
13
12
 
14
13
  export const renderMode = 'ssr' as const
15
14
 
@@ -17,13 +16,13 @@ export const meta = ({ locale }: { readonly locale: string }): PageMeta => ({
17
16
  title: getCatalog(locale)['meta.billing.title'],
18
17
  })
19
18
 
20
- interface CheckoutInput { plan: string; successUrl: string; cancelUrl: string }
21
- interface SeatsInput { quantity: number }
22
-
23
19
  export default function Billing(): ReactNode {
24
- const { data } = useSubscription<SubscriptionState>('app', 'billing.subscription')
25
- const checkout = useAction<CheckoutInput, { url: string }>('app', 'billing.startCheckout')
26
- const seats = useAction<SeatsInput, unknown>('app', 'billing.changeSeats')
20
+ // `billing.*` is contributed by @voltro/plugin-billing and lands in the same
21
+ // generated map as the app's own procedures, so a plugin route is typed here
22
+ // exactly like a hand-written one — no local Checkout/Seats input mirrors.
23
+ const { data } = useSubscription('billing.subscription')
24
+ const checkout = useAction('billing.startCheckout')
25
+ const seats = useAction('billing.changeSeats')
27
26
 
28
27
  const sub = data?.subscription ?? null
29
28
  const planLabel = useT('billing.plan.free')