@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,624 @@
1
+ # Caching
2
+
3
+ > Voltro's caching layer (@voltro/cache) — an always-on memory default, swappable Redis-compatible backends, a low-level wrap primitive, and automatic query-result invalidation.
4
+
5
+
6
+
7
+ ---
8
+
9
+ <!-- source: en/caching/overview.md -->
10
+ ## Overview
11
+
12
+ _Voltro's caching layer (@voltro/cache) — an always-on memory default, swappable Redis-compatible backends, a low-level wrap primitive, and automatic query-result invalidation._
13
+
14
+ Voltro ships a first-class cache in **`@voltro/cache`**. It is **always on** — the in-process `memory` backend is the zero-config default, so `ctx.cache` works in every handler from the first boot with nothing to install. You opt into a *distributed* backend (Redis & friends) only when you need cross-instance sharing.
15
+
16
+ There are two ways to use it, and they share one backend:
17
+
18
+ 1. **Low-level** — `ctx.cache.wrap(key, { ttl, tags }, () => compute())` in any handler. Cache-aside with tags, stale-while-revalidate, and single-flight de-duplication. Use it for expensive derived work: aggregations, external-API enrichment, rendered fragments.
19
+ 2. **Automatic query caching** — add a `cache` field to a `defineQuery` and the framework caches the server snapshot and **invalidates it automatically** when a mutation writes any table the query depends on. Zero manual busting.
20
+
21
+ The defining idea mirrors the rest of Voltro: **the backend is a configuration choice, the code is identical.** The same `wrap` call and the same `cache:` field run against the in-process map in dev and against Redis (or Valkey / KeyDB / Dragonfly / Upstash) in production — you flip `CACHE_BACKEND`, not your handlers.
22
+
23
+ ```ts
24
+ // app.config.ts — memory (default) or redis
25
+ export default { type: 'api', name: 'myApi', store: 'postgres', cache: 'redis' }
26
+ ```
27
+
28
+ ```ts
29
+ // any handler — works on every backend
30
+ export default async (input: { orgId: string }, ctx) =>
31
+ ctx.cache.wrap(
32
+ `dashboard:${input.orgId}`,
33
+ { ttlMs: 60_000, tags: [`org:${input.orgId}`] },
34
+ async () => expensiveDashboard(ctx, input.orgId),
35
+ )
36
+ ```
37
+
38
+ ### What it is not
39
+
40
+ - **Not the reactive query engine.** Live `useSubscription` queries already stay fresh by pushing deltas — caching is for *non-live* derived work and for sharing a query's initial snapshot across many subscribers/instances.
41
+ - **Not a runtime plugin.** A cache backend is infrastructure, so it's an `app.config` + baseline concern (`voltro add redis`), not a `plugins:` entry.
42
+
43
+ ### Where to go next
44
+
45
+ - **[Backends & engines](/docs/caching/backends)** — memory vs Redis, the five RESP-compatible engines, connection presets, env vars.
46
+ - **[Low-level `wrap`](/docs/caching/wrap)** — `ctx.cache` / the Effect `Cache` service, tags, SWR, single-flight.
47
+ - **[Query caching](/docs/caching/query-cache)** — the `cache:` field, the `scope` security rule, auto-invalidation.
48
+ - **[Durable key-value](/docs/caching/key-value)** — `ctx.kv` (durable, never evicted — for state you can't recompute): the full API, TTL, tenant namespacing.
49
+ - **[Key-value backends](/docs/caching/kv-backends)** — `database` (durable) vs `redis` vs `memory`, the `KV_BACKEND` selector, the `KvStore` port, custom backends.
50
+ - **[Named connections](/docs/caching/connections)** — the shared registry behind the cache / KV / rate limiter / broadcast, the `<NAME>_REDIS_URL` → `REDIS_URL` scheme, and per-concern enablement.
51
+ - **[Enabling Redis](/docs/caching/enabling)** — `voltro add redis`, `create-project --cache=redis`, the `voltro cache` CLI, the dashboard panel.
52
+
53
+
54
+
55
+ ---
56
+
57
+ <!-- source: en/caching/backends.md -->
58
+ ## Backends & engines
59
+
60
+ _The two cache backends (memory + RESP) and the five Redis-compatible engines they cover — Redis, Valkey, KeyDB, Dragonfly, Upstash — plus every env var and connection preset._
61
+
62
+ The backend is chosen by environment variable (or `app.config.ts`) and is shared by both caching modes. Everything else — `wrap`, the `cache:` query field, tag invalidation — is backend-agnostic.
63
+
64
+ ## Selecting a backend
65
+
66
+ ```
67
+ CACHE_BACKEND memory | redis (default: memory)
68
+ CACHE_REDIS_URL redis/rediss/REST url (falls back to REDIS_URL)
69
+ CACHE_REDIS_DRIVER resp | http (default: resp; http = Upstash REST, for edge)
70
+ CACHE_REDIS_TOKEN http-driver auth (falls back to UPSTASH_REDIS_REST_TOKEN)
71
+ CACHE_KEY_PREFIX namespace (redis backend only; default: voltro:cache)
72
+ CACHE_MAX_ENTRIES memory capacity cap (optional, memory backend only)
73
+ ```
74
+
75
+ `CACHE_KEY_PREFIX` is consumed only by the **redis** backend (it namespaces the RESP keyspace). Under `CACHE_BACKEND=memory` it parses but is a no-op — the memory map isn't a shared keyspace, so there is nothing to prefix.
76
+
77
+ ```ts
78
+ // app.config.ts — env always wins over this
79
+ export default { type: 'api', name: 'myApi', store: 'postgres', cache: 'redis' }
80
+ ```
81
+
82
+ The resolved backend is printed at boot — `voltro logs --tail 50 | grep 'cache backend'`:
83
+
84
+ ```
85
+ [voltro:dev] cache backend resolved: redis (driver resp)
86
+ ```
87
+
88
+ ## memory (default)
89
+
90
+ In-process Map with lazy expiry. **Single instance only** — entries live in the process, invisible to other nodes. Zero-config, perfect for dev and single-pod deployments. `CACHE_MAX_ENTRIES` bounds it for write-heavy workloads. Resets on restart.
91
+
92
+ Capacity eviction is **oldest-by-insertion (FIFO)** — reads do not bump recency, so under cap pressure a hot frequently-read entry is dropped before a cold never-read one. This is NOT LRU: it's a deliberate simplicity trade for a single-process dev/cache tier (the cross-instance tier is redis). Size the cap above your hot-set, or rely on per-entry TTL, if that matters.
93
+
94
+ Use it when: you're in dev, or you run exactly one app instance and don't need the cache to survive a restart.
95
+
96
+ ## redis — one backend, five engines
97
+
98
+ The `redis` backend speaks the **RESP wire protocol**, so a single implementation drives five engines with **no per-engine code**:
99
+
100
+ | Engine | Driver | Notes |
101
+ |---|---|---|
102
+ | **Redis** | `resp` (TCP) | The reference implementation. |
103
+ | **Valkey** | `resp` (TCP) | The Linux-Foundation fork of Redis — drop-in. |
104
+ | **KeyDB** | `resp` (TCP) | Multithreaded Redis fork — drop-in. |
105
+ | **Dragonfly** | `resp` (TCP) | Modern high-throughput drop-in. |
106
+ | **Upstash** | `resp` (TCP) **or** `http` (REST) | Use `http` for serverless/edge runtimes where a persistent TCP socket isn't viable. |
107
+
108
+ Connection presets:
109
+
110
+ ```sh
111
+ # Redis / Valkey / KeyDB / Dragonfly (all identical — just point at the server)
112
+ CACHE_BACKEND=redis CACHE_REDIS_URL=redis://localhost:6379
113
+
114
+ # TLS
115
+ CACHE_REDIS_URL=rediss://default:password@host:6379
116
+
117
+ # Upstash over the edge HTTP/REST API
118
+ CACHE_BACKEND=redis \
119
+ CACHE_REDIS_DRIVER=http \
120
+ CACHE_REDIS_URL=https://your-db.upstash.io \
121
+ CACHE_REDIS_TOKEN=********
122
+ ```
123
+
124
+ Use it when: you run more than one app instance (k8s replicas, multi-PM2, ECS tasks) and need a shared cache, or you want the cache to survive restarts.
125
+
126
+ ### Storage layout
127
+
128
+ Under `CACHE_KEY_PREFIX` (default `voltro:cache`):
129
+
130
+ - value keys → `voltro:cache:v:<key>` (TTL via `PSETEX`; JSON-encoded)
131
+ - tag sets → `voltro:cache:t:<tag>` (a Redis SET of the raw keys carrying that tag)
132
+
133
+ `clear()` is scoped to the prefix via `SCAN` — it never runs `FLUSHDB`, so a Redis shared with other framework state (e.g. the read-replica RYW store) is safe.
134
+
135
+ > **Reference semantics differ by backend.** The memory backend returns the *same object reference* you stored (fast, but mutating a cached object mutates the cache — don't). The Redis backends JSON round-trip, so they always return a fresh copy. Treat cached values as immutable on both.
136
+
137
+ ## Choosing
138
+
139
+ - **One instance / dev** → `memory`. Nothing to run.
140
+ - **Multiple instances, or restart-survival** → `redis` (pick any of the five engines by URL).
141
+ - **Serverless / edge** (no persistent TCP) → `redis` with `CACHE_REDIS_DRIVER=http` (Upstash).
142
+
143
+ To provision the infra (a Redis container in compose / a Deployment in helm) plus the env, see **[Enabling Redis](/docs/caching/enabling)** — `voltro add redis` wires it all.
144
+
145
+
146
+
147
+ ---
148
+
149
+ <!-- source: en/caching/wrap.md -->
150
+ ## Low-level cache (wrap)
151
+
152
+ _The ctx.cache facade and the Effect Cache service — wrap (cache-aside), tags, stale-while-revalidate, and per-key single-flight de-duplication._
153
+
154
+ The low-level cache is available in every handler two ways:
155
+
156
+ - **async handlers** → `ctx.cache` (the facade)
157
+ - **Effect handlers** → `yield* Cache` from `@voltro/cache`
158
+
159
+ Both wrap the same backend; pick whichever matches the handler you're writing.
160
+
161
+ ## `wrap` — the cache-aside combinator
162
+
163
+ `wrap(key, options, compute)` returns the cached value if present, otherwise runs `compute`, stores the result under `key` with the given TTL + tags, and returns it.
164
+
165
+ ```ts
166
+ // async handler
167
+ export default async (input: { orgId: string }, ctx) =>
168
+ ctx.cache.wrap(
169
+ `dashboard:${input.orgId}`,
170
+ { ttlMs: 60_000, tags: [`org:${input.orgId}`, 'metrics'] },
171
+ async () => expensiveAggregation(ctx, input.orgId), // runs only on a miss
172
+ )
173
+ ```
174
+
175
+ ```ts
176
+ // Effect handler
177
+ import { Cache } from '@voltro/cache'
178
+ import { Effect } from 'effect'
179
+
180
+ export default (input: { orgId: string }) =>
181
+ Effect.gen(function* () {
182
+ const cache = yield* Cache
183
+ return yield* cache.wrap(
184
+ `dashboard:${input.orgId}`,
185
+ { ttlMs: 60_000, tags: [`org:${input.orgId}`] },
186
+ buildDashboard(input.orgId), // an Effect; runs only on a miss
187
+ )
188
+ })
189
+ ```
190
+
191
+ `options`:
192
+
193
+ - **`ttlMs`** — fresh window in milliseconds. Omit for no expiry (lives until invalidated or evicted).
194
+ - **`tags`** — labels for invalidation (typically the tables the compute reads). `invalidateTag(tag)` drops every entry carrying that tag.
195
+ - **`swrMs`** — stale-while-revalidate window past `ttlMs`. Within it, the stale value is served *immediately* and a refresh runs in the background, so callers never wait on the slow recompute.
196
+
197
+ ## Tags + invalidation
198
+
199
+ Tags are the headline feature. Instead of tracking every derived key, invalidate by concept:
200
+
201
+ ```ts
202
+ // write path — a raw-SQL admin action the framework can't observe
203
+ await ctx.cache.invalidateTag('metrics') // drops every entry tagged 'metrics'
204
+ ```
205
+
206
+ You rarely call `invalidateTag` by hand: an ordinary **mutation that writes a table auto-drops every cache entry tagged with that table name** (the same bus that powers [query caching](/docs/caching/query-cache)). Reach for the manual call only for writes the framework doesn't see — raw SQL via `sql.unsafe`, external systems, etc. Tag your `wrap` entries with the table names they derive from and invalidation is automatic.
207
+
208
+ ## Single-flight (thundering-herd protection)
209
+
210
+ When N concurrent callers hit the same cold key, `wrap` runs `compute` **once** and shares the one result with all of them (per-process). This matters most exactly when the cache is empty — without it, a cache miss under load fans out into N identical expensive computes against your database.
211
+
212
+ ## Other operations
213
+
214
+ ```ts
215
+ await ctx.cache.get<T>(key) // T | undefined
216
+ await ctx.cache.set(key, value, { ttlMs }) // no tags
217
+ await ctx.cache.setWithTags(key, value, { ttlMs, tags })
218
+ await ctx.cache.has(key) // boolean
219
+ await ctx.cache.remove(key) // boolean (existed?)
220
+ await ctx.cache.invalidateTag(tag) // number dropped
221
+ await ctx.cache.clear() // whole namespace
222
+ ```
223
+
224
+ In Effect handlers the same methods live on the `Cache` service and return `Effect`s. One return-type difference: the Effect service's `get` yields `Effect<Option<A>, CacheError>` — an `Option`, not a bare value — so reach for `Option.getOrUndefined(...)` (or pattern-match) where the `ctx.cache` facade hands you `A | undefined` directly:
225
+
226
+ ```ts
227
+ import { Cache } from '@voltro/cache'
228
+ import { Effect, Option } from 'effect'
229
+
230
+ const cache = yield* Cache
231
+ const hit = yield* cache.get<Dashboard>(key) // Option<Dashboard>
232
+ const value = Option.getOrUndefined(hit) // Dashboard | undefined
233
+ ```
234
+
235
+ Cache failures surface as a typed `CacheError`; `Effect.catchTag('CacheError', () => compute)` makes the cache best-effort (degrade to always-recompute) when a backend is briefly down.
236
+
237
+ ## Partitions — one namespace per concern
238
+
239
+ `cache.partition(name, defaults?)` returns a key-prefixed VIEW over the cache — one logical namespace per concern, each with its own default ttl/swr:
240
+
241
+ ```ts
242
+ import { Cache } from '@voltro/cache'
243
+ import { Effect } from 'effect'
244
+
245
+ const program = Effect.gen(function* () {
246
+ const cache = yield* Cache
247
+ const sessions = cache.partition('sessions', { ttlMs: 60_000 })
248
+ const reports = cache.partition('reports', { ttlMs: 3_600_000, swrMs: 86_400_000 })
249
+
250
+ yield* sessions.set('u1', { token: 'x' }) // stored under `sessions:u1`
251
+ const r = yield* reports.wrap('q1', {}, Effect.succeed({ rows: 1 })) // inherits the partition's ttl+swr
252
+ return r
253
+ })
254
+ ```
255
+
256
+ Keys become `<name>:<key>` so partitions can't collide; per-call options override the partition defaults. **Tags are NOT prefixed** — they stay global on purpose, so a table-change invalidation through the bus still drops matching entries in every partition. A partition shares the parent cache's backend; to put a concern on a **different backend or server**, configure it as its own concern (the durable [`Kv`](/docs/caching/kv-backends), or a separately-provided cache) via [named connections](/docs/caching/connections). Methods on a partition: `get` / `set` / `has` / `remove` / `wrap` / `invalidateTag`.
257
+
258
+ ## Don't
259
+
260
+ - **Don't mutate a value you got from the cache.** On the memory backend it's the live reference. Clone if you must edit.
261
+ - **Don't cache per-subject data under a global key.** If the value differs per user/tenant, put the subject id in the key (`summary:${tenantId}`). The automatic [query cache](/docs/caching/query-cache) handles this for you via `scope`; with raw `wrap` it's your responsibility.
262
+
263
+
264
+
265
+ ---
266
+
267
+ <!-- source: en/caching/query-cache.md -->
268
+ ## Query caching
269
+
270
+ _Opt a query into server-side snapshot caching with the cache field — automatic table-tag invalidation and the required scope security rule (subject vs global)._
271
+
272
+ Add a `cache` field to a `defineQuery` and the framework caches the query's **server snapshot** and **auto-invalidates it when a mutation writes any table the query depends on**. No manual busting, no glue.
273
+
274
+ ```ts
275
+ // queries/todos.listByTenant.query.ts
276
+ export const listTodos = defineQuery({
277
+ name: 'todos.listByTenant',
278
+ source: 'todos',
279
+ input: Schema.Struct({ done: Schema.optional(Schema.Boolean) }),
280
+ output: Todo,
281
+ cache: { ttl: '30s', swr: '5m', scope: 'subject' }, // tenant-filtered → subject
282
+ })
283
+ ```
284
+
285
+ The matching `.<primitive>.server.ts` is **unchanged** — caching is a descriptor concern. `ttl` / `swr` accept seconds (`30`) or a duration string (`'30s'`, `'5m'`, `'1h'`).
286
+
287
+ ## `scope` is required — and it's a security decision
288
+
289
+ `scope` has **no default**, because guessing wrong leaks one user's rows to another. The rubric is one question:
290
+
291
+ > **Does the resolved query depend on the caller?**
292
+
293
+ - **Yes** — tenant-filtered, `where authorId = me`, anything row-scoped → **`scope: 'subject'`**. The cache key includes the caller's subject id, so two subjects can never share an entry.
294
+ - **No** — the same rows for everyone (reference / lookup data) → **`scope: 'global'`**. One entry shared across all callers.
295
+
296
+ ```ts
297
+ // reference data — identical for everyone → global
298
+ export const listCountries = defineQuery({
299
+ name: 'reference.countries',
300
+ source: 'countries',
301
+ input: Schema.Void,
302
+ output: Country,
303
+ cache: { ttl: '1h', scope: 'global' },
304
+ })
305
+ ```
306
+
307
+ **Never put `scope: 'global'` on a subject-filtered query.** Tenant tables are auto-scoped per subject by the runtime, so a `global` cache over one would serve tenant A's rows to tenant B. When in doubt, use `subject`.
308
+
309
+ ## How auto-invalidation works
310
+
311
+ When you opt in, the framework tags the cached snapshot with the **full set of tables the query reads** — the root `source` plus every table reached through eager `.with(...)` relations and joins. A mutation that writes any of those tables drops the entry through the same invalidation bus the low-level `wrap` uses. The writer's own next read recomputes (read-your-writes holds).
312
+
313
+ - **`CDC=1`** (postgres LISTEN/NOTIFY) → invalidation propagates across instances.
314
+ - **`CDC=0`** → single-process invalidation only. Fine for dev; for multi-instance `global` caches you want CDC on.
315
+
316
+ ## When to use it vs. a live subscription
317
+
318
+ Live `useSubscription` queries already stay fresh by pushing deltas — they don't need this. Query caching earns its keep for the **initial snapshot shared across many subscribers/instances** (cutting redundant DB hits when N tabs/pods open the same query) and for adding an SWR window. If a query is opened once and rarely, the live engine alone is enough; reach for `cache:` on hot, widely-shared read paths.
319
+
320
+ Inspect hit-rate live in the dashboard's **Cache** panel, or via `voltro cache status` — see [Enabling Redis](/docs/caching/enabling).
321
+
322
+
323
+
324
+ ---
325
+
326
+ <!-- source: en/caching/enabling.md -->
327
+ ## Enabling Redis & tooling
328
+
329
+ _Switch from the memory default to Redis — at project creation (--cache=redis) or later (voltro add redis) — plus the voltro cache CLI and the dashboard panel._
330
+
331
+ The `memory` backend is the always-on default — there is nothing to install to start caching. Switching to Redis is two concerns: flip the backend (`app.config` / env) **and** provision the infra to run it (a container in compose, a Deployment in helm, the env vars). The CLI does both for you.
332
+
333
+ ## At project creation
334
+
335
+ ```sh
336
+ voltro create-project myApp --baseline=compose --cache=redis
337
+ ```
338
+
339
+ `--cache=redis` sets `cache: 'redis'` in the api `app.config.ts` and, because a baseline was chosen, injects a `redis` service into the compose `docker-compose.yml` (or a Deployment + Service + values into the helm chart) plus `CACHE_BACKEND` / `CACHE_REDIS_URL` into `.env.example`.
340
+
341
+ ## Later, on an existing project
342
+
343
+ ```sh
344
+ voltro add redis
345
+ ```
346
+
347
+ The same idempotent wiring, applied to the project at the cwd:
348
+
349
+ 1. flips every api `app.config.ts` to `cache: 'redis'`
350
+ 2. injects a `redis` service into `docker-compose.yml` (if the compose baseline is present)
351
+ 3. injects a redis `values` block + a Deployment/Service into the helm chart (if the helm baseline is present)
352
+ 4. activates `CACHE_BACKEND` / `CACHE_REDIS_URL` in `.env.example` — uncommenting the baseline's optional-backends block in place, or appending it if absent (and `.env` if it exists)
353
+
354
+ Every baseline ships that block **commented-out** in `.env.example` from the start — the cache *and* the durable-KV Redis vars — so a fresh project already shows what it can turn on. `voltro add redis` just uncomments the cache half; the durable-KV vars stay commented on purpose (durable KV wants a persistent Redis, not the ephemeral cache one this command provisions — see [Key-value backends](/docs/caching/kv-backends)).
355
+
356
+ It only touches infra files that exist — a `bare`-baseline project just gets the `app.config` + `.env` changes. Re-running is safe (each step checks for an existing marker and skips). Caching is a backend + infrastructure concern, **not** a runtime plugin, so there is deliberately no `plugins:` entry and no `voltro plugin add` for it.
357
+
358
+ Then start Redis and reboot:
359
+
360
+ ```sh
361
+ docker compose up -d redis
362
+ voltro dev .
363
+ # boot log: cache backend resolved: redis (driver resp)
364
+ ```
365
+
366
+ For the engine matrix (Redis / Valkey / KeyDB / Dragonfly / Upstash) and all env vars, see **[Backends & engines](/docs/caching/backends)**.
367
+
368
+ ## The `voltro cache` CLI
369
+
370
+ ```sh
371
+ voltro cache status # resolved backend + hit/miss counters
372
+ voltro cache flush # clear every entry (redis backend)
373
+ voltro cache invalidate <tag> # drop everything carrying <tag>
374
+ ```
375
+
376
+ `flush` / `invalidate` operate on the shared store, so against `redis` they affect every instance. Against the `memory` backend they're refused (the CLI can't reach another process's in-memory map) — `status` still reports the config.
377
+
378
+ ## Dashboard
379
+
380
+ The local DevTools dashboard and the Voltro Cloud dashboard both show a per-app **Cache** panel — resolved backend/engine, driver, and live hit-rate (hits / misses) — backed by `GET /_voltro/inspect/data-cache`. Same surface in both, so what you see locally matches production.
381
+
382
+
383
+
384
+ ---
385
+
386
+ <!-- source: en/caching/key-value.md -->
387
+ ## Durable key-value (ctx.kv)
388
+
389
+ _ctx.kv — the durable key-value primitive from @voltro/kv, distinct from the cache (never evicted). Full API, TTL, tenant namespacing, and the Effect-native Kv service._
390
+
391
+ The cache is for data you can **recompute**. Some state you can't — onboarding progress, a webhook cursor, a sync watermark, a per-user feature toggle. Losing a cached dashboard is free; losing that is data loss. For it Voltro ships a second primitive in **`@voltro/kv`**, reached as **`ctx.kv`** (async handlers) or **`Kv`** (Effect handlers).
392
+
393
+ ## The contract — durable, never evicted
394
+
395
+ `ctx.kv` is a key-value store with the **opposite contract to the cache**: entries are **never evicted for capacity**. They live until you delete them or their (optional) TTL lapses. It is **always present**, and on a SQL app its default backend is the **database** — so values survive restarts and are shared across every replica.
396
+
397
+ | Aspect | `ctx.cache` | `ctx.kv` | `ctx.store` |
398
+ |---|---|---|---|
399
+ | For | recomputable data | state you can't recompute | typed relational rows |
400
+ | A miss means | recompute it | never written / TTL lapsed | row absent |
401
+ | Eviction | yes (capacity, LRU-ish) | **never** | n/a |
402
+ | Default backend | memory | database (durable) | your SQL store |
403
+ | Shape | opaque JSON + tags | opaque JSON | typed tables, queries, joins |
404
+
405
+ ## Usage
406
+
407
+ ```ts
408
+ // any async handler
409
+ export default async (input: { userId: string }, ctx) => {
410
+ const state = await ctx.kv.getOrElse(`onboarding:${input.userId}`, () => ({ step: 0 }))
411
+ await ctx.kv.set(`onboarding:${input.userId}`, { step: state.step + 1 })
412
+ return state
413
+ }
414
+ ```
415
+
416
+ In an Effect handler, reach the service directly:
417
+
418
+ ```ts
419
+ import { Kv } from '@voltro/kv'
420
+ import { Effect } from 'effect'
421
+
422
+ export default (input: { userId: string }) =>
423
+ Effect.gen(function* () {
424
+ const kv = yield* Kv
425
+ const seen = yield* kv.getOrElse(`seen:${input.userId}`, () => 0)
426
+ yield* kv.set(`seen:${input.userId}`, seen + 1)
427
+ return seen
428
+ })
429
+ ```
430
+
431
+ ### The full surface
432
+
433
+ | Method | Returns | Notes |
434
+ |---|---|---|
435
+ | `get<A>(key)` | `A \| undefined` (async) · `Option<A>` (Effect) | miss/expiry → absent |
436
+ | `getOrElse(key, orElse)` | `A` | `orElse()` runs only on a miss |
437
+ | `set(key, value, { ttlMs? })` | `void` | overwrites; omit `ttlMs` for no expiry |
438
+ | `delete(key)` | `boolean` | `true` if it existed (and hadn't expired) |
439
+ | `has(key)` | `boolean` | honours expiry, no deserialize |
440
+ | `list(prefix)` | `string[]` | keys under a prefix (`''` = all in this namespace) |
441
+ | `clear()` | `void` | drops this app's KV namespace only |
442
+
443
+ Values are opaque JSON — a genuine string round-trips **exactly** (it is never re-parsed to a number, the way a dialect-dependent `json` read might be).
444
+
445
+ ### TTL
446
+
447
+ `set(key, value, { ttlMs })` gives an entry an expiry; without it, the entry is permanent. Expiry is **lazy** — an expired entry reads as a miss and is dropped on the next `get`/`has` (no background sweeper). This is durability *with* an optional lifetime, not the cache's capacity-driven eviction.
448
+
449
+ ```ts
450
+ await ctx.kv.set(`otp:${email}`, code, { ttlMs: 10 * 60_000 }) // 10-minute one-time code
451
+ ```
452
+
453
+ ## Tenant namespacing
454
+
455
+ `ctx.kv` keys are **app-global** — namespace by tenant yourself, exactly as you do with `ctx.cache`:
456
+
457
+ ```ts
458
+ const key = `${ctx.request.subject.tenantId ?? 'anon'}:onboarding:${userId}`
459
+ await ctx.kv.set(key, state)
460
+ ```
461
+
462
+ The facade never prefixes for you, so two tenants sharing a bare key would collide — always fold the tenant (or actor) into the key for per-tenant state.
463
+
464
+ ## When to use which
465
+
466
+ - **`ctx.kv`** — durable scratch state you can't rebuild: onboarding/wizard progress, external-sync cursors and watermarks, idempotency-ish markers, per-user flags, short-lived tokens (with TTL).
467
+ - **`ctx.cache`** — anything you can recompute; a miss just recomputes. See [low-level `wrap`](/docs/caching/wrap) and [query caching](/docs/caching/query-cache).
468
+ - **`ctx.store`** — when the data is relational, queried, joined, or reported on — model it as a table.
469
+
470
+ ## Where to go next
471
+
472
+ - **[Key-value backends](/docs/caching/kv-backends)** — `database` (durable) vs `redis` vs `memory`, the `KV_BACKEND` selector, the `KvStore` port, custom backends.
473
+ - **[Named connections](/docs/caching/connections)** — how the cache, KV, rate limiter, and broadcast share one connection registry, and how to point each concern at its own server.
474
+
475
+
476
+
477
+ ---
478
+
479
+ <!-- source: en/caching/kv-backends.md -->
480
+ ## Key-value backends
481
+
482
+ _The KvStore port and its backends — database (durable, _voltro_kv), redis (shared), and memory — plus the KV_BACKEND selector, the @voltro/kv package, and custom backends._
483
+
484
+ `ctx.kv` runs on a `KvStore` — a small Effect-native port with three built-in backends. Which one you get is a **configuration choice; the handler code is identical**, exactly like the cache.
485
+
486
+ ## Selecting the backend
487
+
488
+ `KV_BACKEND` picks it (the `kv:` field in `app.config.ts` seeds the env when unset):
489
+
490
+ ```ts
491
+ // app.config.ts
492
+ export default { type: 'api', name: 'myApi', store: 'postgres', kv: 'database' }
493
+ ```
494
+
495
+ | Backend | Durable? | What it is | Use |
496
+ |---|---|---|---|
497
+ | `database` (default, sql apps) | **yes** — persists in `_voltro_kv` | rows in your own SQL store | the default; survives restarts, shared across replicas |
498
+ | `redis` | no | keys on a RESP server | shared/cross-instance but deliberately ephemeral state |
499
+ | `memory` | no | an in-process map | dev / tests / single-process |
500
+
501
+ Unknown values fall back to `database`. On an app with no SQL store, use `memory` or `redis`.
502
+
503
+ ### `database` — the durable default
504
+
505
+ The database backend stores each entry as a row in **`_voltro_kv`**, a framework-managed table created for every SQL app (empty until `ctx.kv` is used). The value is a JSON string in a `text` column — exact round-trip for any value — with an optional `expiresAt` (lazy eviction on read) and a `UNIQUE(key)` that makes `set` an atomic upsert. Because it lives in your store, it survives restarts and every replica sees the same data.
506
+
507
+ ### `redis` — shared, non-durable
508
+
509
+ The redis backend keys entries under a prefix (default `voltro:kv`) on any RESP server (Redis / Valkey / KeyDB / Dragonfly / Upstash). It draws its client from the shared [connection registry](/docs/caching/connections) — one socket per named connection, reused across the cache, KV, and rate limiter. Redis is a cache, not a durable store, so pick it only for state you can afford to lose on a flush.
510
+
511
+ ### `memory` — dev / tests
512
+
513
+ An in-process map, eviction-free (entries live until deleted or TTL-expired). Lost on restart and invisible to other replicas — for a single process, tests, or throwaway state. Under the test harness (`makeTestContext`) `ctx.kv` is a memory KV whose TTLs honour `ctx.clock`.
514
+
515
+ ## The `@voltro/kv` package
516
+
517
+ The primitive ships in **`@voltro/kv`** (a leaf package — `effect` is the only runtime dependency; the redis drivers are lazily-imported optional deps). Wire the layers by hand only when embedding the KV outside the framework path; inside a Voltro app you just use `ctx.kv` / `Kv`.
518
+
519
+ ```ts
520
+ import { Kv, KvStore, layerMemory, layerRedis } from '@voltro/kv'
521
+ import { Layer } from 'effect'
522
+
523
+ // memory-backed Kv service
524
+ const MemoryKv = Kv.Default.pipe(Layer.provide(layerMemory()))
525
+ ```
526
+
527
+ Key exports: the `Kv` service, the `KvStore` port (+ `KvStoreShape`, `KvSetOptions`), `KvError` (the single typed failure), and the backend layers `layerMemory` / `layerRedis` (plus `makeRedisKvStore` over an existing client). The database backend + the `ctx.kv` facade live in `@voltro/runtime` (they need `@voltro/database`).
528
+
529
+ ### The `KvStore` port
530
+
531
+ `KvStore` is a `Context.Tag` of the `KvStoreShape` interface (`get` / `set` / `delete` / `has` / `list` / `clear`, all Effect-returning). Both built-in backends are just a `Layer<KvStore>`; a custom backend plugs into the same slot:
532
+
533
+ ```ts
534
+ import { Kv, KvStore, type KvStoreShape } from '@voltro/kv'
535
+ import { Layer } from 'effect'
536
+
537
+ const myBackend: KvStoreShape = {
538
+ // get / set / delete / has / list / clear — each returns an Effect<…, KvError>
539
+ } as KvStoreShape
540
+
541
+ const MyKv = Kv.Default.pipe(Layer.provide(Layer.succeed(KvStore, KvStore.of(myBackend))))
542
+ ```
543
+
544
+ ## `KvError`
545
+
546
+ Every KV operation fails with one typed error — a `Schema.TaggedError` with `{ operation, key, cause }`, mirroring the cache's `CacheError`. Declare it in a handler's `error:` schema and the rpc encoder marshals it across the wire; `Effect.catchTag('KvError', …)` catches it on the Effect path.
547
+
548
+
549
+
550
+ ---
551
+
552
+ <!-- source: en/caching/connections.md -->
553
+ ## Named connections
554
+
555
+ _One connection registry shared by the cache, KV, rate limiter and broadcast — the <NAME>_REDIS_URL → REDIS_URL scheme, configuring each concern's server independently, and why enablement is explicit per concern._
556
+
557
+ Four subsystems can talk to a RESP server: the **query cache**, the **durable KV** (on its redis backend), the **rate limiter**, and the **broadcast bus**. Rather than each parsing its own env and opening its own socket, they share **one connection registry** — so you point each concern at the same server or a different one purely by configuration.
558
+
559
+ The shareable thing is the **connection**, not the contract: the cache uses tag sets, the limiter atomic Lua, broadcast pub/sub. The registry deals in connections; each consumer keeps its own semantics on top. (That's also why they stay separate concerns — forcing a rate limiter onto a plain get/set cache would break it.)
560
+
561
+ ## The `<NAME>_REDIS_URL` → `REDIS_URL` scheme
562
+
563
+ Every named connection resolves its **own** var first, then the **shared default**:
564
+
565
+ ```
566
+ REDIS_URL the shared default for every named connection
567
+ CACHE_REDIS_URL the cache's own server (overrides REDIS_URL for the cache)
568
+ KV_REDIS_URL the KV's own server
569
+ RATELIMIT_REDIS_URL the rate limiter's own server
570
+ BROADCAST_REDIS_URL the broadcast bus's own server
571
+ ```
572
+
573
+ So one server for everything is just `REDIS_URL`; splitting a concern onto its own server is one extra var. Each also has a matching `<NAME>_REDIS_DRIVER` (`resp` default, or `http` for Upstash REST) and `<NAME>_REDIS_TOKEN`, falling back to `REDIS_DRIVER` / `REDIS_TOKEN` / `UPSTASH_REDIS_REST_TOKEN`.
574
+
575
+ > **Why `_REDIS_` when Voltro supports Valkey / KeyDB / Dragonfly / Upstash too?** Because the URL *value* is `redis://…` for all of them — there is no `valkey://` or `resp://` scheme; they all speak the Redis wire protocol. `_REDIS_URL` names the protocol, not the vendor, and `REDIS_URL` is the de-facto env var every PaaS injects. The `<NAME>_REDIS_URL` var is the connection for a concern's **redis backend specifically** — the KV on `database` uses your SQL connection, and broadcast on NATS uses `BROADCAST_URL` / `NATS_URL`.
576
+
577
+ ## Enablement is explicit per concern — a shared `REDIS_URL` is NOT a master switch
578
+
579
+ Setting `REDIS_URL` does **not** silently turn every concern on. Each concern has its own on/off selector; the URL is only the connection detail, read once you've opted in:
580
+
581
+ | Concern | Enablement selector | Reads a redis url only when |
582
+ |---|---|---|
583
+ | Cache | `CACHE_BACKEND=memory\|redis` (default `memory`) | `= redis` |
584
+ | KV | `KV_BACKEND=database\|redis\|memory` (default `database`) | `= redis` |
585
+ | Rate limiter | `store: 'memory' \| 'postgres' \| 'redis'` (plugin option) | `redis` |
586
+ | Broadcast | opt-in via `BROADCAST_URL` / `BROADCAST_REDIS_URL` / `BROADCAST_PROVIDER` / the `connection` option | explicitly opted in |
587
+
588
+ So `REDIS_URL` set for the cache leaves the KV on `database` and broadcast on its in-process memory bus. You enable each deliberately. (Broadcast is the one that used to infer "on" from any url; it no longer does — see [broadcast](/docs/plugins/broadcast).)
589
+
590
+ ## The registry API
591
+
592
+ Inside an Effect program, `RedisConnections` hands out pooled clients by name:
593
+
594
+ ```ts
595
+ import { RedisConnections, layerConnections } from '@voltro/kv/connection'
596
+ import { Effect } from 'effect'
597
+
598
+ const program = Effect.gen(function* () {
599
+ const connections = yield* RedisConnections
600
+ const client = yield* connections.client('cache') // pooled RESP command client
601
+ // …get / set / eval / zrange / scan / del …
602
+ })
603
+
604
+ program.pipe(Effect.provide(layerConnections))
605
+ ```
606
+
607
+ - `client(name)` opens (once, pooled — one socket per name for the layer's lifetime) and returns the RESP command client. Concurrent first-callers collapse to a single connect; the socket is closed on shutdown.
608
+ - `config(name)` resolves just the `RedisConnectionConfig` (`url` / `driver` / `token`) **without** opening it — for consumers that build their own client, e.g. a dedicated pub/sub connection over raw ioredis (which the REST driver can't do anyway).
609
+
610
+ A one-off resolve without the service is `connectionConfig(name)`:
611
+
612
+ ```ts
613
+ import { connectionConfig } from '@voltro/kv/connection'
614
+
615
+ const cfg = connectionConfig('broadcast') // Effect<RedisConnectionConfig, ConfigError>
616
+ ```
617
+
618
+ ## Adding a new named connection
619
+
620
+ There is **no registry code to change** for a new connection — the scheme is convention-based. A new consumer just picks a name and calls `connections.client('<name>')`; it automatically reads `<NAME>_REDIS_URL` → `REDIS_URL`. You only write code when the concern needs its own *contract* (atomic Lua, pub/sub) rather than plain get/set — then resolve `config(name)` and build the client shape you need.
621
+
622
+ ## Package
623
+
624
+ The registry ships in **`@voltro/kv`**, importable narrowly as `@voltro/kv/connection`. Exports: `RedisConnections` (the service), `layerConnections` (the pooled registry layer), `connectionConfig` (env resolution), `connect` (build a client from a config), `commandRetry` (the shared bounded retry), and the `RespClient` / `RedisConnectionConfig` types. It consolidates the RESP client that used to be duplicated across `@voltro/cache` and `@voltro/plugin-ratelimit`.