@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,1200 @@
1
+ # database.columnTypes
2
+
3
+ > Postgres-native ENUM types via dbEnum() — cheap ADD VALUE migrations, full type-narrowing. Falls back to CHECK constraints on other dialects.
4
+
5
+
6
+
7
+ ---
8
+
9
+ <!-- source: en/database/enums.md -->
10
+ ## Enums (dbEnum)
11
+
12
+ _Postgres-native ENUM types via dbEnum() — cheap ADD VALUE migrations, full type-narrowing. Falls back to CHECK constraints on other dialects._
13
+
14
+ `dbEnum('name', [values])` declares a postgres-native ENUM type
15
+ that's reusable across columns and tables. The literal value union
16
+ narrows the row type at compile time; cheap `ALTER TYPE ... ADD
17
+ VALUE` migrations replace CHECK-rewrite pain.
18
+
19
+ ## Quick start
20
+
21
+ ```ts
22
+ import { dbEnum, id, table } from '@voltro/database'
23
+
24
+ export const orderStatus = dbEnum('order_status', ['pending', 'paid', 'shipped'] as const)
25
+
26
+ export const orders = table('orders', {
27
+ id: id(),
28
+ status: orderStatus.column().default('pending'),
29
+ })
30
+
31
+ // row.status is typed `'pending' | 'paid' | 'shipped'`, not plain string
32
+ ```
33
+
34
+ ## Why this exists
35
+
36
+ Voltro has two ways to enforce a closed value set on a text column:
37
+
38
+ 1. **`text().oneOf(['a', 'b'])`** — emits a CHECK constraint.
39
+ Cross-dialect, works on every backend, but ADD VALUE is a CHECK
40
+ rewrite (expensive on a 10M-row table).
41
+ 2. **`dbEnum('name', [...])`** — postgres-native ENUM type.
42
+ `ALTER TYPE ... ADD VALUE 'new_value'` is O(1).
43
+
44
+ Use `dbEnum` when:
45
+ - You're on postgres AND
46
+ - The value set is reasonably stable but might grow occasionally AND
47
+ - You want the value set self-documenting in the schema
48
+
49
+ Use `oneOf` when:
50
+ - You're on multiple dialects (the framework's cross-dialect fallback
51
+ for `dbEnum` is the same CHECK constraint, but `oneOf` keeps the
52
+ source code aligned with reality).
53
+ - The value set is highly stable (the cheap ADD VALUE doesn't help).
54
+
55
+ ## Reuse across tables
56
+
57
+ ```ts
58
+ export const status = dbEnum('item_status', ['draft', 'published', 'archived'] as const)
59
+
60
+ export const articles = table('articles', { id: id(), status: status.column() })
61
+ export const videos = table('videos', { id: id(), state: status.column() })
62
+ ```
63
+
64
+ The `CREATE TYPE` DDL emits exactly once — the framework dedups by
65
+ enum name across all schema tables. Defining the same name with
66
+ different value sets throws at DDL time.
67
+
68
+ ## Defaults
69
+
70
+ ```ts
71
+ status: orderStatus.column().default('pending')
72
+ ```
73
+
74
+ Default must be one of the declared values — TypeScript narrows the
75
+ `.default(...)` parameter to the literal union.
76
+
77
+ ## ADD VALUE migrations
78
+
79
+ ```ts
80
+ // Day 1
81
+ export const orderStatus = dbEnum('order_status', ['pending', 'paid', 'shipped'] as const)
82
+
83
+ // Day 30 — add a value
84
+ export const orderStatus = dbEnum('order_status', ['pending', 'paid', 'shipped', 'returned'] as const)
85
+ ```
86
+
87
+ `voltro db plan` detects the new value and emits an `add-enum-value`
88
+ op:
89
+
90
+ ```sql
91
+ ALTER TYPE "order_status" ADD VALUE 'returned';
92
+ ```
93
+
94
+ On postgres this is O(1) — no table rewrite. On
95
+ mysql/mariadb/mssql/sqlite/turso (CHECK fallback) the planner emits a
96
+ CHECK-recreate, which is a brief lock but doesn't rewrite the table.
97
+
98
+ ## Renaming values
99
+
100
+ A value rename looks identical to a drop+add in a naive value-set
101
+ diff — and a drop+add would orphan every row already storing the old
102
+ label. So a rename needs explicit INTENT, exactly like a column rename
103
+ needs `.renamedFrom('oldName')`. Declare it with `declareEnumRename`,
104
+ placed next to the `dbEnum(...)` whose value array now carries the new
105
+ label:
106
+
107
+ ```ts
108
+ import { dbEnum, declareEnumRename } from '@voltro/database'
109
+
110
+ // The enum now spells the NEW value.
111
+ export const orderStatus = dbEnum('order_status', ['awaiting', 'paid', 'shipped'] as const)
112
+
113
+ // Record that 'pending' was renamed to 'awaiting'.
114
+ declareEnumRename('order_status', { from: 'pending', to: 'awaiting' })
115
+ ```
116
+
117
+ The migrator then emits a guarded, data-preserving rename — it fires
118
+ only when the OLD label still exists and the NEW one doesn't, so
119
+ re-runs are no-ops:
120
+
121
+ ```sql
122
+ DO $$
123
+ DECLARE type_oid oid;
124
+ BEGIN
125
+ SELECT oid INTO type_oid FROM pg_type WHERE typname = 'order_status';
126
+ IF type_oid IS NOT NULL
127
+ AND EXISTS (SELECT 1 FROM pg_enum WHERE enumtypid = type_oid AND enumlabel = 'pending')
128
+ AND NOT EXISTS (SELECT 1 FROM pg_enum WHERE enumtypid = type_oid AND enumlabel = 'awaiting') THEN
129
+ IF current_setting('server_version_num')::int >= 140000 THEN
130
+ EXECUTE format('ALTER TYPE %I RENAME VALUE %L TO %L', 'order_status', 'pending', 'awaiting');
131
+ ELSE
132
+ UPDATE pg_enum SET enumlabel = 'awaiting' WHERE enumtypid = type_oid AND enumlabel = 'pending';
133
+ END IF;
134
+ END IF;
135
+ END $$;
136
+ ```
137
+
138
+ - **Postgres 14+** uses the native `ALTER TYPE … RENAME VALUE 'old' TO
139
+ 'new'` — cheap, in-place, no table rewrite.
140
+ - **Postgres 13 and below** falls back to a data-preserving
141
+ `pg_enum.enumlabel` catalog update — every row keeps its value, the
142
+ label is simply re-spelled.
143
+ - **Non-postgres dialects** store enums as text + a CHECK constraint,
144
+ so the new value list lands in the recreated CHECK on its own — no
145
+ dedicated rename DDL is needed.
146
+
147
+ Keep the `declareEnumRename(...)` declaration until the rename has
148
+ applied in every environment you care about, then remove it (the new
149
+ value is by then the only label the enum knows).
150
+
151
+ ## Cross-dialect
152
+
153
+ | Dialect | Emits |
154
+ |------------------|------------------------------------------------------------------|
155
+ | postgres | `CREATE TYPE "name" AS ENUM (...)` + `<col> name` (native) |
156
+ | mysql / mariadb | `<col> ENUM('a','b','c')` (native enum column type) |
157
+ | mssql / sqlite | `<col> NVARCHAR(255) / TEXT` + `CHECK (col IN ('a','b','c'))` (fallback) |
158
+
159
+ The postgres path is the cheap one; the rest treat enums as
160
+ text + CHECK behind the scenes.
161
+
162
+ ## When NOT to use
163
+
164
+ - **You're on mysql or sqlite primarily** — the postgres-only benefit
165
+ doesn't apply. Use `text().oneOf([...])` for consistency.
166
+ - **You need value-level metadata** (descriptions, sort orders) —
167
+ enums are just strings. Build a lookup table.
168
+ - **The value set changes weekly** — every change is still a schema
169
+ migration. For high-churn lists, store the values in a table.
170
+
171
+ ## See also
172
+
173
+ - [Columns](/docs/database/columns) — `text().oneOf([...])` for the
174
+ cross-dialect CHECK form
175
+ - [Migrations](/docs/database/migrations/operation-classes) — the
176
+ planner's classification for enum changes
177
+
178
+
179
+
180
+ ---
181
+
182
+ <!-- source: en/database/generated-columns.md -->
183
+ ## Generated columns (DB-level computed)
184
+
185
+ _Columns the DB engine computes from other columns. Distinct from `.computed()` which runs at INSERT in the app._
186
+
187
+ `.generatedAs(expression, { stored? })` declares a column whose
188
+ value the DB engine computes from other columns on the same row.
189
+ The framework keeps it consistent on INSERT + UPDATE automatically
190
+ — no app-side stamping needed.
191
+
192
+ ## Quick start
193
+
194
+ ```ts
195
+ import { text, id, table } from '@voltro/database'
196
+
197
+ export const people = table('people', {
198
+ id: id(),
199
+ firstName: text(),
200
+ lastName: text(),
201
+ fullName: text().generatedAs(`"firstName" || ' ' || "lastName"`, { stored: true }),
202
+ })
203
+
204
+ // Insert without setting fullName — the DB fills it
205
+ await ctx.store.insert('people', { firstName: 'Mario', lastName: 'Lima' })
206
+ // Read it back
207
+ const rows = await ctx.store.query(database.people.descriptor)
208
+ rows[0]?.fullName // → 'Mario Lima'
209
+ ```
210
+
211
+ ## STORED vs VIRTUAL
212
+
213
+ `{ stored: true }` (the rare-but-useful case):
214
+
215
+ ```ts
216
+ fullName: text().generatedAs(`"firstName" || ' ' || "lastName"`, { stored: true })
217
+ ```
218
+
219
+ - Value is **persisted on disk** — same storage cost as a regular column.
220
+ - **Indexable** — combine with `.expressionIndex('byFullName', ['fullName'])`
221
+ for cheap lookups.
222
+ - Required when the column drives full-text search (see [Full-text search](/docs/database/full-text-search))
223
+ or any other indexed access pattern.
224
+
225
+ `{ stored: false }` (the default):
226
+
227
+ ```ts
228
+ fullName: text().generatedAs(`"firstName" || ' ' || "lastName"`)
229
+ // stored: false
230
+ ```
231
+
232
+ - Value is **recomputed on every read** — no disk cost.
233
+ - **NOT indexable** — the DB has nothing physical to index.
234
+ - Postgres has no VIRTUAL form; the framework auto-promotes to
235
+ STORED and logs a console.warn at boot so you know.
236
+
237
+ | Dialect | STORED | VIRTUAL |
238
+ |------------------|-------------------------------------------------|--------------------------|
239
+ | postgres 12+ | `GENERATED ALWAYS AS (expr) STORED` | not supported → STORED (warns) |
240
+ | mysql 5.7+ | `GENERATED ALWAYS AS (expr) STORED` | `GENERATED ALWAYS AS (expr) VIRTUAL` |
241
+ | mariadb 10.2+ | same as mysql | same as mysql |
242
+ | mssql | `AS (expr) PERSISTED` | `AS (expr)` (computed col, not persisted) |
243
+ | sqlite 3.31+ | `GENERATED ALWAYS AS (expr) STORED` | `GENERATED ALWAYS AS (expr) VIRTUAL` (default) |
244
+
245
+ ## Common patterns
246
+
247
+ ### Search-friendly text column
248
+
249
+ ```ts
250
+ posts: table('posts', {
251
+ id: id(),
252
+ title: text(),
253
+ body: text(),
254
+ searchVec: text().generatedAs(
255
+ `to_tsvector('english', "title" || ' ' || "body")`,
256
+ { stored: true },
257
+ ),
258
+ }).expressionIndex('posts_search_gin', [{ expr: '"searchVec"' }], { kind: 'gin' })
259
+ ```
260
+
261
+ See [Full-text search](/docs/database/full-text-search) — the
262
+ framework's `.fullTextIndex()` helper builds this exact pattern
263
+ for you in one chain.
264
+
265
+ ### Numeric derived column
266
+
267
+ ```ts
268
+ orders: table('orders', {
269
+ id: id(),
270
+ qty: integer(),
271
+ price: integer(),
272
+ total: integer().generatedAs(`"qty" * "price"`, { stored: true }),
273
+ })
274
+ ```
275
+
276
+ Cheap to read (no per-query arithmetic), atomic on update — change
277
+ `qty` or `price` and `total` updates in the same statement.
278
+
279
+ ### Slug from a title
280
+
281
+ ```ts
282
+ posts: table('posts', {
283
+ id: id(),
284
+ title: text(),
285
+ // Postgres: LOWER + replace spaces — pure expression, no functions
286
+ slug: text().generatedAs(`LOWER(REPLACE("title", ' ', '-'))`, { stored: true }),
287
+ })
288
+ ```
289
+
290
+ For complex slug logic (transliteration, deduplication), use
291
+ `.computed(row => ...)` instead — it runs in the MutationStore
292
+ middleware where you have the full JS stdlib.
293
+
294
+ ## Distinct from `.computed(row => ...)`
295
+
296
+ The framework has two "derive a column from other columns" hooks.
297
+ They look similar but solve different problems:
298
+
299
+ | Property | `.computed(row => ...)` | `.generatedAs(expr, ...)` |
300
+ |----------------------------------|---------------------------------------------|-------------------------------------------|
301
+ | Runs in | App (MutationStore middleware) | DB engine |
302
+ | Can reference subject/tenant context | Yes — the row carries them at stamp time | NO — only same-row columns |
303
+ | Fires on | INSERT only (v1) | INSERT + UPDATE |
304
+ | Indexable | Yes — the column is a regular `text()` | Only when `stored: true` |
305
+ | Cross-dialect | Identical on every backend | Per-dialect syntax (framework dispatches) |
306
+ | Value visible to raw SQL? | Yes (after insert) | Yes always |
307
+
308
+ **Reach for `.computed()` when** you need subject context, complex
309
+ JS logic, or the value comes from outside the row.
310
+
311
+ **Reach for `.generatedAs()` when** the value is a pure expression
312
+ over other columns + you want UPDATE to keep it in sync.
313
+
314
+ ## Suppressed modifiers
315
+
316
+ The framework suppresses `NOT NULL` / `UNIQUE` / `DEFAULT` on
317
+ generated columns automatically. The DDL would conflict in some
318
+ drivers:
319
+
320
+ ```ts
321
+ // These modifiers are SUPPRESSED on a generated column:
322
+ title: text().unique().default('untitled')
323
+ .generatedAs(`LOWER("rawTitle")`, { stored: true })
324
+ // Emitted: ... text GENERATED ALWAYS AS (LOWER("rawTitle")) STORED
325
+ // ^^ no NOT NULL, no UNIQUE, no DEFAULT
326
+ ```
327
+
328
+ To enforce uniqueness on a generated column, declare a UNIQUE INDEX
329
+ on it explicitly via `.expressionIndex(name, [...], { ... })`.
330
+
331
+ ## When NOT to use
332
+
333
+ - **You need conditional logic** (`if A then B else C`) — most
334
+ DB-level expressions support this via `CASE WHEN`, but complex
335
+ cases land in `.computed()` more naturally.
336
+ - **You need context** (subject id, tenant id, request metadata) —
337
+ not visible to a DB-level expression. Use `.computed()` or
338
+ `.default(() => ...)`.
339
+ - **You need it on a different table** — generated columns can only
340
+ reference columns on the SAME row.
341
+
342
+ ## See also
343
+
344
+ - [Columns](/docs/database/columns) — `.computed(row => ...)` and
345
+ `.default(() => ...)` for the app-side variants
346
+ - [Indexes](/docs/database/indexes#expression) — `.expressionIndex()`
347
+ for indexing a generated column
348
+ - [Full-text search](/docs/database/full-text-search) — the FTS
349
+ pattern uses STORED tsvector generated columns
350
+
351
+
352
+
353
+ ---
354
+
355
+ <!-- source: en/database/arrays-intervals.md -->
356
+ ## Arrays + intervals
357
+
358
+ _Postgres-native array() and interval() columns. App-side codec keeps the API portable across mysql/mariadb/mssql/sqlite/turso._
359
+
360
+ `array(elementType)` and `interval()` ship native Postgres types
361
+ with transparent fallbacks for the other dialects. Your code reads
362
+ and writes JS arrays / strings; the framework handles the per-
363
+ dialect serialization.
364
+
365
+ ## Arrays
366
+
367
+ ```ts
368
+ import { array, text, integer, id, table } from '@voltro/database'
369
+
370
+ export const posts = table('posts', {
371
+ id: id(),
372
+ title: text(),
373
+ tags: array(text()), // ReadonlyArray<string>
374
+ scores: array(integer()), // ReadonlyArray<number>
375
+ })
376
+
377
+ // Insert — pass a regular JS array
378
+ await ctx.store.insert('posts', {
379
+ title: 'voltro is great',
380
+ tags: ['voltro', 'effect', 'pg'],
381
+ scores: [10, 20, 30],
382
+ })
383
+
384
+ // Read — get a regular JS array back
385
+ const rows = await ctx.store.query(database.posts.descriptor)
386
+ rows[0]?.tags // → ['voltro', 'effect', 'pg']
387
+ ```
388
+
389
+ The element type drives both the row type AND the per-dialect
390
+ storage:
391
+
392
+ | Element | Postgres column | Fallback dialects |
393
+ |----------------|-----------------|---------------------|
394
+ | `array(text())` | `TEXT[]` | JSON / NVARCHAR(MAX) / TEXT |
395
+ | `array(integer())` | `INTEGER[]` | JSON / NVARCHAR(MAX) / TEXT |
396
+ | `array(boolean())` | `BOOLEAN[]` | JSON / NVARCHAR(MAX) / TEXT |
397
+ | `array(timestamp())` | `TIMESTAMPTZ[]` | JSON / NVARCHAR(MAX) / TEXT |
398
+
399
+ ### App-side codec
400
+
401
+ On non-postgres dialects, the framework's storeMiddleware
402
+ transparently:
403
+
404
+ - **Write**: `JSON.stringify(array)` before binding to the SQL
405
+ statement. Native JSON serialisation, preserves types correctly.
406
+ - **Read**: `JSON.parse(string)` on the way out. Defensive — pre-
407
+ array values (some drivers do this themselves) pass through;
408
+ null / undefined pass through; malformed JSON leaves the raw
409
+ string (logs no error so caller can debug).
410
+
411
+ Postgres is a no-op — the driver binds arrays natively, no codec
412
+ needed.
413
+
414
+ ### Defaults
415
+
416
+ ```ts
417
+ tags: array(text()).default([])
418
+ ```
419
+
420
+ The literal `[]` becomes `'{}'::text[]` on postgres + `'[]'` (JSON
421
+ empty array) on the fallbacks. Use `[]` rather than `'{}'` even
422
+ though postgres accepts the latter — the JS-array form keeps the
423
+ schema portable.
424
+
425
+ ### Querying
426
+
427
+ Array containment operators are in the query builder:
428
+
429
+ ```ts
430
+ import { arrayHas, arrayContains, arrayOverlaps } from '@voltro/database'
431
+
432
+ queryFor(database.posts).where(arrayHas('tags', 'effect')) // 'effect' ∈ tags
433
+ queryFor(database.posts).where(arrayContains('tags', ['effect', 'ts'])) // tags ⊇ both
434
+ queryFor(database.posts).where(arrayOverlaps('tags', ['effect', 'go'])) // shares ≥1
435
+ ```
436
+
437
+ On postgres these compile to the native `= ANY(col)` / `@>` / `&&`
438
+ operators. On the other dialects, where arrays are stored as JSON,
439
+ they lower to a JSON-containment check (`JSON_CONTAINS` on mysql/mariadb,
440
+ `json_each` on sqlite, `OPENJSON` on mssql) — so the same query is
441
+ portable. Postgres is fastest here (a GIN index on the array column
442
+ accelerates `@>`/`&&`); the JSON paths scan.
443
+
444
+ For fallback dialects (JSON-shaped storage), index-friendly
445
+ querying is even harder — the framework recommends extracting the
446
+ array contents to a separate join table if you need filtering by
447
+ contents.
448
+
449
+ ## Intervals
450
+
451
+ Time durations — SLA deadlines, rate-limit windows, "expires-in":
452
+
453
+ ```ts
454
+ import { interval, id, text, table } from '@voltro/database'
455
+
456
+ export const tickets = table('tickets', {
457
+ id: id(),
458
+ title: text(),
459
+ slaDeadline: interval(), // postgres native, others fall back
460
+ })
461
+
462
+ // Insert — string form on postgres, ms-number on others
463
+ await ctx.store.insert('tickets', {
464
+ title: 'investigate logs',
465
+ slaDeadline: '4 hours', // postgres-native literal
466
+ })
467
+ ```
468
+
469
+ | Dialect | Storage | Input form |
470
+ |------------------|-----------|-------------------------------------------|
471
+ | postgres | `INTERVAL` | string (any pg interval literal: '1 day', '45 minutes', etc.) |
472
+ | mysql / mariadb | `BIGINT` | number of milliseconds |
473
+ | mssql | `BIGINT` | number of milliseconds |
474
+ | sqlite | `INTEGER` | number of milliseconds |
475
+
476
+ For portability across dialects you'd need a per-dialect codec; the
477
+ framework doesn't ship one in v1. Apps that target multiple
478
+ dialects with intervals should store milliseconds-as-BIGINT
479
+ explicitly via `integer()` and convert at read time.
480
+
481
+ ### Querying
482
+
483
+ Postgres lets you compute against intervals natively:
484
+
485
+ ```sql
486
+ WHERE "createdAt" + "slaDeadline" < NOW()
487
+ ```
488
+
489
+ The query builder doesn't have a typed wrapper for this — use
490
+ sub-query helpers or drop to raw SQL.
491
+
492
+ ## When NOT to use
493
+
494
+ - **You're not on postgres** — the array fallback (JSON-shaped
495
+ storage) is correct but slow for any non-trivial query. If you
496
+ need cross-dialect array support with index-friendly access,
497
+ use a join table (the `manyToMany` mixin pattern).
498
+ - **Intervals on mysql/mssql/sqlite** — the BIGINT-ms fallback
499
+ works but loses the postgres-native arithmetic. For cross-
500
+ dialect interval semantics, store milliseconds in `integer()`
501
+ and do the math in JS.
502
+ - **Heavy spatial work** — see [PostGIS](/docs/database/postgis)
503
+ instead of trying to roll your own geometry-as-array column.
504
+
505
+ ## See also
506
+
507
+ - [Columns](/docs/database/columns) — the regular column types you
508
+ pass to `array(...)`
509
+ - [PostGIS](/docs/database/postgis) — for location-aware apps,
510
+ the spatial types are better than arrays of coordinates
511
+ - [JSON](/docs/database/json) — `json<T>()` for arbitrary nested
512
+ structures (arrays are special-cased; JSON is the general form)
513
+
514
+
515
+
516
+ ---
517
+
518
+ <!-- source: en/database/full-text-search.md -->
519
+ ## Full-text search
520
+
521
+ _.fullTextIndex() + .matching() for tsvector / FULLTEXT INDEX search across columns. Cross-dialect per-engine where available._
522
+
523
+ `.fullTextIndex(name, columns, options?)` declares an FTS index on
524
+ one or more text columns. `.matching('indexName', 'query')` on the
525
+ query builder runs the search.
526
+
527
+ ## Quick start
528
+
529
+ ```ts
530
+ import { id, text, table } from '@voltro/database'
531
+
532
+ export const posts = table('posts', {
533
+ id: id(),
534
+ title: text(),
535
+ body: text(),
536
+ }).fullTextIndex('postSearch', ['title', 'body'], {
537
+ config: 'english',
538
+ weights: { title: 'A', body: 'B' },
539
+ })
540
+
541
+ // Query
542
+ const hits = await ctx.store.query(
543
+ queryFor(database.posts).matching('postSearch', 'voltro effect').descriptor,
544
+ )
545
+ ```
546
+
547
+ ## What the framework emits
548
+
549
+ ### Postgres — tsvector + GIN
550
+
551
+ Postgres' canonical pattern:
552
+
553
+ ```sql
554
+ ALTER TABLE "posts"
555
+ ADD COLUMN IF NOT EXISTS "postSearch_tsv" tsvector
556
+ GENERATED ALWAYS AS (
557
+ setweight(to_tsvector('english', coalesce("title", '')), 'A') ||
558
+ setweight(to_tsvector('english', coalesce("body", '')), 'B')
559
+ ) STORED;
560
+
561
+ CREATE INDEX IF NOT EXISTS "postSearch" ON "posts" USING GIN ("postSearch_tsv");
562
+ ```
563
+
564
+ The framework adds the synthetic `<index>_tsv` STORED tsvector
565
+ column + a GIN index. `.matching(...)` queries compile to:
566
+
567
+ ```sql
568
+ "postSearch_tsv" @@ plainto_tsquery('voltro effect')
569
+ ```
570
+
571
+ ### MySQL / MariaDB — FULLTEXT INDEX
572
+
573
+ ```sql
574
+ CREATE FULLTEXT INDEX `postSearch` ON `posts` (`title`, `body`);
575
+ ```
576
+
577
+ `.matching(...)` queries compile to:
578
+
579
+ ```sql
580
+ MATCH("title", "body") AGAINST('voltro effect' IN NATURAL LANGUAGE MODE)
581
+ ```
582
+
583
+ The `IN NATURAL LANGUAGE MODE` is mysql's default-tier ranking —
584
+ relevance-weighted, no Boolean operators. For Boolean mode
585
+ (`+voltro +effect`), use the raw SQL escape hatch.
586
+
587
+ ### SQLite — native FTS5
588
+
589
+ The migrator creates a native FTS5 virtual table named
590
+ `<table>_<index>_fts` plus three triggers that keep it in lockstep
591
+ with the base table on every INSERT / UPDATE / DELETE:
592
+
593
+ ```sql
594
+ CREATE VIRTUAL TABLE IF NOT EXISTS "posts_postSearch_fts"
595
+ USING fts5("title", "body", row_id UNINDEXED, tokenize='porter');
596
+ -- + AFTER INSERT / UPDATE / DELETE triggers syncing row_id = posts.id
597
+ ```
598
+
599
+ `row_id` carries the base row's string `id` (FTS5's integer rowid
600
+ can't hold a TypeID). `.matching(...)` queries compile to:
601
+
602
+ ```sql
603
+ "id" IN (SELECT "row_id" FROM "posts_postSearch_fts" WHERE "posts_postSearch_fts" MATCH 'voltro effect')
604
+ ```
605
+
606
+ `tokenize='porter'` gives stemming comparable to postgres' `'english'`
607
+ config + mysql's natural-language mode.
608
+
609
+ ### MSSQL — ranked LIKE fallback
610
+
611
+ MSSQL native full-text needs a server-managed `CREATE FULLTEXT
612
+ CATALOG` (operations work, not always available). Rather than require
613
+ that, the framework emits a **portable ranked-LIKE search** at query
614
+ time — no catalog, no `CONTAINS`, works on every MSSQL install. The
615
+ migrator emits no FTS DDL (every base column is already present) and
616
+ notes the fallback at boot:
617
+
618
+ ```
619
+ [voltro:migrate] full-text index 'postSearch' on 'posts':
620
+ mssql uses a ranked LIKE search at query time (no native FTS catalog required).
621
+ ```
622
+
623
+ `.matching(...)` compiles to a per-column case-insensitive LIKE
624
+ disjunction:
625
+
626
+ ```sql
627
+ (LOWER("title") LIKE LOWER('%voltro effect%') ESCAPE '\'
628
+ OR LOWER("body") LIKE LOWER('%voltro effect%') ESCAPE '\')
629
+ ```
630
+
631
+ This is substring matching, not tokenised FTS — it won't stem or rank
632
+ on term frequency the way postgres/mysql/sqlite do. It's the honest
633
+ portable path; for true server FTS on MSSQL set up a catalog + use raw
634
+ SQL with `CONTAINS`.
635
+
636
+ ## Configuring per-language
637
+
638
+ ```ts
639
+ .fullTextIndex('postSearch', ['title', 'body'], {
640
+ config: 'german', // postgres tsvector config
641
+ weights: { title: 'A', body: 'B', tags: 'C' },
642
+ })
643
+ ```
644
+
645
+ `config` defaults to `'english'`. Postgres ships configs for
646
+ ~20 languages; install additional ones via `CREATE TEXT SEARCH
647
+ CONFIGURATION` if needed. The framework emits the config name
648
+ verbatim into the `to_tsvector(...)` call.
649
+
650
+ `weights` is ignored on non-postgres dialects (no per-column
651
+ weight concept in FULLTEXT INDEX).
652
+
653
+ ## Multi-column with per-column weights
654
+
655
+ ```ts
656
+ .fullTextIndex('postSearch', ['title', 'body', 'tags'], {
657
+ weights: { title: 'A', body: 'B', tags: 'C' },
658
+ })
659
+ ```
660
+
661
+ Postgres combines them in the generated tsvector — a query that
662
+ matches `title` ranks higher than one that only matches `tags`.
663
+
664
+ ## Relevance ranking — `.rankBy()`
665
+
666
+ Chain `.rankBy()` after `.matching(...)` to surface a relevance score
667
+ column AND order results best-match-first — on every dialect:
668
+
669
+ ```ts
670
+ queryFor(database.posts)
671
+ .matching('postSearch', input.query)
672
+ .rankBy() // adds a `rank` column + ORDER BY rank DESC
673
+ .limit(20)
674
+ ```
675
+
676
+ Each returned row carries a numeric `rank` (higher = better). The
677
+ score expression is per-dialect, but the API is identical:
678
+
679
+ | Dialect | Score expression |
680
+ |------------------|-------------------------------------------|
681
+ | postgres | `ts_rank(<tsvCol>, plainto_tsquery(...))` |
682
+ | mysql / mariadb | `MATCH(cols) AGAINST(...)` score |
683
+ | sqlite | `-bm25(<fts>)` (negated so higher = better) |
684
+ | mssql | count of columns whose `LOWER LIKE` the query |
685
+
686
+ Options:
687
+
688
+ - `.rankBy({ alias: 'score' })` — name the column `score` instead of `rank`.
689
+ - `.rankBy({ order: false })` — surface the score column WITHOUT forcing
690
+ `ORDER BY` (combine with your own `.orderBy(...)`).
691
+
692
+ ```ts
693
+ // Score column, but sort by recency with relevance as a secondary key:
694
+ queryFor(database.posts)
695
+ .matching('postSearch', input.query)
696
+ .rankBy({ order: false })
697
+ .orderBy('createdAt', 'desc')
698
+ .limit(20)
699
+ ```
700
+
701
+ On postgres the `ts_rank` reads the same stored tsvector column the
702
+ GIN index covers, so ranking stays index-accelerated.
703
+
704
+ ## Querying
705
+
706
+ `.matching('indexName', 'query')` is composable with other chain
707
+ methods:
708
+
709
+ ```ts
710
+ queryFor(database.posts)
711
+ .where(eq('orgId', oid)) // pre-filter
712
+ .matching('postSearch', input.query) // FTS
713
+ .orderBy('createdAt', 'desc') // tie-breaker
714
+ .limit(20)
715
+ ```
716
+
717
+ Multiple `.matching()` calls override — FTS is a single-string
718
+ concept, no AND-merge across queries.
719
+
720
+ ## Limitations
721
+
722
+ - **MSSQL is a ranked LIKE fallback, not tokenised FTS.** It matches
723
+ substrings (`LOWER(col) LIKE '%query%'`) and ranks by per-column
724
+ hit count — no stemming, no term-frequency weighting. For true
725
+ server FTS on MSSQL, set up a `CREATE FULLTEXT CATALOG` and use raw
726
+ SQL with `CONTAINS`.
727
+ - **Boolean / phrase queries on postgres** — `.matching(...)` uses
728
+ `plainto_tsquery` which strips operators. For Boolean
729
+ (`+voltro -effect`) drop to `store.raw` with `to_tsquery(...)`:
730
+ ```ts
731
+ import { sql } from '@voltro/database/sql'
732
+ await ctx.store.raw!<{ id: string }>(sql`
733
+ SELECT id FROM posts
734
+ WHERE "postSearch_tsv" @@ to_tsquery(${input.query})
735
+ `, { dependsOn: ['posts'] })
736
+ ```
737
+ The query string binds as a parameter; `to_tsquery` parses its
738
+ operators (`+`, `-`, `&`, `|`, `<->`).
739
+ - **Reactivity** — FTS subscriptions ARE reactive on postgres (the
740
+ tsvector column is a regular column the dispatcher tracks). On
741
+ mysql/mariadb the MATCH AGAINST predicate isn't bucketed into
742
+ the matcher's index — subscriptions re-evaluate on every write
743
+ to the source columns.
744
+
745
+ ## When NOT to use
746
+
747
+ - **Trigram / fuzzy match** — postgres' `pg_trgm` extension is a
748
+ different access pattern. Install + use raw SQL.
749
+ - **Vector search** — see [Vector columns](/docs/database/vectors)
750
+ for embeddings-based semantic search.
751
+ - **Substring search on a small table** — `LIKE '%term%'` is fine
752
+ up to ~100k rows. Don't carry the FTS overhead for tiny tables.
753
+
754
+ ## See also
755
+
756
+ - [Generated columns](/docs/database/generated-columns) — the FTS
757
+ pattern is "generated tsvector + GIN expression index"
758
+ - [Indexes](/docs/database/indexes) — `.expressionIndex({ kind: 'gist' })`
759
+ for the GIN/GiST plumbing under FTS
760
+ - [Vector columns](/docs/database/vectors) — for embedding-based
761
+ semantic search (different problem from keyword FTS)
762
+
763
+
764
+
765
+ ---
766
+
767
+ <!-- source: en/database/postgis.md -->
768
+ ## PostGIS (geography / geometry)
769
+
770
+ _Postgres-native geography/geometry columns, geometry constructors, spatial predicates (ST_DWithin, ST_Within, ST_Contains, ST_Intersects, ST_Buffer, bbox), ST_Distance projection + <-> KNN ordering for location-aware apps. Postgres only._
771
+
772
+ The `@voltro/plugin-postgis` package adds first-class PostGIS columns
773
+ and spatial operators to the Voltro schema DSL. **Postgres only.**
774
+ PostGIS is a postgres extension; other dialects have no portable
775
+ equivalent, so schema emission (`voltro migrate` / auto-migrate)
776
+ FAILS LOUD with a `postgres-only` error on non-postgres backends,
777
+ and spatial predicates throw in the SQL compiler.
778
+
779
+ ## Install + enable
780
+
781
+ ```bash
782
+ pnpm add @voltro/plugin-postgis
783
+ ```
784
+
785
+ On postgres, the migrator emits a one-time
786
+ `CREATE EXTENSION IF NOT EXISTS postgis;` alongside the first spatial
787
+ column's DDL. If your database user lacks the privilege to create
788
+ extensions, run that statement once per database as a superuser
789
+ before migrating.
790
+
791
+ ## Geography vs geometry
792
+
793
+ | Type | Coordinate system | Distance calculation | When to use |
794
+ |---|---|---|---|
795
+ | `geography(...)` | Spheroidal (curves with the earth) | Great-circle | Global apps, WGS-84 lat/lon (SRID 4326). Easy mental model. |
796
+ | `geometry(...)` | Planar / projected | Straight-line in the SRID's frame | Local apps with projected coords (UTM zone, state plane). Faster + works with non-spherical analysis. |
797
+
798
+ If you're unsure: pick `geography` with SRID 4326. It's the
799
+ default mental model for "GPS coordinates" and the distance
800
+ calculations are correct for any global scale.
801
+
802
+ ## Declare a location column
803
+
804
+ ```ts
805
+ import { geography, point } from '@voltro/plugin-postgis'
806
+ import { id, text, table, queryFor } from '@voltro/database'
807
+
808
+ export const venues = table('venues', {
809
+ id: id(),
810
+ name: text(),
811
+ location: geography('Point', 4326), // lon/lat
812
+ })
813
+ ```
814
+
815
+ Insert a row using the `point()` helper to build the EWKT literal:
816
+
817
+ ```ts
818
+ await ctx.store.insert('venues', {
819
+ name: 'Café Mockup',
820
+ location: point(8.682, 50.110, 4326), // Frankfurt
821
+ })
822
+ ```
823
+
824
+ The literal serialises as `SRID=4326;POINT(8.682 50.11)`, which
825
+ postgres parses into the geography column via implicit cast.
826
+
827
+ ## Query within a radius
828
+
829
+ ```ts
830
+ import { ST_DWithin, point } from '@voltro/plugin-postgis'
831
+ import { queryFor } from '@voltro/database'
832
+
833
+ const nearMe = await ctx.store.query(
834
+ queryFor(venues)
835
+ .where(ST_DWithin('location', point(8.68, 50.11), 1000)) // 1 km
836
+ .descriptor,
837
+ )
838
+ ```
839
+
840
+ `ST_DWithin(column, point, meters)` is the canonical "find rows
841
+ within X distance" predicate. PostGIS handles the great-circle math.
842
+
843
+ ## Query inside a polygon
844
+
845
+ ```ts
846
+ import { ST_Intersects, polygon } from '@voltro/plugin-postgis'
847
+ import { queryFor } from '@voltro/database'
848
+
849
+ const cityCenter = polygon([
850
+ [[8.65, 50.10], [8.72, 50.10], [8.72, 50.13], [8.65, 50.13], [8.65, 50.10]],
851
+ ])
852
+
853
+ const inside = await ctx.store.query(
854
+ queryFor(venues)
855
+ .where(ST_Intersects('location', cityCenter)) // venue location ∈ polygon
856
+ .descriptor,
857
+ )
858
+ ```
859
+
860
+ Every predicate takes the **column first**, then the WKT geometry:
861
+
862
+ - `ST_DWithin(column, geom, meters)` — within a metric radius.
863
+ - `ST_Within(column, geom)` — the column's geometry is fully inside
864
+ `geom` (the right shape for "point column inside a polygon").
865
+ - `ST_Contains(column, geom)` — the column's geometry fully contains
866
+ `geom` (e.g. a region `boundary` column containing a point).
867
+ - `ST_Intersects(column, geom)` — any shared point.
868
+ - `ST_Buffer(column, geom, meters)` — the column intersects `geom`
869
+ grown by `meters` (a metric buffer; shape-aware "within N metres").
870
+ - `bboxOverlaps(column, geom)` — the `&&` bounding-box overlap
871
+ operator: cheap, GiST-index-only, approximate. Build the box with
872
+ `envelope(minLon, minLat, maxLon, maxLat, srid)`.
873
+
874
+ Spatial predicates work in one-shot `ctx.store.query` reads only;
875
+ they throw in the in-memory reactive matcher, so keep them out of
876
+ subscriptions.
877
+
878
+ ## Geometry constructors
879
+
880
+ `point` / `lineString` / `polygon` / `multiPoint` /
881
+ `multiLineString` / `multiPolygon` build EWKT literals; `geoJson`
882
+ lowers a GeoJSON geometry to the same EWKT (SRID 4326 by the GeoJSON
883
+ spec). Coordinates are `[lon, lat]` (x before y) throughout.
884
+
885
+ ```ts
886
+ import { point, lineString, polygon, multiPolygon, geoJson } from '@voltro/plugin-postgis'
887
+
888
+ const route = lineString([[8.68, 50.11], [8.69, 50.12]], 4326)
889
+ const zones = multiPolygon([[[[8.6, 50.1], [8.7, 50.1], [8.7, 50.2], [8.6, 50.1]]]], 4326)
890
+ const fromGeoJson = geoJson({ type: 'Point', coordinates: [8.682, 50.110] })
891
+ ```
892
+
893
+ ## Surfacing distance + nearest-neighbour ordering
894
+
895
+ `withDistance(...)` projects `ST_Distance(column, geom)` as a
896
+ selectable column (metric metres by default), and `nearestBy(...)`
897
+ adds a `<->` KNN `ORDER BY` for indexed nearest-neighbour search.
898
+ Both are `QueryTransform`s applied via the builder's `.use(...)` seam
899
+ (the same mechanism as `hybridSearch`), so they compose with
900
+ `.where(...)` and `.limit(k)`. Postgres-only.
901
+
902
+ ```ts
903
+ import { withDistance, nearestBy, ST_DWithin, point } from '@voltro/plugin-postgis'
904
+ import { queryFor } from '@voltro/database'
905
+
906
+ // Distance-as-a-value in metres.
907
+ const withMeters = await ctx.store.query(
908
+ queryFor(venues)
909
+ .use(withDistance('location', point(8.68, 50.11), { as: 'meters' }))
910
+ .where(ST_DWithin('location', point(8.68, 50.11), 5000))
911
+ .descriptor,
912
+ )
913
+
914
+ // The 5 nearest venues via the indexed `<->` operator.
915
+ const nearest5 = await ctx.store.query(
916
+ queryFor(venues).use(nearestBy('location', point(8.68, 50.11))).limit(5).descriptor,
917
+ )
918
+ ```
919
+
920
+ `withDistance(column, geom, { as?, useGeography? })` aliases the
921
+ distance column (default `distance`); `nearestBy(column, geom,
922
+ { direction? })` leads any explicit `.orderBy(...)`. `<->` is planar
923
+ (postgres has no geography `<->`), so KNN ranks by planar distance —
924
+ add `withDistance` when you need a metric value.
925
+
926
+ ## Indexes
927
+
928
+ GiST is the PostGIS-recommended index access method for
929
+ geometry/geography columns. The framework's regular `.index([...])`
930
+ uses B-tree, which is useless for spatial queries — declare the
931
+ index with `kind: 'gist'` on `.expressionIndex()`:
932
+
933
+ ```ts
934
+ export const venues = table('venues', {
935
+ id: id(),
936
+ name: text(),
937
+ location: geography('Point', 4326),
938
+ }).expressionIndex(
939
+ 'venues_loc_gist',
940
+ [{ expr: '"location"' }],
941
+ { kind: 'gist' },
942
+ )
943
+ ```
944
+
945
+ The framework emits:
946
+
947
+ ```sql
948
+ CREATE INDEX IF NOT EXISTS "venues_loc_gist"
949
+ ON "venues" USING GIST (("location"));
950
+ ```
951
+
952
+ on postgres. On every other dialect the migrator warns + falls back
953
+ to a plain B-tree index (spatial queries will then fall back to
954
+ sequential scan — there's no portable equivalent of GiST).
955
+
956
+ Combine `kind: 'gist'` with `where: …` for a partial GiST index:
957
+
958
+ ```ts
959
+ .expressionIndex(
960
+ 'active_venues_loc_gist',
961
+ [{ expr: '"location"' }],
962
+ { kind: 'gist', where: `"active" = true` },
963
+ )
964
+ ```
965
+
966
+ ## Cross-dialect
967
+
968
+ PostGIS is a postgres extension. A schema using `geography()` /
969
+ `geometry()` fails loud at schema emission when the active dialect
970
+ isn't postgres — no silent TEXT fallback, no graceful degradation.
971
+ Apps that need spatial queries on
972
+ MySQL/MariaDB/MSSQL/SQLite should either:
973
+
974
+ - Use the dialect's native spatial type (mysql's `POINT`/`GEOMETRY`,
975
+ mssql's `geography`/`geometry`) via a file-based migration + raw SQL
976
+ - Store coords as numeric columns + filter app-side (acceptable up to
977
+ ~100k rows)
978
+
979
+ Voltro doesn't ship a cross-dialect spatial abstraction — the
980
+ behavioural gaps are too large to paper over.
981
+
982
+ ## See also
983
+
984
+ - [Columns](/docs/database/columns) — the regular schema-DSL types
985
+ - [Expression indexes](/docs/database/indexes#expression) — the
986
+ framework's index API (`kind: 'gist' | 'gin'` on postgres)
987
+ - [PostGIS docs](https://postgis.net/docs/) — the official manual,
988
+ authoritative for every spatial function the framework re-exports
989
+
990
+
991
+
992
+ ---
993
+
994
+ <!-- source: en/database/vectors.md -->
995
+ ## Vector columns
996
+
997
+ _Vectors and RAG — embedding columns, HNSW indexing, nearestNeighbours queries, the vectorEmbedding() mixin, across all five dialects._
998
+
999
+ For RAG and semantic search, vectors live in the same table as the rest of your data, in the same transactions, with the same tenant scoping. No separate vector DB. No two-store consistency model.
1000
+
1001
+ Vector storage works on **all five dialects** — the float array is always persisted. The HNSW *index* is emitted on **postgres** (via pgvector); MariaDB exposes native `VEC_DISTANCE_*` operators so its nearest-neighbour queries are correct, and the other dialects compute distance by sequential scan. The rule across the board: **portable result everywhere, index-accelerated on postgres**.
1002
+
1003
+ ## Prerequisites
1004
+
1005
+ On postgres, the `vector` extension provides the `VECTOR(n)` type, the distance operators, and the HNSW access method. The framework emits it automatically — `voltro migrate` prepends `CREATE EXTENSION IF NOT EXISTS vector;` to the migration whenever any table declares a `vector(...)` column. MariaDB / MySQL ship vectors natively (no extension); MSSQL / SQLite store the bytes with no extra step.
1006
+
1007
+ ## Declaring a vector column
1008
+
1009
+ ```ts
1010
+ import { table, id, text, vector } from '@voltro/database'
1011
+
1012
+ const docs = table('docs', {
1013
+ id: id(),
1014
+ body: text(),
1015
+ embedding: vector(1536),
1016
+ })
1017
+ ```
1018
+
1019
+ The number is the **dimensionality**, persisted on the column so the migration emitter can size the DDL (`VECTOR(1536)` on postgres / MariaDB / MySQL; `VARBINARY(MAX)` on MSSQL; `BLOB` on SQLite):
1020
+
1021
+ | Provider | Model | Dimensions |
1022
+ |---|---|---|
1023
+ | OpenAI | `text-embedding-3-small` | 1536 |
1024
+ | OpenAI | `text-embedding-3-large` | 3072 |
1025
+ | Anthropic | (via Voyage) `voyage-3` | 1024 |
1026
+ | Cohere | `embed-english-v3.0` | 1024 |
1027
+
1028
+ Pick once. Changing dimensionality means rebuilding the entire column. `vector(0)` and non-integer dimensions throw at declaration time.
1029
+
1030
+ ## Generating embeddings
1031
+
1032
+ ```ts
1033
+ import { embed } from '@voltro/ai'
1034
+
1035
+ const queryVec = yield* embed('how do mutations work?')
1036
+ // ReadonlyArray<number> of length 1536 (or whatever your provider returns)
1037
+
1038
+ await ctx.store.update('docs', id, { embedding: queryVec })
1039
+ ```
1040
+
1041
+ `embed` is an Effect — it runs on the server only (the provider call lives in `@voltro/ai`, never a browser bundle). For most apps you don't write this code at all — use the [`vectorEmbedding()` mixin](#auto-embedding-via-vectorembedding) instead and the runtime embeds on write for you.
1042
+
1043
+ ## ANN search
1044
+
1045
+ ```ts
1046
+ ctx.store.select('docs')
1047
+ .nearestNeighbours('embedding', queryVec, { distance: 'cosine' })
1048
+ .limit(5)
1049
+ .all()
1050
+ ```
1051
+
1052
+ Each result row carries a synthetic `distance` field. Available distance metrics:
1053
+
1054
+ | Metric | Operator (pgvector) | When |
1055
+ |---|---|---|
1056
+ | `cosine` *(default)* | `<=>` | Normalised text embeddings — most common. |
1057
+ | `l2` | `<->` | Euclidean. Image embeddings often need this. |
1058
+ | `inner` | `<#>` | Inner product. Useful for some recommendation models. |
1059
+
1060
+ The choice **must match** the index's opclass (see HNSW below), or the index can't accelerate the search.
1061
+
1062
+ On MariaDB / MySQL the same query compiles to `VEC_DISTANCE_COSINE(...)` / `VEC_DISTANCE_EUCLIDEAN(...)`. On MSSQL / SQLite there's no portable distance operator — the query still returns rows (sequential scan) but unranked; the migrate-time warning flags that the dialect has no ANN acceleration.
1063
+
1064
+ ## HNSW index
1065
+
1066
+ Without an index, `nearestNeighbours` is a sequential scan — fine for ≤10k rows, painful beyond. Indexes are declared **at the table level** (there is no column-level `.index()` modifier). Add an HNSW index over the vector column:
1067
+
1068
+ ```ts
1069
+ const docs = table('docs', {
1070
+ id: id(),
1071
+ body: text(),
1072
+ embedding: vector(1536),
1073
+ }).index('docsEmbeddingHnsw', ['embedding'], { kind: 'hnsw' })
1074
+ ```
1075
+
1076
+ With explicit tuning:
1077
+
1078
+ ```ts
1079
+ const docs = table('docs', {
1080
+ id: id(),
1081
+ body: text(),
1082
+ embedding: vector(1536),
1083
+ }).index('docsEmbeddingHnsw', ['embedding'], {
1084
+ kind: 'hnsw',
1085
+ kindOptions: {
1086
+ hnsw: {
1087
+ m: 24, // links per node — higher = better recall + more memory (default 16)
1088
+ efConstruction: 128, // index build effort — higher = better quality + slower build (default 64)
1089
+ distance: 'cosine', // selects the opclass; MUST match query distance (default 'cosine')
1090
+ },
1091
+ },
1092
+ })
1093
+ ```
1094
+
1095
+ The `distance` metric selects the pgvector opclass (`cosine` → `vector_cosine_ops`, `l2` → `vector_l2_ops`, `inner` → `vector_ip_ops`). Set `opclass` directly to override (e.g. `halfvec_cosine_ops` for a half-precision column). Declaring `kind: 'hnsw'` on a non-vector column, on multiple fields, or on an expression throws at declaration time.
1096
+
1097
+ The HNSW DDL is postgres-only; on MariaDB / MySQL / MSSQL / SQLite the index is skipped with a migrate-time warning (a btree on a vector is meaningless). MariaDB queries still run correctly via its native `VEC_DISTANCE_*` operators — just without index acceleration.
1098
+
1099
+ ## Per-query effort
1100
+
1101
+ ```ts
1102
+ ctx.store.select('docs')
1103
+ .nearestNeighbours('embedding', queryVec)
1104
+ .efSearch(80) // search effort (default: 40)
1105
+ .limit(10)
1106
+ .all()
1107
+ ```
1108
+
1109
+ Higher `efSearch` = better recall, slower query. Sweet spot is usually 40-100; benchmark on your data. (pgvector `hnsw.ef_search`.)
1110
+
1111
+ ## Tenant scoping + vectors
1112
+
1113
+ If the table has the `tenant()` mixin, vector searches stay scoped — they only consider rows in the caller's tenant. The runtime AND-merges the tenant filter before the ANN order/limit, so cross-tenant rows never enter the candidate set. No cross-tenant leakage via similarity search.
1114
+
1115
+ ```ts
1116
+ const docs = table('docs', {
1117
+ id: id(),
1118
+ body: text(),
1119
+ embedding: vector(1536),
1120
+ })
1121
+ .with(tenant())
1122
+ .index('docsEmbeddingHnsw', ['embedding'], { kind: 'hnsw' })
1123
+
1124
+ // Cross-tenant searches IMPOSSIBLE — the runtime injects the tenant filter.
1125
+ ```
1126
+
1127
+ ## Auto-embedding via `vectorEmbedding()`
1128
+
1129
+ Most RAG apps want: write text → embedding gets generated automatically. The mixin adds the vector column, its HNSW index, and the re-embed behaviors in one line:
1130
+
1131
+ ```ts
1132
+ import { vectorEmbedding } from '@voltro/database'
1133
+
1134
+ const docs = table('docs', {
1135
+ id: id(),
1136
+ body: text(),
1137
+ }).with(vectorEmbedding({
1138
+ from: 'body',
1139
+ model: 'text-embedding-3-small',
1140
+ dimensions: 1536,
1141
+ }))
1142
+ ```
1143
+
1144
+ Behaviour:
1145
+
1146
+ - On INSERT, the runtime computes the embedding from `body` + writes it into the `embedding` column (override the column name with `as`).
1147
+ - On UPDATE of `body`, the embedding is recomputed.
1148
+ - The mixin also contributes the HNSW index (pass `index: false` to skip it, `distance` to pick the metric).
1149
+ - A query helper `nearestNeighbours(queryString, k)` embeds the string for you:
1150
+
1151
+ ```ts
1152
+ ctx.store.select('docs')
1153
+ .nearestNeighbours('how do mutations work?', 5)
1154
+ .all()
1155
+ ```
1156
+
1157
+ The runtime injects `@voltro/ai`'s `embed` into the mixin's write hook — the embedding API calls are billed to your provider account and traced by `@voltro/plugin-audit`.
1158
+
1159
+ ### Backfilling pre-existing rows
1160
+
1161
+ The mixin only embeds rows written *after* it's in place. For rows that already existed (or after switching embedding model), seed them with the CLI:
1162
+
1163
+ ```bash
1164
+ voltro embeddings backfill docs --text body --vector embedding [--model text-embedding-3-small] [--batch 50] [--dry-run]
1165
+ ```
1166
+
1167
+ It selects rows whose vector column is empty (or, with `--model-field`/`--model`, embedded under a different model), embeds them in batches via `@voltro/ai`, and writes the vectors back. `--dry-run` reports what *would* be embedded without writing.
1168
+
1169
+ ## Hybrid search
1170
+
1171
+ For best results, combine vector similarity with full-text search (FTS):
1172
+
1173
+ ```ts
1174
+ import { hybridSearch } from '@voltro/database'
1175
+
1176
+ ctx.store.select('docs').use(hybridSearch({
1177
+ vector: { col: 'embedding', query: 'how to deploy' },
1178
+ fts: { indexName: 'docsBody', query: 'how to deploy' },
1179
+ alpha: 0.7, // 0=pure FTS, 1=pure vector
1180
+ })).limit(10).all()
1181
+ ```
1182
+
1183
+ The FTS clause narrows the candidate set (lexical recall); the vector clause ranks those candidates by embedding distance (semantic ordering). `alpha` weights how much the vector ranking dominates when the two rank signals are fused (Reciprocal Rank Fusion). Beats pure vector on most knowledge-base style retrievals.
1184
+
1185
+ ## Storage cost
1186
+
1187
+ A 1536-dim float32 vector is ~6 KB per row. With HNSW overhead it's roughly 9-10 KB.
1188
+
1189
+ - 10k docs → ~100 MB
1190
+ - 1M docs → ~10 GB
1191
+
1192
+ For very large corpora, downcast to half-precision — `vector(1536, { precision: 'half' })` emits pgvector's `HALFVEC(n)`, half the bytes, marginally lower recall. (MariaDB / MySQL have no half type — the emitter upcasts to float32 there.)
1193
+
1194
+ ## When NOT to use postgres / pgvector
1195
+
1196
+ - **Billions of vectors.** Postgres + pgvector tops out around 10-50M vectors with reasonable latency. Beyond that, Qdrant / Weaviate / Pinecone.
1197
+ - **Frequent re-embedding of entire corpus.** A separate vector DB is easier to wipe + rebuild than a column.
1198
+ - **Multi-tenant where each tenant has their own embedding model.** A vector column pins one dimensionality.
1199
+
1200
+ For most B2B SaaS, postgres + pgvector is more than enough + you get transactional + tenant-scoped + same-backup-line semantics for free.