@voltro/cli 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (626) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/LICENSE +57 -0
  3. package/README.md +26 -0
  4. package/SECURITY.md +56 -0
  5. package/THIRD-PARTY-NOTICES.md +20236 -0
  6. package/bin/voltro.mjs +43 -0
  7. package/dist/bin.d.ts +1 -0
  8. package/dist/bin.js +9 -0
  9. package/dist/commands-CfPH2Wf4.js +18061 -0
  10. package/dist/frameworkInspectState-CX2250XB.js +86 -0
  11. package/dist/index.d.ts +48 -0
  12. package/dist/index.js +5 -0
  13. package/dist/inspectState.d.ts +116 -0
  14. package/dist/inspectState.js +2 -0
  15. package/dist/startup.d.ts +26 -0
  16. package/dist/startup.js +2 -0
  17. package/dist/startupRunner-CRhuUl91.js +71 -0
  18. package/package.json +88 -0
  19. package/templates/AGENTS.core.md +258 -0
  20. package/templates/AGENTS.md +351 -0
  21. package/templates/agent-docs/_index.md +93 -0
  22. package/templates/agent-docs/_manifest.json +655 -0
  23. package/templates/agent-docs/ai.md +1845 -0
  24. package/templates/agent-docs/authentication.md +1788 -0
  25. package/templates/agent-docs/caching.md +624 -0
  26. package/templates/agent-docs/cli.md +1650 -0
  27. package/templates/agent-docs/configuration.md +295 -0
  28. package/templates/agent-docs/data.md +2172 -0
  29. package/templates/agent-docs/database/advancedqueries.md +1583 -0
  30. package/templates/agent-docs/database/columntypes.md +1200 -0
  31. package/templates/agent-docs/database/hosting.md +881 -0
  32. package/templates/agent-docs/database/migrations.md +2938 -0
  33. package/templates/agent-docs/database/misc.md +270 -0
  34. package/templates/agent-docs/database/overview.md +108 -0
  35. package/templates/agent-docs/database/querying.md +1622 -0
  36. package/templates/agent-docs/database/scaling.md +331 -0
  37. package/templates/agent-docs/database/schema.md +1458 -0
  38. package/templates/agent-docs/database/seedsdialects.md +1235 -0
  39. package/templates/agent-docs/database/transactions.md +285 -0
  40. package/templates/agent-docs/deployment.md +999 -0
  41. package/templates/agent-docs/internationalization.md +359 -0
  42. package/templates/agent-docs/introduction.md +438 -0
  43. package/templates/agent-docs/multi-tenancy.md +610 -0
  44. package/templates/agent-docs/observability.md +350 -0
  45. package/templates/agent-docs/plugins.md +1273 -0
  46. package/templates/agent-docs/reference.md +978 -0
  47. package/templates/agent-docs/routing.md +1553 -0
  48. package/templates/agent-docs/scheduling.md +666 -0
  49. package/templates/agent-docs/schema-driven-ui.md +607 -0
  50. package/templates/agent-docs/security.md +42 -0
  51. package/templates/agent-docs/templates/apibackends.md +3214 -0
  52. package/templates/agent-docs/templates/appshells.md +2062 -0
  53. package/templates/agent-docs/templates/custom.md +128 -0
  54. package/templates/agent-docs/templates/overview.md +122 -0
  55. package/templates/agent-docs/templates/serverless.md +315 -0
  56. package/templates/agent-docs/testing.md +376 -0
  57. package/templates/agent-docs/workflows.md +1351 -0
  58. package/templates/apps/api-ai/README.md +105 -0
  59. package/templates/apps/api-ai/actions/summarize.action.server.tsx +32 -0
  60. package/templates/apps/api-ai/actions/summarize.action.ts +33 -0
  61. package/templates/apps/api-ai/agents/support.agent.server.tsx +31 -0
  62. package/templates/apps/api-ai/agents/support.agent.tsx +27 -0
  63. package/templates/apps/api-ai/app.config.ts +57 -0
  64. package/templates/apps/api-ai/database/schema.ts +67 -0
  65. package/templates/apps/api-ai/package.json +27 -0
  66. package/templates/apps/api-ai/seeds/docs.seed.ts +67 -0
  67. package/templates/apps/api-ai/template.json +6 -0
  68. package/templates/apps/api-ai/tests/summarize.test.ts +50 -0
  69. package/templates/apps/api-ai/tools/searchDocs.tool.tsx +60 -0
  70. package/templates/apps/api-ai/tsconfig.json +5 -0
  71. package/templates/apps/api-auth/.env +17 -0
  72. package/templates/apps/api-auth/README.md +109 -0
  73. package/templates/apps/api-auth/actions/me.action.server.ts +20 -0
  74. package/templates/apps/api-auth/actions/me.action.ts +19 -0
  75. package/templates/apps/api-auth/app.config.ts +59 -0
  76. package/templates/apps/api-auth/database/schema.ts +26 -0
  77. package/templates/apps/api-auth/package.json +28 -0
  78. package/templates/apps/api-auth/template.json +6 -0
  79. package/templates/apps/api-auth/tests/me.test.ts +37 -0
  80. package/templates/apps/api-auth/tsconfig.json +5 -0
  81. package/templates/apps/api-backend/README.md +25 -0
  82. package/templates/apps/api-backend/app.config.ts +39 -0
  83. package/templates/apps/api-backend/database/schema.ts +57 -0
  84. package/templates/apps/api-backend/mutations/notes.create.mutation.server.ts +19 -0
  85. package/templates/apps/api-backend/mutations/notes.create.mutation.ts +37 -0
  86. package/templates/apps/api-backend/package.json +28 -0
  87. package/templates/apps/api-backend/queries/notes.query.server.ts +14 -0
  88. package/templates/apps/api-backend/queries/notes.query.ts +20 -0
  89. package/templates/apps/api-backend/template.json +6 -0
  90. package/templates/apps/api-backend/tests/notes.create.test.ts +50 -0
  91. package/templates/apps/api-backend/tsconfig.json +5 -0
  92. package/templates/apps/api-backend-deactivation/README.md +49 -0
  93. package/templates/apps/api-backend-deactivation/actions/users.get.action.server.ts +17 -0
  94. package/templates/apps/api-backend-deactivation/actions/users.get.action.ts +18 -0
  95. package/templates/apps/api-backend-deactivation/app.config.ts +20 -0
  96. package/templates/apps/api-backend-deactivation/database/schema.ts +41 -0
  97. package/templates/apps/api-backend-deactivation/mutations/users.create.mutation.server.ts +6 -0
  98. package/templates/apps/api-backend-deactivation/mutations/users.create.mutation.ts +16 -0
  99. package/templates/apps/api-backend-deactivation/mutations/users.deactivate.mutation.server.ts +10 -0
  100. package/templates/apps/api-backend-deactivation/mutations/users.deactivate.mutation.ts +19 -0
  101. package/templates/apps/api-backend-deactivation/package.json +28 -0
  102. package/templates/apps/api-backend-deactivation/template.json +6 -0
  103. package/templates/apps/api-backend-deactivation/tests/users.deactivate.test.ts +47 -0
  104. package/templates/apps/api-backend-deactivation/tsconfig.json +5 -0
  105. package/templates/apps/api-backend-mail/README.md +39 -0
  106. package/templates/apps/api-backend-mail/actions/sendWelcome.action.server.ts +15 -0
  107. package/templates/apps/api-backend-mail/actions/sendWelcome.action.ts +21 -0
  108. package/templates/apps/api-backend-mail/app.config.ts +34 -0
  109. package/templates/apps/api-backend-mail/database/schema.ts +57 -0
  110. package/templates/apps/api-backend-mail/emails/welcome.email.tsx +106 -0
  111. package/templates/apps/api-backend-mail/mutations/notes.create.mutation.server.ts +19 -0
  112. package/templates/apps/api-backend-mail/mutations/notes.create.mutation.ts +37 -0
  113. package/templates/apps/api-backend-mail/package.json +30 -0
  114. package/templates/apps/api-backend-mail/queries/notes.query.server.ts +14 -0
  115. package/templates/apps/api-backend-mail/queries/notes.query.ts +20 -0
  116. package/templates/apps/api-backend-mail/template.json +6 -0
  117. package/templates/apps/api-backend-mail/tests/notes.create.test.ts +50 -0
  118. package/templates/apps/api-backend-mail/tsconfig.json +5 -0
  119. package/templates/apps/api-backend-mariadb/.env.example +35 -0
  120. package/templates/apps/api-backend-mariadb/README.md +31 -0
  121. package/templates/apps/api-backend-mariadb/app.config.ts +68 -0
  122. package/templates/apps/api-backend-mariadb/database/schema.ts +57 -0
  123. package/templates/apps/api-backend-mariadb/mutations/notes.create.mutation.server.ts +19 -0
  124. package/templates/apps/api-backend-mariadb/mutations/notes.create.mutation.ts +37 -0
  125. package/templates/apps/api-backend-mariadb/package.json +30 -0
  126. package/templates/apps/api-backend-mariadb/queries/notes.query.server.ts +14 -0
  127. package/templates/apps/api-backend-mariadb/queries/notes.query.ts +20 -0
  128. package/templates/apps/api-backend-mariadb/template.json +6 -0
  129. package/templates/apps/api-backend-mariadb/tests/notes.create.test.ts +50 -0
  130. package/templates/apps/api-backend-mariadb/tsconfig.json +5 -0
  131. package/templates/apps/api-backend-storage/README.md +86 -0
  132. package/templates/apps/api-backend-storage/actions/uploadAvatar.action.server.ts +21 -0
  133. package/templates/apps/api-backend-storage/actions/uploadAvatar.action.ts +21 -0
  134. package/templates/apps/api-backend-storage/actions/uploadDocument.action.server.ts +21 -0
  135. package/templates/apps/api-backend-storage/actions/uploadDocument.action.ts +20 -0
  136. package/templates/apps/api-backend-storage/app.config.ts +38 -0
  137. package/templates/apps/api-backend-storage/database/schema.ts +57 -0
  138. package/templates/apps/api-backend-storage/mutations/notes.create.mutation.server.ts +19 -0
  139. package/templates/apps/api-backend-storage/mutations/notes.create.mutation.ts +37 -0
  140. package/templates/apps/api-backend-storage/package.json +27 -0
  141. package/templates/apps/api-backend-storage/queries/notes.query.server.ts +14 -0
  142. package/templates/apps/api-backend-storage/queries/notes.query.ts +20 -0
  143. package/templates/apps/api-backend-storage/template.json +6 -0
  144. package/templates/apps/api-backend-storage/tests/notes.create.test.ts +50 -0
  145. package/templates/apps/api-backend-storage/tsconfig.json +5 -0
  146. package/templates/apps/api-data-advanced/.env +17 -0
  147. package/templates/apps/api-data-advanced/README.md +88 -0
  148. package/templates/apps/api-data-advanced/app.config.ts +56 -0
  149. package/templates/apps/api-data-advanced/database/actors.entity.ts +14 -0
  150. package/templates/apps/api-data-advanced/database/authors.entity.ts +30 -0
  151. package/templates/apps/api-data-advanced/database/authors.relations.ts +15 -0
  152. package/templates/apps/api-data-advanced/database/books.entity.ts +59 -0
  153. package/templates/apps/api-data-advanced/database/books.relations.ts +10 -0
  154. package/templates/apps/api-data-advanced/database/index.ts +23 -0
  155. package/templates/apps/api-data-advanced/database/tenants.entity.ts +11 -0
  156. package/templates/apps/api-data-advanced/package.json +28 -0
  157. package/templates/apps/api-data-advanced/queries/authors.withBooks.query.server.ts +18 -0
  158. package/templates/apps/api-data-advanced/queries/authors.withBooks.query.ts +40 -0
  159. package/templates/apps/api-data-advanced/queries/books.search.query.server.ts +17 -0
  160. package/templates/apps/api-data-advanced/queries/books.search.query.ts +39 -0
  161. package/templates/apps/api-data-advanced/seeds/catalog.seed.ts +101 -0
  162. package/templates/apps/api-data-advanced/template.json +6 -0
  163. package/templates/apps/api-data-advanced/tests/queries.test.ts +124 -0
  164. package/templates/apps/api-data-advanced/tsconfig.json +5 -0
  165. package/templates/apps/api-durable/README.md +81 -0
  166. package/templates/apps/api-durable/aggregates/orderStats.aggregate.ts +49 -0
  167. package/templates/apps/api-durable/app.config.ts +41 -0
  168. package/templates/apps/api-durable/database/schema.ts +65 -0
  169. package/templates/apps/api-durable/mutations/orders.approve.mutation.server.ts +43 -0
  170. package/templates/apps/api-durable/mutations/orders.approve.mutation.ts +23 -0
  171. package/templates/apps/api-durable/mutations/orders.place.mutation.server.ts +45 -0
  172. package/templates/apps/api-durable/mutations/orders.place.mutation.ts +40 -0
  173. package/templates/apps/api-durable/package.json +28 -0
  174. package/templates/apps/api-durable/schedules/nightlyReport.cron.tsx +37 -0
  175. package/templates/apps/api-durable/startup/warm.startup.tsx +33 -0
  176. package/templates/apps/api-durable/subscribers/orderChanges.subscribe.ts +27 -0
  177. package/templates/apps/api-durable/template.json +6 -0
  178. package/templates/apps/api-durable/tests/orders.place.test.ts +91 -0
  179. package/templates/apps/api-durable/triggers/order.placed.trigger.tsx +21 -0
  180. package/templates/apps/api-durable/tsconfig.json +5 -0
  181. package/templates/apps/api-durable/workflows/order.fulfill.workflow.server.tsx +103 -0
  182. package/templates/apps/api-durable/workflows/order.fulfill.workflow.tsx +25 -0
  183. package/templates/apps/api-feature-flags/README.md +63 -0
  184. package/templates/apps/api-feature-flags/actions/notes.export.action.server.ts +11 -0
  185. package/templates/apps/api-feature-flags/actions/notes.export.action.ts +18 -0
  186. package/templates/apps/api-feature-flags/app.config.ts +41 -0
  187. package/templates/apps/api-feature-flags/database/schema.ts +37 -0
  188. package/templates/apps/api-feature-flags/mutations/notes.create.mutation.server.ts +29 -0
  189. package/templates/apps/api-feature-flags/mutations/notes.create.mutation.ts +28 -0
  190. package/templates/apps/api-feature-flags/package.json +29 -0
  191. package/templates/apps/api-feature-flags/template.json +6 -0
  192. package/templates/apps/api-feature-flags/tests/notes.create.test.ts +67 -0
  193. package/templates/apps/api-feature-flags/tsconfig.json +5 -0
  194. package/templates/apps/api-governance/.env +4 -0
  195. package/templates/apps/api-governance/README.md +61 -0
  196. package/templates/apps/api-governance/actions/profiles.get.action.server.ts +17 -0
  197. package/templates/apps/api-governance/actions/profiles.get.action.ts +17 -0
  198. package/templates/apps/api-governance/app.config.ts +35 -0
  199. package/templates/apps/api-governance/database/schema.ts +41 -0
  200. package/templates/apps/api-governance/mutations/profiles.create.mutation.server.ts +17 -0
  201. package/templates/apps/api-governance/mutations/profiles.create.mutation.ts +21 -0
  202. package/templates/apps/api-governance/package.json +29 -0
  203. package/templates/apps/api-governance/template.json +6 -0
  204. package/templates/apps/api-governance/tests/profiles.create.test.ts +59 -0
  205. package/templates/apps/api-governance/tsconfig.json +5 -0
  206. package/templates/apps/api-kv/README.md +100 -0
  207. package/templates/apps/api-kv/actions/sync.pull.action.server.ts +75 -0
  208. package/templates/apps/api-kv/actions/sync.pull.action.ts +25 -0
  209. package/templates/apps/api-kv/actions/sync.reset.action.server.ts +25 -0
  210. package/templates/apps/api-kv/actions/sync.reset.action.ts +22 -0
  211. package/templates/apps/api-kv/actions/sync.status.action.server.ts +28 -0
  212. package/templates/apps/api-kv/actions/sync.status.action.ts +17 -0
  213. package/templates/apps/api-kv/app.config.ts +48 -0
  214. package/templates/apps/api-kv/database/schema.ts +62 -0
  215. package/templates/apps/api-kv/package.json +28 -0
  216. package/templates/apps/api-kv/queries/events.list.query.server.ts +14 -0
  217. package/templates/apps/api-kv/queries/events.list.query.ts +23 -0
  218. package/templates/apps/api-kv/template.json +6 -0
  219. package/templates/apps/api-kv/tests/sync.test.ts +103 -0
  220. package/templates/apps/api-kv/tsconfig.json +5 -0
  221. package/templates/apps/api-moderation/README.md +46 -0
  222. package/templates/apps/api-moderation/app.config.ts +33 -0
  223. package/templates/apps/api-moderation/database/schema.ts +45 -0
  224. package/templates/apps/api-moderation/mutations/comments.create.mutation.server.ts +8 -0
  225. package/templates/apps/api-moderation/mutations/comments.create.mutation.ts +20 -0
  226. package/templates/apps/api-moderation/mutations/posts.create.mutation.server.ts +9 -0
  227. package/templates/apps/api-moderation/mutations/posts.create.mutation.ts +25 -0
  228. package/templates/apps/api-moderation/package.json +29 -0
  229. package/templates/apps/api-moderation/template.json +6 -0
  230. package/templates/apps/api-moderation/tests/posts.create.test.ts +79 -0
  231. package/templates/apps/api-moderation/tsconfig.json +5 -0
  232. package/templates/apps/api-observability/README.md +106 -0
  233. package/templates/apps/api-observability/app.config.ts +32 -0
  234. package/templates/apps/api-observability/database/schema.ts +38 -0
  235. package/templates/apps/api-observability/mutations/notes.create.mutation.server.ts +33 -0
  236. package/templates/apps/api-observability/mutations/notes.create.mutation.ts +15 -0
  237. package/templates/apps/api-observability/package.json +29 -0
  238. package/templates/apps/api-observability/queries/notes.list.query.server.ts +7 -0
  239. package/templates/apps/api-observability/queries/notes.list.query.ts +14 -0
  240. package/templates/apps/api-observability/template.json +6 -0
  241. package/templates/apps/api-observability/tests/notes.create.test.ts +36 -0
  242. package/templates/apps/api-observability/tsconfig.json +5 -0
  243. package/templates/apps/api-ratelimit/README.md +49 -0
  244. package/templates/apps/api-ratelimit/app.config.ts +42 -0
  245. package/templates/apps/api-ratelimit/database/schema.ts +37 -0
  246. package/templates/apps/api-ratelimit/mutations/notes.create.mutation.server.ts +16 -0
  247. package/templates/apps/api-ratelimit/mutations/notes.create.mutation.ts +34 -0
  248. package/templates/apps/api-ratelimit/package.json +29 -0
  249. package/templates/apps/api-ratelimit/template.json +6 -0
  250. package/templates/apps/api-ratelimit/tests/notes.create.test.ts +69 -0
  251. package/templates/apps/api-ratelimit/tsconfig.json +5 -0
  252. package/templates/apps/api-rbac/README.md +59 -0
  253. package/templates/apps/api-rbac/app.config.ts +48 -0
  254. package/templates/apps/api-rbac/database/schema.ts +37 -0
  255. package/templates/apps/api-rbac/mutations/notes.create.mutation.server.ts +22 -0
  256. package/templates/apps/api-rbac/mutations/notes.create.mutation.ts +26 -0
  257. package/templates/apps/api-rbac/package.json +29 -0
  258. package/templates/apps/api-rbac/template.json +6 -0
  259. package/templates/apps/api-rbac/tests/notes.create.test.ts +80 -0
  260. package/templates/apps/api-rbac/tsconfig.json +5 -0
  261. package/templates/apps/api-rest/README.md +85 -0
  262. package/templates/apps/api-rest/app.config.ts +70 -0
  263. package/templates/apps/api-rest/database/schema.ts +52 -0
  264. package/templates/apps/api-rest/lib/product.ts +30 -0
  265. package/templates/apps/api-rest/package.json +27 -0
  266. package/templates/apps/api-rest/routes/v1/products.create.route.tsx +45 -0
  267. package/templates/apps/api-rest/routes/v1/products.delete.route.tsx +25 -0
  268. package/templates/apps/api-rest/routes/v1/products.get.route.tsx +29 -0
  269. package/templates/apps/api-rest/routes/v1/products.list.route.tsx +43 -0
  270. package/templates/apps/api-rest/template.json +6 -0
  271. package/templates/apps/api-rest/tests/products.create.test.ts +53 -0
  272. package/templates/apps/api-rest/tsconfig.json +5 -0
  273. package/templates/apps/api-saas/README.md +106 -0
  274. package/templates/apps/api-saas/app.config.ts +51 -0
  275. package/templates/apps/api-saas/database/schema.ts +50 -0
  276. package/templates/apps/api-saas/mutations/projects.create.mutation.server.ts +52 -0
  277. package/templates/apps/api-saas/mutations/projects.create.mutation.ts +18 -0
  278. package/templates/apps/api-saas/package.json +31 -0
  279. package/templates/apps/api-saas/queries/projects.list.query.server.ts +7 -0
  280. package/templates/apps/api-saas/queries/projects.list.query.ts +18 -0
  281. package/templates/apps/api-saas/template.json +6 -0
  282. package/templates/apps/api-saas/tests/projects.create.test.ts +47 -0
  283. package/templates/apps/api-saas/tsconfig.json +5 -0
  284. package/templates/apps/api-search/README.md +68 -0
  285. package/templates/apps/api-search/app.config.ts +31 -0
  286. package/templates/apps/api-search/database/schema.ts +47 -0
  287. package/templates/apps/api-search/lib/search.ts +27 -0
  288. package/templates/apps/api-search/mutations/articles.create.mutation.server.ts +19 -0
  289. package/templates/apps/api-search/mutations/articles.create.mutation.ts +35 -0
  290. package/templates/apps/api-search/package.json +29 -0
  291. package/templates/apps/api-search/queries/articles.list.query.server.ts +14 -0
  292. package/templates/apps/api-search/queries/articles.list.query.ts +19 -0
  293. package/templates/apps/api-search/seeds/articles.seed.ts +41 -0
  294. package/templates/apps/api-search/startup/searchBackfill.startup.tsx +24 -0
  295. package/templates/apps/api-search/template.json +6 -0
  296. package/templates/apps/api-search/tests/articles.create.test.ts +69 -0
  297. package/templates/apps/api-search/tsconfig.json +5 -0
  298. package/templates/apps/api-versioning/README.md +51 -0
  299. package/templates/apps/api-versioning/actions/documents.asOf.action.server.ts +13 -0
  300. package/templates/apps/api-versioning/actions/documents.asOf.action.ts +14 -0
  301. package/templates/apps/api-versioning/actions/documents.history.action.server.ts +15 -0
  302. package/templates/apps/api-versioning/actions/documents.history.action.ts +17 -0
  303. package/templates/apps/api-versioning/app.config.ts +24 -0
  304. package/templates/apps/api-versioning/database/schema.ts +38 -0
  305. package/templates/apps/api-versioning/mutations/documents.create.mutation.server.ts +8 -0
  306. package/templates/apps/api-versioning/mutations/documents.create.mutation.ts +21 -0
  307. package/templates/apps/api-versioning/mutations/documents.update.mutation.server.ts +9 -0
  308. package/templates/apps/api-versioning/mutations/documents.update.mutation.ts +21 -0
  309. package/templates/apps/api-versioning/package.json +29 -0
  310. package/templates/apps/api-versioning/template.json +6 -0
  311. package/templates/apps/api-versioning/tests/documents.create.test.ts +37 -0
  312. package/templates/apps/api-versioning/tsconfig.json +5 -0
  313. package/templates/apps/api-webhooks/.env +6 -0
  314. package/templates/apps/api-webhooks/README.md +106 -0
  315. package/templates/apps/api-webhooks/app.config.ts +16 -0
  316. package/templates/apps/api-webhooks/database/schema.ts +49 -0
  317. package/templates/apps/api-webhooks/events/order.completed.webhook.tsx +22 -0
  318. package/templates/apps/api-webhooks/mutations/orders.fulfill.mutation.server.ts +37 -0
  319. package/templates/apps/api-webhooks/mutations/orders.fulfill.mutation.ts +15 -0
  320. package/templates/apps/api-webhooks/package.json +28 -0
  321. package/templates/apps/api-webhooks/queries/orders.list.query.server.ts +7 -0
  322. package/templates/apps/api-webhooks/queries/orders.list.query.ts +15 -0
  323. package/templates/apps/api-webhooks/template.json +6 -0
  324. package/templates/apps/api-webhooks/tests/orders.fulfill.test.ts +51 -0
  325. package/templates/apps/api-webhooks/tsconfig.json +5 -0
  326. package/templates/apps/api-webhooks/webhooks/orders.webhook.tsx +35 -0
  327. package/templates/apps/changelog/README.md +77 -0
  328. package/templates/apps/changelog/app.config.ts +27 -0
  329. package/templates/apps/changelog/content/releases/0.1.0.mdx +19 -0
  330. package/templates/apps/changelog/content/releases/0.2.0.mdx +28 -0
  331. package/templates/apps/changelog/package.json +30 -0
  332. package/templates/apps/changelog/scripts/generate-rss.mjs +38 -0
  333. package/templates/apps/changelog/src/globals.css +66 -0
  334. package/templates/apps/changelog/src/globals.d.ts +16 -0
  335. package/templates/apps/changelog/src/lib/locale.test.ts +72 -0
  336. package/templates/apps/changelog/src/lib/locale.ts +55 -0
  337. package/templates/apps/changelog/src/lib/releases.ts +21 -0
  338. package/templates/apps/changelog/src/locales/de.ts +22 -0
  339. package/templates/apps/changelog/src/locales/en.ts +30 -0
  340. package/templates/apps/changelog/src/pages/[locale]/[slug].tsx +24 -0
  341. package/templates/apps/changelog/src/pages/[locale]/index.tsx +13 -0
  342. package/templates/apps/changelog/src/pages/[locale]/mirrors.test.tsx +95 -0
  343. package/templates/apps/changelog/src/pages/[slug].test.tsx +127 -0
  344. package/templates/apps/changelog/src/pages/[slug].tsx +52 -0
  345. package/templates/apps/changelog/src/pages/index.test.tsx +112 -0
  346. package/templates/apps/changelog/src/pages/index.tsx +55 -0
  347. package/templates/apps/changelog/src/pages/layout.tsx +84 -0
  348. package/templates/apps/changelog/template.json +6 -0
  349. package/templates/apps/changelog/tsconfig.json +11 -0
  350. package/templates/apps/edge-functions/README.md +55 -0
  351. package/templates/apps/edge-functions/functions/aiComplete.serverless.ts +58 -0
  352. package/templates/apps/edge-functions/functions/currencyConvert.serverless.ts +47 -0
  353. package/templates/apps/edge-functions/functions/geoGreeting.serverless.ts +44 -0
  354. package/templates/apps/edge-functions/functions/health.serverless.ts +29 -0
  355. package/templates/apps/edge-functions/functions/resolveLink.serverless.ts +32 -0
  356. package/templates/apps/edge-functions/functions/shareLink.serverless.ts +30 -0
  357. package/templates/apps/edge-functions/functions/slackNotify.serverless.ts +40 -0
  358. package/templates/apps/edge-functions/functions/verifySignature.serverless.ts +52 -0
  359. package/templates/apps/edge-functions/package.json +21 -0
  360. package/templates/apps/edge-functions/template.json +6 -0
  361. package/templates/apps/edge-functions/tsconfig.json +10 -0
  362. package/templates/apps/frontend-admin/README.md +40 -0
  363. package/templates/apps/frontend-admin/app.config.ts +37 -0
  364. package/templates/apps/frontend-admin/package.json +30 -0
  365. package/templates/apps/frontend-admin/src/config.ts +8 -0
  366. package/templates/apps/frontend-admin/src/globals.css +76 -0
  367. package/templates/apps/frontend-admin/src/globals.d.ts +6 -0
  368. package/templates/apps/frontend-admin/src/lib/admin.ts +16 -0
  369. package/templates/apps/frontend-admin/src/lib/auth.ts +24 -0
  370. package/templates/apps/frontend-admin/src/locales/de.ts +67 -0
  371. package/templates/apps/frontend-admin/src/locales/en.ts +79 -0
  372. package/templates/apps/frontend-admin/src/locales/index.ts +15 -0
  373. package/templates/apps/frontend-admin/src/pages/(marketing)/index.test.tsx +55 -0
  374. package/templates/apps/frontend-admin/src/pages/(marketing)/index.tsx +32 -0
  375. package/templates/apps/frontend-admin/src/pages/(marketing)/layout.tsx +29 -0
  376. package/templates/apps/frontend-admin/src/pages/(marketing)/login.test.tsx +73 -0
  377. package/templates/apps/frontend-admin/src/pages/(marketing)/login.tsx +35 -0
  378. package/templates/apps/frontend-admin/src/pages/admin/[entity].tsx +121 -0
  379. package/templates/apps/frontend-admin/src/pages/admin/entity.test.tsx +119 -0
  380. package/templates/apps/frontend-admin/src/pages/admin/error.tsx +20 -0
  381. package/templates/apps/frontend-admin/src/pages/admin/fallbacks.test.tsx +68 -0
  382. package/templates/apps/frontend-admin/src/pages/admin/index.test.tsx +88 -0
  383. package/templates/apps/frontend-admin/src/pages/admin/index.tsx +65 -0
  384. package/templates/apps/frontend-admin/src/pages/admin/layout.test.tsx +114 -0
  385. package/templates/apps/frontend-admin/src/pages/admin/layout.tsx +97 -0
  386. package/templates/apps/frontend-admin/src/pages/admin/loading.tsx +15 -0
  387. package/templates/apps/frontend-admin/src/pages/admin/not-found.tsx +15 -0
  388. package/templates/apps/frontend-admin/src/pages/layout.tsx +12 -0
  389. package/templates/apps/frontend-admin/src/pages/layouts.test.tsx +54 -0
  390. package/templates/apps/frontend-admin/template.json +6 -0
  391. package/templates/apps/frontend-admin/tsconfig.json +11 -0
  392. package/templates/apps/frontend-app/README.md +98 -0
  393. package/templates/apps/frontend-app/app.config.ts +43 -0
  394. package/templates/apps/frontend-app/package.json +30 -0
  395. package/templates/apps/frontend-app/src/locales/de.ts +36 -0
  396. package/templates/apps/frontend-app/src/locales/en.ts +43 -0
  397. package/templates/apps/frontend-app/src/locales/index.ts +15 -0
  398. package/templates/apps/frontend-app/src/pages/index.test.tsx +167 -0
  399. package/templates/apps/frontend-app/src/pages/index.tsx +136 -0
  400. package/templates/apps/frontend-app/src/pages/layout.tsx +41 -0
  401. package/templates/apps/frontend-app/src/pages/schema-ui.test.tsx +99 -0
  402. package/templates/apps/frontend-app/src/pages/schema-ui.tsx +74 -0
  403. package/templates/apps/frontend-app/template.json +6 -0
  404. package/templates/apps/frontend-app/tsconfig.json +11 -0
  405. package/templates/apps/frontend-blank/README.md +18 -0
  406. package/templates/apps/frontend-blank/app.config.ts +29 -0
  407. package/templates/apps/frontend-blank/package.json +29 -0
  408. package/templates/apps/frontend-blank/src/locales/de.ts +15 -0
  409. package/templates/apps/frontend-blank/src/locales/en.ts +22 -0
  410. package/templates/apps/frontend-blank/src/locales/index.ts +15 -0
  411. package/templates/apps/frontend-blank/src/pages/index.test.tsx +55 -0
  412. package/templates/apps/frontend-blank/src/pages/index.tsx +27 -0
  413. package/templates/apps/frontend-blank/src/pages/layout.test.tsx +54 -0
  414. package/templates/apps/frontend-blank/src/pages/layout.tsx +35 -0
  415. package/templates/apps/frontend-blank/template.json +6 -0
  416. package/templates/apps/frontend-blank/tsconfig.json +11 -0
  417. package/templates/apps/frontend-contact/README.md +65 -0
  418. package/templates/apps/frontend-contact/app.config.ts +25 -0
  419. package/templates/apps/frontend-contact/functions/sendMessage.serverless.ts +69 -0
  420. package/templates/apps/frontend-contact/package.json +33 -0
  421. package/templates/apps/frontend-contact/src/components/ContactForm.island.test.tsx +142 -0
  422. package/templates/apps/frontend-contact/src/components/ContactForm.island.tsx +104 -0
  423. package/templates/apps/frontend-contact/src/config.ts +12 -0
  424. package/templates/apps/frontend-contact/src/globals.css +84 -0
  425. package/templates/apps/frontend-contact/src/lib/locale.ts +55 -0
  426. package/templates/apps/frontend-contact/src/locales/de.ts +26 -0
  427. package/templates/apps/frontend-contact/src/locales/en.ts +29 -0
  428. package/templates/apps/frontend-contact/src/pages/[locale]/index.tsx +14 -0
  429. package/templates/apps/frontend-contact/src/pages/index.test.tsx +70 -0
  430. package/templates/apps/frontend-contact/src/pages/index.tsx +63 -0
  431. package/templates/apps/frontend-contact/src/pages/layout.tsx +65 -0
  432. package/templates/apps/frontend-contact/template.json +6 -0
  433. package/templates/apps/frontend-contact/tsconfig.json +11 -0
  434. package/templates/apps/frontend-dashboard/README.md +54 -0
  435. package/templates/apps/frontend-dashboard/app.config.ts +39 -0
  436. package/templates/apps/frontend-dashboard/package.json +29 -0
  437. package/templates/apps/frontend-dashboard/src/config.ts +8 -0
  438. package/templates/apps/frontend-dashboard/src/globals.css +72 -0
  439. package/templates/apps/frontend-dashboard/src/globals.d.ts +6 -0
  440. package/templates/apps/frontend-dashboard/src/lib/auth.ts +27 -0
  441. package/templates/apps/frontend-dashboard/src/locales/de.ts +49 -0
  442. package/templates/apps/frontend-dashboard/src/locales/en.ts +60 -0
  443. package/templates/apps/frontend-dashboard/src/locales/index.ts +15 -0
  444. package/templates/apps/frontend-dashboard/src/pages/(marketing)/index.test.tsx +55 -0
  445. package/templates/apps/frontend-dashboard/src/pages/(marketing)/index.tsx +34 -0
  446. package/templates/apps/frontend-dashboard/src/pages/(marketing)/layout.tsx +32 -0
  447. package/templates/apps/frontend-dashboard/src/pages/(marketing)/login.test.tsx +74 -0
  448. package/templates/apps/frontend-dashboard/src/pages/(marketing)/login.tsx +40 -0
  449. package/templates/apps/frontend-dashboard/src/pages/dashboard/error.tsx +21 -0
  450. package/templates/apps/frontend-dashboard/src/pages/dashboard/fallbacks.test.tsx +68 -0
  451. package/templates/apps/frontend-dashboard/src/pages/dashboard/index.test.tsx +55 -0
  452. package/templates/apps/frontend-dashboard/src/pages/dashboard/index.tsx +38 -0
  453. package/templates/apps/frontend-dashboard/src/pages/dashboard/layout.test.tsx +88 -0
  454. package/templates/apps/frontend-dashboard/src/pages/dashboard/layout.tsx +69 -0
  455. package/templates/apps/frontend-dashboard/src/pages/dashboard/loading.tsx +21 -0
  456. package/templates/apps/frontend-dashboard/src/pages/dashboard/not-found.tsx +19 -0
  457. package/templates/apps/frontend-dashboard/src/pages/dashboard/settings.test.tsx +69 -0
  458. package/templates/apps/frontend-dashboard/src/pages/dashboard/settings.tsx +35 -0
  459. package/templates/apps/frontend-dashboard/src/pages/layout.tsx +12 -0
  460. package/templates/apps/frontend-dashboard/src/pages/layouts.test.tsx +54 -0
  461. package/templates/apps/frontend-dashboard/template.json +6 -0
  462. package/templates/apps/frontend-dashboard/tsconfig.json +11 -0
  463. package/templates/apps/frontend-docs/README.md +19 -0
  464. package/templates/apps/frontend-docs/app.config.ts +25 -0
  465. package/templates/apps/frontend-docs/package.json +29 -0
  466. package/templates/apps/frontend-docs/src/globals.css +32 -0
  467. package/templates/apps/frontend-docs/src/lib/locale.test.ts +72 -0
  468. package/templates/apps/frontend-docs/src/lib/locale.ts +56 -0
  469. package/templates/apps/frontend-docs/src/locales/de.ts +24 -0
  470. package/templates/apps/frontend-docs/src/locales/en.ts +28 -0
  471. package/templates/apps/frontend-docs/src/pages/[locale]/docs/[...slug].tsx +18 -0
  472. package/templates/apps/frontend-docs/src/pages/[locale]/index.tsx +13 -0
  473. package/templates/apps/frontend-docs/src/pages/[locale]/mirrors.test.tsx +67 -0
  474. package/templates/apps/frontend-docs/src/pages/docs/[...slug].test.tsx +115 -0
  475. package/templates/apps/frontend-docs/src/pages/docs/[...slug].tsx +82 -0
  476. package/templates/apps/frontend-docs/src/pages/index.test.tsx +90 -0
  477. package/templates/apps/frontend-docs/src/pages/index.tsx +45 -0
  478. package/templates/apps/frontend-docs/src/pages/layout.test.tsx +84 -0
  479. package/templates/apps/frontend-docs/src/pages/layout.tsx +66 -0
  480. package/templates/apps/frontend-docs/template.json +6 -0
  481. package/templates/apps/frontend-docs/tsconfig.json +11 -0
  482. package/templates/apps/frontend-i18n/README.md +61 -0
  483. package/templates/apps/frontend-i18n/app.config.ts +33 -0
  484. package/templates/apps/frontend-i18n/package.json +28 -0
  485. package/templates/apps/frontend-i18n/src/globals.css +46 -0
  486. package/templates/apps/frontend-i18n/src/globals.d.ts +6 -0
  487. package/templates/apps/frontend-i18n/src/lib/locale.test.ts +72 -0
  488. package/templates/apps/frontend-i18n/src/lib/locale.ts +55 -0
  489. package/templates/apps/frontend-i18n/src/locales/de.ts +24 -0
  490. package/templates/apps/frontend-i18n/src/locales/en.ts +25 -0
  491. package/templates/apps/frontend-i18n/src/pages/[locale]/about.tsx +11 -0
  492. package/templates/apps/frontend-i18n/src/pages/[locale]/index.tsx +13 -0
  493. package/templates/apps/frontend-i18n/src/pages/[locale]/mirrors.test.tsx +50 -0
  494. package/templates/apps/frontend-i18n/src/pages/about.test.tsx +75 -0
  495. package/templates/apps/frontend-i18n/src/pages/about.tsx +31 -0
  496. package/templates/apps/frontend-i18n/src/pages/index.test.tsx +102 -0
  497. package/templates/apps/frontend-i18n/src/pages/index.tsx +43 -0
  498. package/templates/apps/frontend-i18n/src/pages/layout.test.tsx +86 -0
  499. package/templates/apps/frontend-i18n/src/pages/layout.tsx +70 -0
  500. package/templates/apps/frontend-i18n/template.json +6 -0
  501. package/templates/apps/frontend-i18n/tsconfig.json +11 -0
  502. package/templates/apps/frontend-landing/README.md +17 -0
  503. package/templates/apps/frontend-landing/app.config.ts +25 -0
  504. package/templates/apps/frontend-landing/package.json +29 -0
  505. package/templates/apps/frontend-landing/src/globals.css +23 -0
  506. package/templates/apps/frontend-landing/src/lib/locale.test.ts +72 -0
  507. package/templates/apps/frontend-landing/src/lib/locale.ts +55 -0
  508. package/templates/apps/frontend-landing/src/locales/de.ts +24 -0
  509. package/templates/apps/frontend-landing/src/locales/en.ts +26 -0
  510. package/templates/apps/frontend-landing/src/pages/[locale]/index.tsx +13 -0
  511. package/templates/apps/frontend-landing/src/pages/[locale]/mirrors.test.tsx +37 -0
  512. package/templates/apps/frontend-landing/src/pages/index.test.tsx +110 -0
  513. package/templates/apps/frontend-landing/src/pages/index.tsx +75 -0
  514. package/templates/apps/frontend-landing/src/pages/layout.test.tsx +84 -0
  515. package/templates/apps/frontend-landing/src/pages/layout.tsx +66 -0
  516. package/templates/apps/frontend-landing/template.json +6 -0
  517. package/templates/apps/frontend-landing/tsconfig.json +11 -0
  518. package/templates/apps/frontend-spa/README.md +45 -0
  519. package/templates/apps/frontend-spa/app.config.ts +27 -0
  520. package/templates/apps/frontend-spa/package.json +29 -0
  521. package/templates/apps/frontend-spa/src/globals.css +84 -0
  522. package/templates/apps/frontend-spa/src/locales/de.ts +22 -0
  523. package/templates/apps/frontend-spa/src/locales/en.ts +29 -0
  524. package/templates/apps/frontend-spa/src/locales/index.ts +15 -0
  525. package/templates/apps/frontend-spa/src/pages/index.test.tsx +137 -0
  526. package/templates/apps/frontend-spa/src/pages/index.tsx +123 -0
  527. package/templates/apps/frontend-spa/src/pages/layout.tsx +27 -0
  528. package/templates/apps/frontend-spa/template.json +6 -0
  529. package/templates/apps/frontend-spa/tsconfig.json +11 -0
  530. package/templates/apps/frontend-ssr/README.md +68 -0
  531. package/templates/apps/frontend-ssr/app.config.ts +32 -0
  532. package/templates/apps/frontend-ssr/package.json +29 -0
  533. package/templates/apps/frontend-ssr/src/globals.css +67 -0
  534. package/templates/apps/frontend-ssr/src/locales/de.ts +41 -0
  535. package/templates/apps/frontend-ssr/src/locales/en.ts +54 -0
  536. package/templates/apps/frontend-ssr/src/locales/index.ts +16 -0
  537. package/templates/apps/frontend-ssr/src/pages/feed-swr.test.tsx +69 -0
  538. package/templates/apps/frontend-ssr/src/pages/feed-swr.tsx +54 -0
  539. package/templates/apps/frontend-ssr/src/pages/feed.test.tsx +73 -0
  540. package/templates/apps/frontend-ssr/src/pages/feed.tsx +64 -0
  541. package/templates/apps/frontend-ssr/src/pages/index.test.tsx +89 -0
  542. package/templates/apps/frontend-ssr/src/pages/index.tsx +72 -0
  543. package/templates/apps/frontend-ssr/src/pages/layout.tsx +37 -0
  544. package/templates/apps/frontend-ssr/template.json +6 -0
  545. package/templates/apps/frontend-ssr/tsconfig.json +11 -0
  546. package/templates/apps/frontend-ssr-api/README.md +50 -0
  547. package/templates/apps/frontend-ssr-api/app.config.ts +43 -0
  548. package/templates/apps/frontend-ssr-api/package.json +30 -0
  549. package/templates/apps/frontend-ssr-api/src/globals.css +38 -0
  550. package/templates/apps/frontend-ssr-api/src/globals.d.ts +6 -0
  551. package/templates/apps/frontend-ssr-api/src/locales/de.ts +20 -0
  552. package/templates/apps/frontend-ssr-api/src/locales/en.ts +31 -0
  553. package/templates/apps/frontend-ssr-api/src/locales/index.ts +16 -0
  554. package/templates/apps/frontend-ssr-api/src/pages/index.test.tsx +105 -0
  555. package/templates/apps/frontend-ssr-api/src/pages/index.tsx +83 -0
  556. package/templates/apps/frontend-ssr-api/src/pages/layout.tsx +28 -0
  557. package/templates/apps/frontend-ssr-api/template.json +6 -0
  558. package/templates/apps/frontend-ssr-api/tsconfig.json +11 -0
  559. package/templates/apps/frontend-static-blog/README.md +49 -0
  560. package/templates/apps/frontend-static-blog/app.config.ts +34 -0
  561. package/templates/apps/frontend-static-blog/package.json +28 -0
  562. package/templates/apps/frontend-static-blog/src/components/ReadingProgress.island.test.tsx +65 -0
  563. package/templates/apps/frontend-static-blog/src/components/ReadingProgress.island.tsx +35 -0
  564. package/templates/apps/frontend-static-blog/src/content/posts.ts +64 -0
  565. package/templates/apps/frontend-static-blog/src/globals.css +75 -0
  566. package/templates/apps/frontend-static-blog/src/lib/locale.test.ts +72 -0
  567. package/templates/apps/frontend-static-blog/src/lib/locale.ts +55 -0
  568. package/templates/apps/frontend-static-blog/src/locales/de.ts +19 -0
  569. package/templates/apps/frontend-static-blog/src/locales/en.ts +26 -0
  570. package/templates/apps/frontend-static-blog/src/pages/[locale]/blog/[slug].tsx +20 -0
  571. package/templates/apps/frontend-static-blog/src/pages/[locale]/index.tsx +13 -0
  572. package/templates/apps/frontend-static-blog/src/pages/[locale]/mirrors.test.tsx +62 -0
  573. package/templates/apps/frontend-static-blog/src/pages/blog/[slug].test.tsx +116 -0
  574. package/templates/apps/frontend-static-blog/src/pages/blog/[slug].tsx +69 -0
  575. package/templates/apps/frontend-static-blog/src/pages/index.test.tsx +100 -0
  576. package/templates/apps/frontend-static-blog/src/pages/index.tsx +58 -0
  577. package/templates/apps/frontend-static-blog/src/pages/layout.tsx +63 -0
  578. package/templates/apps/frontend-static-blog/template.json +6 -0
  579. package/templates/apps/frontend-static-blog/tsconfig.json +11 -0
  580. package/templates/baselines/bare/.env.example +37 -0
  581. package/templates/baselines/bare/README.md +47 -0
  582. package/templates/baselines/bare/baseline.json +31 -0
  583. package/templates/baselines/bare/deploy/README.md +43 -0
  584. package/templates/baselines/bare/deploy/voltro.service.example +36 -0
  585. package/templates/baselines/compose/.env.example +46 -0
  586. package/templates/baselines/compose/README.md +69 -0
  587. package/templates/baselines/compose/baseline.json +51 -0
  588. package/templates/baselines/compose/docker/.dockerignore +38 -0
  589. package/templates/baselines/compose/docker/api.Dockerfile +57 -0
  590. package/templates/baselines/compose/docker/dev.Dockerfile +35 -0
  591. package/templates/baselines/compose/docker/web.Dockerfile +59 -0
  592. package/templates/baselines/compose/docker-compose.dev.yml +89 -0
  593. package/templates/baselines/compose/docker-compose.prod.yml +87 -0
  594. package/templates/baselines/compose/docker-compose.yml +41 -0
  595. package/templates/baselines/compose-mariadb/.env.example +57 -0
  596. package/templates/baselines/compose-mariadb/README.md +78 -0
  597. package/templates/baselines/compose-mariadb/baseline.json +51 -0
  598. package/templates/baselines/compose-mariadb/docker/.dockerignore +38 -0
  599. package/templates/baselines/compose-mariadb/docker/api.Dockerfile +57 -0
  600. package/templates/baselines/compose-mariadb/docker/dev.Dockerfile +35 -0
  601. package/templates/baselines/compose-mariadb/docker/mariadb-init.sql +6 -0
  602. package/templates/baselines/compose-mariadb/docker/web.Dockerfile +59 -0
  603. package/templates/baselines/compose-mariadb/docker-compose.dev.yml +117 -0
  604. package/templates/baselines/compose-mariadb/docker-compose.prod.yml +114 -0
  605. package/templates/baselines/compose-mariadb/docker-compose.yml +79 -0
  606. package/templates/baselines/helm/.env.example +39 -0
  607. package/templates/baselines/helm/README.md +98 -0
  608. package/templates/baselines/helm/baseline.json +53 -0
  609. package/templates/baselines/helm/charts/voltro-app/.helmignore +10 -0
  610. package/templates/baselines/helm/charts/voltro-app/Chart.yaml +10 -0
  611. package/templates/baselines/helm/charts/voltro-app/templates/_helpers.tpl +36 -0
  612. package/templates/baselines/helm/charts/voltro-app/templates/configmap.yaml +13 -0
  613. package/templates/baselines/helm/charts/voltro-app/templates/deployment-api.yaml +120 -0
  614. package/templates/baselines/helm/charts/voltro-app/templates/deployment-web.yaml +45 -0
  615. package/templates/baselines/helm/charts/voltro-app/templates/ingress.yaml +37 -0
  616. package/templates/baselines/helm/charts/voltro-app/templates/postgres-service.yaml +19 -0
  617. package/templates/baselines/helm/charts/voltro-app/templates/postgres-statefulset.yaml +73 -0
  618. package/templates/baselines/helm/charts/voltro-app/templates/secret.yaml +33 -0
  619. package/templates/baselines/helm/charts/voltro-app/templates/service-api.yaml +19 -0
  620. package/templates/baselines/helm/charts/voltro-app/templates/service-web.yaml +19 -0
  621. package/templates/baselines/helm/charts/voltro-app/values-dev.yaml +21 -0
  622. package/templates/baselines/helm/charts/voltro-app/values-prod.yaml +58 -0
  623. package/templates/baselines/helm/charts/voltro-app/values-staging.yaml +25 -0
  624. package/templates/baselines/helm/charts/voltro-app/values.yaml +109 -0
  625. package/templates/baselines/helm/deploy/README.md +94 -0
  626. package/templates/patches/@effect__cluster@0.59.0.patch +262 -0
@@ -0,0 +1,42 @@
1
+ // Rate-limited backend for the {{projectName}} project.
2
+ //
3
+ // `rateLimitPlugin` runs on the rpc interceptors — the one surface that sees
4
+ // every call AND the resolved subject. A `default` fallback applies to every
5
+ // procedure; `rules` override per endpoint (exact tag or `/regex/`), with a
6
+ // choice of algorithm (sliding-window / fixed-window / token-bucket+burst) and
7
+ // composite keying (`by: 'subject' | 'tenant' | 'apiKey' | 'global'` or an
8
+ // array). Over-limit calls fail a typed `RateLimited` error — the plugin merges
9
+ // it into every procedure's wire error union, so the client decodes it typed.
10
+ // `store: 'memory'` is single-node; switch to 'postgres' / 'redis' for a cluster.
11
+ import { defineEnv, envVar } from '@voltro/env'
12
+ import { rateLimitPlugin } from '@voltro/plugin-ratelimit'
13
+
14
+ export const env = defineEnv({
15
+ LOG_LEVEL: envVar.enum(['debug', 'info', 'warn', 'error'], { access: 'public', default: 'info' }),
16
+ })
17
+
18
+ export default {
19
+ type: 'api' as const,
20
+ name: '{{capProjectName}}{{capAppName}}',
21
+ store: 'memory' as const,
22
+ plugins: [
23
+ rateLimitPlugin({
24
+ // Global fallback: 100 calls/min, per subject.
25
+ default: { limit: 100, window: '1m' },
26
+ rules: [
27
+ // Tight, per-endpoint: notes.create → 3/min per TENANT, token-bucket
28
+ // (the bucket starts full, so a burst of 3 is allowed, then refills).
29
+ {
30
+ match: 'notes.create',
31
+ limit: 3,
32
+ window: '1m',
33
+ algorithm: 'token-bucket',
34
+ burst: 3,
35
+ by: 'tenant',
36
+ },
37
+ ],
38
+ store: 'memory',
39
+ }),
40
+ ],
41
+ env,
42
+ }
@@ -0,0 +1,37 @@
1
+ // Schema for the {{projectName}} rate-limited backend — a tiny `notes` domain
2
+ // to throttle. `actors` + `tenants` are the framework core tables.
3
+
4
+ import {
5
+ databaseHandle,
6
+ id,
7
+ table,
8
+ text,
9
+ timestamp,
10
+ type InferRow,
11
+ } from '@voltro/database'
12
+ import { tenant } from '@voltro/plugin-multitenancy'
13
+
14
+ export const actors = table('actors', {
15
+ id: id(),
16
+ kind: text().oneOf(['user', 'serviceAccount', 'apiKey', 'system']),
17
+ displayName: text().nullable(),
18
+ createdAt: timestamp().default('now'),
19
+ })
20
+
21
+ export const tenants = table('tenants', {
22
+ id: id(),
23
+ name: text(),
24
+ createdAt: timestamp().default('now'),
25
+ })
26
+
27
+ export const notes = table('notes', {
28
+ id: id({ prefix: 'note' }),
29
+ title: text(),
30
+ body: text(),
31
+ })
32
+ .with(tenant())
33
+ .reactive()
34
+
35
+ export type Note = InferRow<typeof notes>
36
+
37
+ export const database = databaseHandle({ actors, tenants, notes })
@@ -0,0 +1,16 @@
1
+ import { assertOwnTenant } from '@voltro/plugin-multitenancy/guard'
2
+ import type { AppContext } from '@voltro/runtime'
3
+
4
+ const execute = async (
5
+ input: { tenantId: string; title: string; body: string },
6
+ ctx: AppContext,
7
+ ) => {
8
+ assertOwnTenant(input.tenantId, ctx.request.subject)
9
+ return ctx.store.insert('notes', {
10
+ title: input.title,
11
+ body: input.body,
12
+ tenantId: input.tenantId,
13
+ })
14
+ }
15
+
16
+ export default execute
@@ -0,0 +1,34 @@
1
+ // Creates a note. There's NO rate-limit code here — the limit is enforced by
2
+ // the plugin's rpc interceptor (see app.config.ts). When the caller is over
3
+ // the limit, the call fails a typed `RateLimited` error that the plugin merges
4
+ // into this procedure's wire error union automatically — you don't declare it.
5
+ // (`TenantMismatch` is the explicit error this handler can throw.)
6
+
7
+ import { defineMutation } from '@voltro/protocol'
8
+ import { TenantMismatch } from '@voltro/plugin-multitenancy/guard'
9
+ import { Schema } from 'effect'
10
+
11
+ export const createNote = defineMutation({
12
+ name: 'notes.create',
13
+ target: {
14
+ table: 'notes',
15
+ op: 'insert',
16
+ shape: (input: { tenantId: string; title: string; body: string }) => ({
17
+ title: input.title,
18
+ body: input.body,
19
+ tenantId: input.tenantId,
20
+ }),
21
+ },
22
+ input: Schema.Struct({
23
+ tenantId: Schema.String,
24
+ title: Schema.NonEmptyString,
25
+ body: Schema.String,
26
+ }),
27
+ output: Schema.Struct({
28
+ id: Schema.String,
29
+ title: Schema.String,
30
+ body: Schema.String,
31
+ tenantId: Schema.String,
32
+ }),
33
+ error: TenantMismatch,
34
+ })
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "@{{projectName}}/{{appName}}",
3
+ "version": "0.0.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "voltro dev .",
8
+ "migrate": "voltro migrate",
9
+ "test": "voltro test",
10
+ "typecheck": "tsc --noEmit"
11
+ },
12
+ "dependencies": {
13
+ "@effect/platform": "^0.96.1",
14
+ "@effect/rpc": "^0.75.1",
15
+ "@voltro/cli": "0.1.0",
16
+ "@voltro/database": "0.1.0",
17
+ "@voltro/env": "0.1.0",
18
+ "@voltro/plugin-multitenancy": "0.1.0",
19
+ "@voltro/plugin-ratelimit": "0.1.0",
20
+ "@voltro/protocol": "0.1.0",
21
+ "@voltro/runtime": "0.1.0",
22
+ "effect": "^3.21.2"
23
+ },
24
+ "devDependencies": {
25
+ "@voltro/testing": "0.1.0",
26
+ "typescript": "^5.7.0",
27
+ "vitest": "^3.0.0"
28
+ }
29
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "id": "api-ratelimit",
3
+ "kind": "api",
4
+ "summary": "Per-endpoint / per-subject / per-tenant rate limiting — @voltro/plugin-ratelimit intercepts rpc calls: a default fallback + rules (exact tag or regex), sliding-window / token-bucket algorithms, composite keying (by subject/tenant/apiKey/global), and a typed RateLimited error auto-merged into the wire error union. Memory store, zero infra.",
5
+ "tags": ["api", "rate-limit", "throttle", "token-bucket", "plugin"]
6
+ }
@@ -0,0 +1,69 @@
1
+ // Unit test for `notes.create` — driven through `@voltro/testing`.
2
+ //
3
+ // The rate limit itself is enforced by the plugin's rpc INTERCEPTOR (a
4
+ // dispatch-time middleware wired in app.config.ts), NOT by this executor — and
5
+ // `invoke` reproduces only the input-Schema decode, not middleware (see its
6
+ // header). So we test the executor's OWN observable behavior: the tenant-guarded
7
+ // insert. `makeTestContext` hands the handler the SAME mixin-wrapped `ctx.store`
8
+ // it gets in production, so tenant auto-scoping behaves as at runtime.
9
+ // Run with `voltro test` (vitest).
10
+
11
+ import { describe, it, expect } from 'vitest'
12
+ import { makeTestContext, mockStore, invoke } from '@voltro/testing'
13
+ import { database } from '../database/schema' // registers actors / tenants / notes
14
+ import { createNote } from '../mutations/notes.create.mutation'
15
+ import createNoteHandler from '../mutations/notes.create.mutation.server'
16
+ import { TenantMismatch } from '@voltro/plugin-multitenancy/guard'
17
+
18
+ const subject = { type: 'user', id: 'u1', tenantId: 'acme' } as const
19
+
20
+ describe('notes.create', () => {
21
+ it('inserts a note stamped with the caller tenant + a note_ TypeID', async () => {
22
+ const ctx = makeTestContext({ subject, store: mockStore({ notes: [] }) })
23
+
24
+ const row = (await invoke(
25
+ createNote,
26
+ createNoteHandler,
27
+ { tenantId: 'acme', title: 'Ship it', body: 'World' },
28
+ ctx,
29
+ )) as { id: string; title: string; body: string; tenantId: string }
30
+
31
+ expect(row.title).toBe('Ship it')
32
+ expect(row.tenantId).toBe('acme')
33
+ expect(row.id).toMatch(/^note_/)
34
+
35
+ // The row is really in the store — the live tenant-scoped query sees it.
36
+ const rows = await ctx.store.query(database.notes.descriptor)
37
+ expect(rows).toHaveLength(1)
38
+ })
39
+
40
+ it('rejects a cross-tenant write with a typed TenantMismatch — nothing written', async () => {
41
+ const ctx = makeTestContext({ subject, store: mockStore({ notes: [] }) })
42
+
43
+ await expect(
44
+ invoke(createNote, createNoteHandler, { tenantId: 'evil-corp', title: 'x', body: '' }, ctx),
45
+ ).rejects.toBeInstanceOf(TenantMismatch)
46
+
47
+ const rows = await ctx.store.select('notes').unscoped().all()
48
+ expect(rows).toHaveLength(0)
49
+ })
50
+
51
+ it('rejects an empty title at the input-schema decode (NonEmptyString)', async () => {
52
+ const ctx = makeTestContext({ subject, store: mockStore({ notes: [] }) })
53
+
54
+ await expect(
55
+ invoke(createNote, createNoteHandler, { tenantId: 'acme', title: '', body: '' }, ctx),
56
+ ).rejects.toThrow()
57
+ })
58
+
59
+ it('isolates tenants — a second tenant never sees the first tenant row', async () => {
60
+ const ctx = makeTestContext({
61
+ subject: { type: 'user', id: 'u1', tenantId: 't1' },
62
+ store: mockStore({ notes: [] }),
63
+ })
64
+ await invoke(createNote, createNoteHandler, { tenantId: 't1', title: 'secret', body: '' }, ctx)
65
+
66
+ const seenByT2 = await ctx.withTenant('t2', (c) => c.store.query(database.notes.descriptor))
67
+ expect(seenByT2).toHaveLength(0)
68
+ })
69
+ })
@@ -0,0 +1,5 @@
1
+ {
2
+ "extends": "../../../tsconfig.base.json",
3
+ "include": ["**/*.ts", "**/*.tsx"],
4
+ "exclude": ["node_modules", "dist"]
5
+ }
@@ -0,0 +1,59 @@
1
+ # {{projectName}} / {{appName}}
2
+
3
+ Voltro API scaffold (template: **api-rbac**) — role-based access control with
4
+ `@voltro/plugin-rbac`.
5
+
6
+ ## Boot
7
+
8
+ ```bash
9
+ pnpm install
10
+ pnpm --filter @{{projectName}}/{{appName}} dev
11
+ # → http://localhost:4000 (store: memory — zero infra)
12
+ ```
13
+
14
+ ## What this shows
15
+
16
+ - **Roles → scopes, as config** — `app.config.ts` declares a role map
17
+ (`viewer → notes:read`, `editor → notes:read + notes:write`,
18
+ `admin → admin:full`). An rpc interceptor resolves each caller's roles to
19
+ scopes and stamps them on the subject.
20
+ - **Guard with `permission()`** — `notes.create` calls
21
+ `permission(ctx, 'notes:write')`, the Effect-native guard. It fails typed
22
+ `Forbidden` when the caller lacks the scope; `admin:full` is a blanket bypass.
23
+ Companions: `anyPermission(ctx, [...])` (OR), `assertPermission` (sync throw),
24
+ `can(ctx, scope)` (boolean).
25
+ - **Where roles come from** — the shipped `resolveRoles` is a DEMO that maps
26
+ the request's **tenant** to a role so you can try each with the `x-tenant`
27
+ header. Production reads the caller's real roles: the default resolver reads
28
+ `subject.metadata.roles` (set by your auth strategy), or do a DB lookup.
29
+
30
+ ## Try it
31
+
32
+ ```bash
33
+ # admin tenant (acme) → admin:full → notes:write passes:
34
+ curl -s localhost:4000/_voltro/inspect/invoke -H 'content-type: application/json' -H 'x-tenant: acme' \
35
+ -d '{"tag":"notes.create","input":{"title":"hi","body":"x"}}'
36
+ # → { ok:true, result:{ id, … } }
37
+
38
+ # readers tenant → viewer → notes:write missing → typed Forbidden:
39
+ curl -s localhost:4000/_voltro/inspect/invoke -H 'content-type: application/json' -H 'x-tenant: readers' \
40
+ -d '{"tag":"notes.create","input":{"title":"hi","body":"x"}}'
41
+ # → { ok:false, error:{ _tag:"Forbidden", required:"notes:write" } }
42
+ ```
43
+
44
+ ## On the web side
45
+
46
+ ```tsx
47
+ import { useCan } from '@voltro/plugin-rbac/web'
48
+ const canWrite = useCan('notes:write')
49
+ {canWrite && <NewNoteButton />} // hide affordances the user can't use
50
+ ```
51
+
52
+ `useCan` is a UI affordance only — the server `permission()` guard is the real
53
+ enforcement. Never trust the client.
54
+
55
+ ## Persisted roles
56
+
57
+ Set `tables: true` to store role assignments in `roles` / `userRoles` tables
58
+ and resolve them from the DB (multitenant-aware). The shipped template stays
59
+ config-only.
@@ -0,0 +1,48 @@
1
+ // RBAC backend for the {{projectName}} project.
2
+ //
3
+ // `rbacPlugin` compiles a role→scope map; an rpc interceptor resolves each
4
+ // caller's ROLES to SCOPES and stamps them on the subject, so in-handler
5
+ // guards like `permission(ctx, 'notes:write')` (Effect-native, fails typed
6
+ // `Forbidden`) just work. The `admin:full` scope is a blanket bypass.
7
+ //
8
+ // Config-only — no tables, no infra.
9
+ import { defineEnv, envVar } from '@voltro/env'
10
+ import { rbacPlugin } from '@voltro/plugin-rbac'
11
+
12
+ export const env = defineEnv({
13
+ LOG_LEVEL: envVar.enum(['debug', 'info', 'warn', 'error'], { access: 'public', default: 'info' }),
14
+ })
15
+
16
+ // role → the scopes it grants. Scopes are `domain:action`; `admin:full` is the
17
+ // framework's blanket bypass (passes every permission check).
18
+ const roles = {
19
+ viewer: ['notes:read'],
20
+ editor: ['notes:read', 'notes:write'],
21
+ admin: ['admin:full'],
22
+ }
23
+
24
+ export default {
25
+ type: 'api' as const,
26
+ name: '{{capProjectName}}{{capAppName}}',
27
+ store: 'memory' as const,
28
+ plugins: [
29
+ rbacPlugin({
30
+ roles,
31
+ // DEMO resolver — assigns a role by the request's tenant so you can try
32
+ // each role via the `x-tenant` header with zero auth setup. PRODUCTION
33
+ // reads the caller's REAL roles: the default resolver reads
34
+ // `subject.metadata.roles` (set by your auth strategy), or do a DB
35
+ // lookup here (`subject.id` / `subject.tenantId` → roles). Delete this
36
+ // whole `resolveRoles` to fall back to the metadata default.
37
+ resolveRoles: (subject) => {
38
+ const demo: Record<string, ReadonlyArray<string>> = {
39
+ acme: ['admin'],
40
+ editors: ['editor'],
41
+ readers: ['viewer'],
42
+ }
43
+ return demo[subject.tenantId ?? ''] ?? ['viewer']
44
+ },
45
+ }),
46
+ ],
47
+ env,
48
+ }
@@ -0,0 +1,37 @@
1
+ // Schema for the {{projectName}} RBAC backend — a tiny `notes` domain to
2
+ // guard. `actors` + `tenants` are the framework core tables.
3
+
4
+ import {
5
+ databaseHandle,
6
+ id,
7
+ table,
8
+ text,
9
+ timestamp,
10
+ type InferRow,
11
+ } from '@voltro/database'
12
+ import { tenant } from '@voltro/plugin-multitenancy'
13
+
14
+ export const actors = table('actors', {
15
+ id: id(),
16
+ kind: text().oneOf(['user', 'serviceAccount', 'apiKey', 'system']),
17
+ displayName: text().nullable(),
18
+ createdAt: timestamp().default('now'),
19
+ })
20
+
21
+ export const tenants = table('tenants', {
22
+ id: id(),
23
+ name: text(),
24
+ createdAt: timestamp().default('now'),
25
+ })
26
+
27
+ export const notes = table('notes', {
28
+ id: id({ prefix: 'note' }),
29
+ title: text(),
30
+ body: text(),
31
+ })
32
+ .with(tenant())
33
+ .reactive()
34
+
35
+ export type Note = InferRow<typeof notes>
36
+
37
+ export const database = databaseHandle({ actors, tenants, notes })
@@ -0,0 +1,22 @@
1
+ import { EffectStore } from '@voltro/runtime'
2
+ import type { AppContext } from '@voltro/runtime'
3
+ import { permission } from '@voltro/plugin-rbac'
4
+ import { Effect } from 'effect'
5
+
6
+ const execute = (input: { title: string; body: string }, ctx: AppContext) =>
7
+ Effect.gen(function* () {
8
+ // RBAC guard — passes for editor/admin, fails typed Forbidden for viewer.
9
+ yield* permission(ctx, 'notes:write')
10
+
11
+ const store = yield* EffectStore
12
+ // tenant() auto-fills tenantId from the subject — no need to pass it.
13
+ const row = yield* store.insert('notes', { title: input.title, body: input.body })
14
+ return {
15
+ id: row['id'] as string,
16
+ title: row['title'] as string,
17
+ body: row['body'] as string,
18
+ tenantId: row['tenantId'] as string,
19
+ }
20
+ })
21
+
22
+ export default execute
@@ -0,0 +1,26 @@
1
+ // Creates a note — requires the `notes:write` scope. The executor guards with
2
+ // `permission(ctx, 'notes:write')`, the Effect-native RBAC guard that fails
3
+ // typed `Forbidden` when the caller's resolved scopes don't include it.
4
+ // Declaring `error: Forbidden` surfaces that to the client typed.
5
+ //
6
+ // `Forbidden` comes from the browser-safe `/errors` subpath.
7
+
8
+ import { defineMutation } from '@voltro/protocol'
9
+ import { Forbidden } from '@voltro/plugin-rbac/errors'
10
+ import { Schema } from 'effect'
11
+
12
+ export const createNote = defineMutation({
13
+ name: 'notes.create',
14
+ target: { table: 'notes', op: 'insert' },
15
+ input: Schema.Struct({
16
+ title: Schema.NonEmptyString,
17
+ body: Schema.String,
18
+ }),
19
+ output: Schema.Struct({
20
+ id: Schema.String,
21
+ title: Schema.String,
22
+ body: Schema.String,
23
+ tenantId: Schema.String,
24
+ }),
25
+ error: Forbidden,
26
+ })
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "@{{projectName}}/{{appName}}",
3
+ "version": "0.0.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "voltro dev .",
8
+ "migrate": "voltro migrate",
9
+ "test": "voltro test",
10
+ "typecheck": "tsc --noEmit"
11
+ },
12
+ "dependencies": {
13
+ "@effect/platform": "^0.96.1",
14
+ "@effect/rpc": "^0.75.1",
15
+ "@voltro/cli": "0.1.0",
16
+ "@voltro/database": "0.1.0",
17
+ "@voltro/env": "0.1.0",
18
+ "@voltro/plugin-multitenancy": "0.1.0",
19
+ "@voltro/plugin-rbac": "0.1.0",
20
+ "@voltro/protocol": "0.1.0",
21
+ "@voltro/runtime": "0.1.0",
22
+ "effect": "^3.21.2"
23
+ },
24
+ "devDependencies": {
25
+ "@voltro/testing": "0.1.0",
26
+ "typescript": "^5.7.0",
27
+ "vitest": "^3.0.0"
28
+ }
29
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "id": "api-rbac",
3
+ "kind": "api",
4
+ "summary": "Role-based access control — @voltro/plugin-rbac compiles a role→scope map and an interceptor resolves the caller's roles to scopes; guard handlers with permission(ctx, 'notes:write') (Effect-native, fails typed Forbidden). admin:full bypasses. Memory/config-only, zero infra; useCan() for web UI affordance.",
5
+ "tags": ["api", "rbac", "authz", "roles", "permissions", "plugin"]
6
+ }
@@ -0,0 +1,80 @@
1
+ // Unit test for `notes.create` — driven through `@voltro/testing`.
2
+ //
3
+ // This executor is Effect-native: it `yield*`s the RBAC guard
4
+ // `permission(ctx, 'notes:write')` (fails typed `Forbidden` for a caller
5
+ // lacking the scope) and then `yield* EffectStore` to insert. `makeTestContext`
6
+ // exposes the mixin-wrapped store as `ctx.store`; we provide it to the Effect as
7
+ // the `EffectStore` layer via `makeEffectStoreLayer` — the SAME wiring the CLI
8
+ // dev/serve dispatcher does — so the handler runs exactly as at runtime.
9
+ //
10
+ // In production the rbac INTERCEPTOR resolves the caller's roles → scopes and
11
+ // stashes them on the subject before the handler runs. That interceptor is a
12
+ // dispatch-time concern the unit harness doesn't run, so we drive the handler's
13
+ // OWN guard directly by putting the resolved scope on `subject.scopes` — exactly
14
+ // what the interceptor would have stashed. Run with `voltro test` (vitest).
15
+
16
+ import { describe, it, expect } from 'vitest'
17
+ import { Cause, Effect, Exit, Option, Schema } from 'effect'
18
+ import { makeTestContext, mockStore } from '@voltro/testing'
19
+ import { makeEffectStoreLayer } from '@voltro/runtime'
20
+ import { database } from '../database/schema' // registers actors / tenants / notes
21
+ import { createNote } from '../mutations/notes.create.mutation'
22
+ import createNoteHandler from '../mutations/notes.create.mutation.server'
23
+ import { Forbidden } from '@voltro/plugin-rbac/errors'
24
+
25
+ // Run the Effect-native executor with the test store provided as EffectStore.
26
+ const runExec = (input: { title: string; body: string }, ctx: ReturnType<typeof makeTestContext>) =>
27
+ Effect.runPromise(createNoteHandler(input, ctx).pipe(Effect.provide(makeEffectStoreLayer(ctx.store))))
28
+
29
+ // Same, but returns the raw Exit so a typed failure can be inspected WITHOUT
30
+ // the `FiberFailure` wrapper `runPromise` throws (which would defeat a plain
31
+ // `instanceof Forbidden` assertion). Unwrap the Cause to get the real error.
32
+ const runExit = (input: { title: string; body: string }, ctx: ReturnType<typeof makeTestContext>) =>
33
+ Effect.runPromiseExit(createNoteHandler(input, ctx).pipe(Effect.provide(makeEffectStoreLayer(ctx.store))))
34
+
35
+ describe('notes.create', () => {
36
+ it('inserts for a caller holding notes:write — tenant-stamped, note_ TypeID', async () => {
37
+ const ctx = makeTestContext({
38
+ subject: { type: 'user', id: 'editor_1', tenantId: 'acme', scopes: ['notes:write'] },
39
+ store: mockStore({ notes: [] }),
40
+ })
41
+
42
+ const row = await runExec({ title: 'Ship it', body: 'World' }, ctx)
43
+
44
+ expect(row.title).toBe('Ship it')
45
+ expect(row.tenantId).toBe('acme') // tenant() auto-filled from the subject
46
+ expect(row.id).toMatch(/^note_/)
47
+
48
+ // The row is really in the store — the live tenant-scoped query sees it.
49
+ const rows = await ctx.store.query(database.notes.descriptor)
50
+ expect(rows).toHaveLength(1)
51
+ })
52
+
53
+ it('rejects a caller WITHOUT notes:write with typed Forbidden — nothing written', async () => {
54
+ const ctx = makeTestContext({
55
+ subject: { type: 'user', id: 'viewer_1', tenantId: 'acme', scopes: [] },
56
+ store: mockStore({ notes: [] }),
57
+ })
58
+
59
+ const exit = await runExit({ title: 'x', body: '' }, ctx)
60
+
61
+ expect(Exit.isFailure(exit)).toBe(true)
62
+ const failure = Exit.isFailure(exit) ? Cause.failureOption(exit.cause) : Option.none()
63
+ expect(Option.getOrThrow(failure)).toBeInstanceOf(Forbidden)
64
+
65
+ const rows = await ctx.store.query(database.notes.descriptor)
66
+ expect(rows).toHaveLength(0)
67
+ })
68
+
69
+ it('pins the descriptor: name, input decode + the declared Forbidden error', () => {
70
+ expect(createNote.name).toBe('notes.create')
71
+
72
+ const decoded = Schema.decodeUnknownSync(createNote.input)({ title: 'ok', body: 'b' })
73
+ expect(decoded).toEqual({ title: 'ok', body: 'b' })
74
+ // Empty title is rejected at the input Schema (NonEmptyString).
75
+ expect(() => Schema.decodeUnknownSync(createNote.input)({ title: '', body: '' })).toThrow()
76
+
77
+ const forbidden = new Forbidden({ required: 'notes:write' })
78
+ expect(Schema.is(Forbidden)(forbidden)).toBe(true)
79
+ })
80
+ })
@@ -0,0 +1,5 @@
1
+ {
2
+ "extends": "../../../tsconfig.base.json",
3
+ "include": ["**/*.ts", "**/*.tsx"],
4
+ "exclude": ["node_modules", "dist"]
5
+ }
@@ -0,0 +1,85 @@
1
+ # {{capProjectName}} {{capAppName}} — public REST API
2
+
3
+ A turnkey **public REST API** built on `defineRestRoute`, with an
4
+ auto-generated **OpenAPI 3.1 spec + Swagger UI**. Unlike the reactive
5
+ rpc/WebSocket surface (`api-backend`), these are plain raw-HTTP endpoints a
6
+ third party calls with a URL + JSON.
7
+
8
+ ## What it demonstrates
9
+
10
+ - **`defineRestRoute`** — four endpoints over a `products` catalog:
11
+ - `GET /v1/products?limit=&cursor=` — public, cursor-paginated list
12
+ - `GET /v1/products/:id` — public, one product or `404`
13
+ - `POST /v1/products` — **scope-guarded** + **idempotent** create
14
+ - `DELETE /v1/products/:id` — **scope-guarded** delete
15
+ - **Query string + path params + JSON body** parsing → one decoded `input`.
16
+ - **Scope guards** — `requireScope('products:write')` returns `403` unless the
17
+ caller's API key carries the scope.
18
+ - **`Idempotency-Key`** — a retried `POST` replays the first response instead
19
+ of inserting twice (`app.config.ts` `idempotency: true`).
20
+ - **`@voltro/plugin-openapi`** — `GET /openapi.json` (the spec) + `GET /docs`
21
+ (Swagger UI), generated from the route descriptors. No hand-written docs.
22
+
23
+ REST routes are the ONE primitive that is **not auto-discovered** — they're
24
+ registered explicitly via `restRoutes` in `app.config.ts`.
25
+
26
+ ## Run it
27
+
28
+ ```bash
29
+ voltro dev .
30
+ ```
31
+
32
+ Boots on `http://localhost:4000`. Then:
33
+
34
+ ```bash
35
+ # The spec + interactive docs
36
+ open http://localhost:4000/docs
37
+ curl -s http://localhost:4000/openapi.json | jq '.info, (.paths | keys)'
38
+
39
+ # Public reads — no auth
40
+ curl -s http://localhost:4000/v1/products # → { "data": [], "nextCursor": null }
41
+ curl -s http://localhost:4000/v1/products/prod_xyz # → 404
42
+
43
+ # Guarded create — the demo API key carries products:write
44
+ curl -s -X POST http://localhost:4000/v1/products \
45
+ -H 'Authorization: Bearer restdemo_devkey' \
46
+ -H 'Content-Type: application/json' \
47
+ -d '{"name":"Widget","priceCents":1999}' # → 201 the created product
48
+
49
+ # Same call WITHOUT the key → 403 (the guard rejects)
50
+ curl -s -o /dev/null -w '%{http_code}\n' -X POST http://localhost:4000/v1/products \
51
+ -H 'Content-Type: application/json' -d '{"name":"x","priceCents":1}' # → 403
52
+
53
+ # Idempotency — repeat with the same key header → the SAME response replayed
54
+ curl -s -X POST http://localhost:4000/v1/products \
55
+ -H 'Authorization: Bearer restdemo_devkey' \
56
+ -H 'Idempotency-Key: order-42' \
57
+ -H 'Content-Type: application/json' \
58
+ -d '{"name":"Once","priceCents":500}'
59
+ ```
60
+
61
+ ## Auth — the demo key is DEV-ONLY
62
+
63
+ `app.config.ts` ships ONE hardcoded API key (`restdemo_devkey` → scopes
64
+ `products:read` + `products:write`) so the guarded routes work on first boot.
65
+ The `apiKeyStrategy` SHA-256-hashes the bearer token and matches the hash to a
66
+ fixed Subject.
67
+
68
+ **Before you deploy**, delete the demo constant and do one of:
69
+
70
+ - look the token hash up against your own key store (store key **hashes**,
71
+ never raw tokens), or
72
+ - flip on the framework's first-class API keys — set `apiKeys: true` in
73
+ `app.config.ts` for admin-gated issue/list/revoke against `_voltro_api_keys`,
74
+ with hash-only storage built in.
75
+
76
+ ## Make it real
77
+
78
+ - `store: 'memory'` → `store: 'postgres'` (data survives restarts).
79
+ - Replace `products` in `database/schema.ts` with your domain; add `tenant()`
80
+ if the API is multi-tenant (then scope each handler by the key's `tenantId`).
81
+ - Add routes — drop another `*.route.tsx`, `export default defineRestRoute(…)`,
82
+ and add it to the `routes` array in `app.config.ts` (it joins both the mount
83
+ and the OpenAPI spec automatically).
84
+ - Version/deprecate — a descriptor's `deprecated` sets a `Deprecation: true`
85
+ header; `sunset` sets `Sunset:` and `410`s past the date.