@voltro/cli 0.52.0 → 0.54.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 (244) hide show
  1. package/CHANGELOG.md +424 -0
  2. package/THIRD-PARTY-NOTICES.md +8311 -3318
  3. package/dist/{agentsMd-0l980yhL.js → agentsMd-BaLC10Na.js} +110 -82
  4. package/dist/agentsMd-DCY1RSs8.js +2 -0
  5. package/dist/apiBuild-CeUN55uk.js +2 -0
  6. package/dist/{apiBuild-CSFI8QGq.js → apiBuild-DTWp0S_q.js} +11 -5
  7. package/dist/bin.js +1 -1
  8. package/dist/build-D4ygSbnV.js +843 -0
  9. package/dist/{checkCommand-COmqc2cB.js → checkCommand-Dg1G7Gwd.js} +6 -6
  10. package/dist/{checkCommand-2SbqzukH.js → checkCommand-L7DTlpIF.js} +1 -1
  11. package/dist/{cloudCmd-QUXh-b5w.js → cloudCmd-DzKcSYuy.js} +1 -1
  12. package/dist/{codegen-VF479Cnb.js → codegen-DSLM8Su9.js} +1 -1
  13. package/dist/codegen-DjgxEOnD.js +2 -0
  14. package/dist/codegenCommand-CG_Vx4lc.js +41 -0
  15. package/dist/{codemodRunner-r7J9lIa7.js → codemodRunner-Cd4xkC6u.js} +109 -11
  16. package/dist/{commands-Cc_nV8WI.js → commands-6Kzi92Np.js} +96 -73
  17. package/dist/{dashboardCommand-C-gKvwqh.js → dashboardCommand-Cq1PWvI1.js} +5 -5
  18. package/dist/{dataCommand-BgpBHnlB.js → dataCommand-DYzW8vkv.js} +3 -3
  19. package/dist/{dbCommand-sHedr-NJ.js → dbCommand-B4NWZtGL.js} +278 -237
  20. package/dist/dbCommand-CSFWs9ev.js +2 -0
  21. package/dist/{dev-CRHoCEiy.js → dev-CmuvUKRq.js} +2903 -2306
  22. package/dist/{dev--A3nsxA3.js → dev-cKUiZZsB.js} +1 -1
  23. package/dist/{doctorCommand-DtfJ3FA6.js → doctorCommand-DCiFVMtZ.js} +101 -69
  24. package/dist/doctorCommand-J3qu4E0Y.js +2 -0
  25. package/dist/{dormancyCommand-Drn7o0No.js → dormancyCommand-w1TrmgYP.js} +1 -1
  26. package/dist/{embeddingsCommand-Z-jO1fWN.js → embeddingsCommand-CMgPyRTr.js} +1 -1
  27. package/dist/{envCommand-D4gCrrTZ.js → envCommand-Cyynmcfa.js} +8 -8
  28. package/dist/{evolveCommand-CMROeKeA.js → evolveCommand-BwvQ8dVH.js} +2 -2
  29. package/dist/fileConventions-l-RIXbx8.js +36 -0
  30. package/dist/{fileTaxonomy-DvDUV9wq.js → fileTaxonomy-CbyMQYx_.js} +42 -42
  31. package/dist/fontPipeline-LxIHa1vo.js +2 -0
  32. package/dist/fontPipeline-Tsh8kZfA.js +152 -0
  33. package/dist/{frameworkTableAssembly-w-XnLa3q.js → frameworkTableAssembly-D7LJuALW.js} +5 -5
  34. package/dist/frameworkTableAssembly-IPD1pUnZ.js +2 -0
  35. package/dist/imagePipeline-B_GVJgm6.js +2 -0
  36. package/dist/imagePipeline-CBZmjT4i.js +127 -0
  37. package/dist/index.js +2 -2
  38. package/dist/{infoCommand-DXM868o_.js → infoCommand-DlYlUPqs.js} +1 -1
  39. package/dist/{inspect-CtL_xTbu.js → inspect-Bd8-9wsi.js} +1 -1
  40. package/dist/inspect-CuoDInfZ.js +2 -0
  41. package/dist/{inspectGateHint-BjnFubmH.js → inspectGateHint-4LxkNtrz.js} +1 -1
  42. package/dist/manifestBuild-C4-J1-m_.js +2 -0
  43. package/dist/{manifestBuild-CuU1VrSm.js → manifestBuild-Cqgsx2bM.js} +1 -1
  44. package/dist/{metaCommands-C6RFmF1r.js → metaCommands-x7RCi2AF.js} +9 -3
  45. package/dist/{migrate-D0F-eTlK.js → migrate-BK_Bbx-_.js} +2 -2
  46. package/dist/mobileCommand-D9O6iq3D.js +428 -0
  47. package/dist/mobileCommand-DAum7tsG.js +2 -0
  48. package/dist/{pageConvention-CzUiSbtU.js → pageConvention-CMpfDN6r.js} +1 -1
  49. package/dist/{privacyCommand-DGdopOI6.js → privacyCommand-BCa2OoZG.js} +1 -1
  50. package/dist/{probeCommand-C9gazU0H.js → probeCommand-_C0YU207.js} +83 -24
  51. package/dist/{projectScaffold-EzlErR4E.js → projectScaffold-CJfP-xbT.js} +1 -1
  52. package/dist/{projectScaffold-B4dmTlwT.js → projectScaffold-CSN0OzBV.js} +2 -2
  53. package/dist/renderModeScan-43yQ2opo.js +147 -0
  54. package/dist/{renderProfile-Ck32Fzxr.js → renderProfile-DvrhVJHa.js} +2 -2
  55. package/dist/{runtimeTrace-BPQyCmC5.js → runtimeTrace-CGWx1Q6l.js} +1 -1
  56. package/dist/{sdkgen-Se88ifTd.js → sdkgen-CDGHQUFj.js} +1 -1
  57. package/dist/serveCommand-BiPe8BJm.js +2 -0
  58. package/dist/{serveCommand-DkP3OT0W.js → serveCommand-Bje09q1v.js} +889 -825
  59. package/dist/serveEntry.js +1 -1
  60. package/dist/start-B0bnJgxI.js +3 -0
  61. package/dist/{start-jw89Xbqy.js → start-Clz-1BHB.js} +633 -455
  62. package/dist/startEntry.js +1 -1
  63. package/dist/{staticCommand-BwNEDlSU.js → staticCommand-ey0kYmOT.js} +1 -1
  64. package/dist/{subcommandNames-DpYs3DXr.js → subcommandNames-CDzfEtKV.js} +3 -3
  65. package/dist/{templates-BR-fb4SP.js → templates-BTWZkJJT.js} +41 -9
  66. package/dist/{test-f3amja6a.js → test-D_kW4KMj.js} +1 -1
  67. package/dist/updateCommand-CIoVDKnj.js +2 -0
  68. package/dist/{updateCommand-BMk2e4ky.js → updateCommand-CRJlAOaM.js} +139 -115
  69. package/dist/{webDev-BgWL9gKV.js → webDev-DSI9SOhs.js} +1598 -1028
  70. package/dist/webDev-DlvZO30c.js +2 -0
  71. package/dist/{webhooksCommand-CoIO3jbj.js → webhooksCommand-BvzXNHji.js} +1 -1
  72. package/dist/workspaceDeps-RKEkX92S.js +45 -0
  73. package/package.json +60 -19
  74. package/templates/AGENTS.core.md +2 -0
  75. package/templates/AGENTS.md +8 -4
  76. package/templates/agent-docs/_index.md +6 -4
  77. package/templates/agent-docs/_manifest.json +21 -5
  78. package/templates/agent-docs/ai.md +6 -6
  79. package/templates/agent-docs/authentication.md +73 -1
  80. package/templates/agent-docs/cli.md +6 -3
  81. package/templates/agent-docs/configuration.md +17 -0
  82. package/templates/agent-docs/data.md +522 -29
  83. package/templates/agent-docs/database/advancedqueries.md +8 -8
  84. package/templates/agent-docs/database/columntypes.md +2 -2
  85. package/templates/agent-docs/database/migrations.md +1 -1
  86. package/templates/agent-docs/database/querying.md +1 -1
  87. package/templates/agent-docs/database/schema.md +1 -1
  88. package/templates/agent-docs/database/seedsdialects.md +65 -3
  89. package/templates/agent-docs/database/transactions.md +3 -3
  90. package/templates/agent-docs/deployment.md +8 -0
  91. package/templates/agent-docs/internationalization.md +4 -2
  92. package/templates/agent-docs/introduction.md +32 -1
  93. package/templates/agent-docs/local-first-mobile.md +226 -30
  94. package/templates/agent-docs/observability.md +5 -1
  95. package/templates/agent-docs/plugins/atlassian.md +2 -2
  96. package/templates/agent-docs/plugins/audit.md +2 -2
  97. package/templates/agent-docs/plugins/auth.md +1 -1
  98. package/templates/agent-docs/plugins/billing.md +1 -1
  99. package/templates/agent-docs/plugins/cdc-out.md +8 -3
  100. package/templates/agent-docs/plugins/comments.md +164 -0
  101. package/templates/agent-docs/plugins/notifications.md +47 -4
  102. package/templates/agent-docs/plugins/presence.md +45 -3
  103. package/templates/agent-docs/plugins/prometheus.md +3 -1
  104. package/templates/agent-docs/plugins/queue.md +172 -0
  105. package/templates/agent-docs/plugins.md +17 -13
  106. package/templates/agent-docs/reference.md +35 -4
  107. package/templates/agent-docs/routing.md +585 -7
  108. package/templates/agent-docs/scheduling.md +1 -1
  109. package/templates/agent-docs/schema-driven-ui.md +226 -4
  110. package/templates/agent-docs/security.md +3 -3
  111. package/templates/agent-docs/templates/appshells.md +36 -4
  112. package/templates/agent-docs/whats-new.md +134 -66
  113. package/templates/apps/api-ai/package.json +6 -6
  114. package/templates/apps/api-auth/package.json +8 -8
  115. package/templates/apps/api-backend/package.json +7 -7
  116. package/templates/apps/api-backend-deactivation/package.json +7 -7
  117. package/templates/apps/api-backend-mail/package.json +8 -8
  118. package/templates/apps/api-backend-mariadb/package.json +9 -9
  119. package/templates/apps/api-backend-sqlite/package.json +8 -8
  120. package/templates/apps/api-backend-storage/package.json +8 -8
  121. package/templates/apps/api-cms/package.json +9 -9
  122. package/templates/apps/api-collab/README.md +3 -3
  123. package/templates/apps/api-collab/app.config.ts +1 -1
  124. package/templates/apps/api-collab/database/schema.ts +12 -8
  125. package/templates/apps/api-collab/mutations/documents.create.mutation.server.ts +1 -1
  126. package/templates/apps/api-collab/mutations/documents.setBody.mutation.server.ts +1 -1
  127. package/templates/apps/api-collab/mutations/documents.setBody.mutation.ts +1 -1
  128. package/templates/apps/api-collab/package.json +8 -8
  129. package/templates/apps/api-collab/template.json +1 -1
  130. package/templates/apps/api-collab/tests/documents.setBody.test.ts +10 -2
  131. package/templates/apps/api-data-advanced/package.json +8 -8
  132. package/templates/apps/api-durable/package.json +8 -8
  133. package/templates/apps/api-feature-flags/package.json +9 -9
  134. package/templates/apps/api-governance/package.json +8 -8
  135. package/templates/apps/api-kv/package.json +8 -8
  136. package/templates/apps/api-moderation/package.json +8 -8
  137. package/templates/apps/api-observability/package.json +8 -8
  138. package/templates/apps/api-ratelimit/package.json +8 -8
  139. package/templates/apps/api-rbac/package.json +8 -8
  140. package/templates/apps/api-rest/package.json +7 -7
  141. package/templates/apps/api-row-history/package.json +8 -8
  142. package/templates/apps/api-saas/package.json +11 -10
  143. package/templates/apps/api-saas-starter/package.json +10 -10
  144. package/templates/apps/api-search/package.json +8 -8
  145. package/templates/apps/api-status/package.json +8 -8
  146. package/templates/apps/api-webhooks/package.json +9 -9
  147. package/templates/apps/changelog/app.config.ts +26 -2
  148. package/templates/apps/changelog/content/releases/{0.1.0.mdx → v0-1-0.mdx} +0 -1
  149. package/templates/apps/changelog/content/releases/{0.2.0.mdx → v0-2-0.mdx} +0 -1
  150. package/templates/apps/changelog/package.json +9 -8
  151. package/templates/apps/changelog/src/collections/releases.collection.ts +48 -0
  152. package/templates/apps/changelog/src/globals.d.ts +1 -1
  153. package/templates/apps/changelog/src/locales/de.ts +1 -1
  154. package/templates/apps/changelog/src/locales/en.ts +1 -1
  155. package/templates/apps/changelog/src/pages/[locale]/[slug]/page.tsx +7 -4
  156. package/templates/apps/changelog/src/pages/[locale]/mirrors.test.tsx +13 -41
  157. package/templates/apps/changelog/src/pages/[slug]/page.test.tsx +26 -51
  158. package/templates/apps/changelog/src/pages/[slug]/page.tsx +21 -19
  159. package/templates/apps/changelog/src/pages/page.test.tsx +14 -37
  160. package/templates/apps/changelog/src/pages/page.tsx +18 -12
  161. package/templates/apps/edge-functions/package.json +2 -2
  162. package/templates/apps/frontend-admin/package.json +8 -8
  163. package/templates/apps/frontend-app/package.json +9 -9
  164. package/templates/apps/frontend-auth/package.json +8 -8
  165. package/templates/apps/frontend-blank/package.json +7 -7
  166. package/templates/apps/frontend-cms/package.json +9 -9
  167. package/templates/apps/frontend-collab/README.md +43 -24
  168. package/templates/apps/frontend-collab/app.config.ts +3 -3
  169. package/templates/apps/frontend-collab/package.json +14 -10
  170. package/templates/apps/frontend-collab/src/locales/de.ts +1 -2
  171. package/templates/apps/frontend-collab/src/locales/en.ts +1 -2
  172. package/templates/apps/frontend-collab/src/pages/page.test.tsx +72 -76
  173. package/templates/apps/frontend-collab/src/pages/page.tsx +45 -22
  174. package/templates/apps/frontend-collab/template.json +2 -2
  175. package/templates/apps/frontend-contact/package.json +7 -7
  176. package/templates/apps/frontend-dashboard/package.json +7 -7
  177. package/templates/apps/frontend-docs/content/docs/de/guides/first-page.md +4 -0
  178. package/templates/apps/frontend-docs/content/docs/de/intro/getting-started.md +4 -0
  179. package/templates/apps/frontend-docs/content/docs/en/guides/first-page.md +4 -0
  180. package/templates/apps/frontend-docs/content/docs/en/intro/getting-started.md +4 -0
  181. package/templates/apps/frontend-docs/package.json +9 -6
  182. package/templates/apps/frontend-docs/src/collections/docs.collection.ts +21 -0
  183. package/templates/apps/frontend-docs/src/locales/de.ts +0 -5
  184. package/templates/apps/frontend-docs/src/locales/en.ts +0 -5
  185. package/templates/apps/frontend-docs/src/pages/[locale]/docs/[...slug]/page.tsx +17 -9
  186. package/templates/apps/frontend-docs/src/pages/[locale]/mirrors.test.tsx +10 -5
  187. package/templates/apps/frontend-docs/src/pages/[locale]/page.tsx +8 -0
  188. package/templates/apps/frontend-docs/src/pages/docs/[...slug]/page.test.tsx +53 -15
  189. package/templates/apps/frontend-docs/src/pages/docs/[...slug]/page.tsx +27 -33
  190. package/templates/apps/frontend-docs/src/pages/page.test.tsx +17 -3
  191. package/templates/apps/frontend-docs/src/pages/page.tsx +16 -12
  192. package/templates/apps/frontend-i18n/package.json +6 -6
  193. package/templates/apps/frontend-landing/README.md +48 -0
  194. package/templates/apps/frontend-landing/app.config.ts +28 -0
  195. package/templates/apps/frontend-landing/package.json +7 -6
  196. package/templates/apps/frontend-landing/src/assets/hero.jpg +0 -0
  197. package/templates/apps/frontend-landing/src/fonts/Geist-Variable.woff2 +0 -0
  198. package/templates/apps/frontend-landing/src/fonts/LICENSE-Geist.txt +92 -0
  199. package/templates/apps/frontend-landing/src/globals.css +15 -0
  200. package/templates/apps/frontend-landing/src/globals.d.ts +17 -0
  201. package/templates/apps/frontend-landing/src/locales/de.ts +3 -2
  202. package/templates/apps/frontend-landing/src/locales/en.ts +5 -2
  203. package/templates/apps/frontend-landing/src/pages/page.test.tsx +79 -0
  204. package/templates/apps/frontend-landing/src/pages/page.tsx +26 -3
  205. package/templates/apps/frontend-landing/template.json +2 -2
  206. package/templates/apps/frontend-portal/package.json +8 -8
  207. package/templates/apps/frontend-saas/package.json +8 -8
  208. package/templates/apps/frontend-spa/package.json +7 -7
  209. package/templates/apps/frontend-ssr/package.json +7 -7
  210. package/templates/apps/frontend-ssr-api/package.json +8 -8
  211. package/templates/apps/frontend-static-blog/content/posts/cms-to-ssg.md +12 -0
  212. package/templates/apps/frontend-static-blog/content/posts/hello-static.md +12 -0
  213. package/templates/apps/frontend-static-blog/content/posts/islands-not-hydration.md +14 -0
  214. package/templates/apps/frontend-static-blog/package.json +9 -6
  215. package/templates/apps/frontend-static-blog/src/collections/posts.collection.ts +27 -0
  216. package/templates/apps/frontend-static-blog/src/pages/[locale]/blog/[slug]/page.tsx +7 -4
  217. package/templates/apps/frontend-static-blog/src/pages/[locale]/mirrors.test.tsx +7 -1
  218. package/templates/apps/frontend-static-blog/src/pages/blog/[slug]/page.test.tsx +19 -9
  219. package/templates/apps/frontend-static-blog/src/pages/blog/[slug]/page.tsx +19 -21
  220. package/templates/apps/frontend-static-blog/src/pages/page.test.tsx +18 -5
  221. package/templates/apps/frontend-static-blog/src/pages/page.tsx +21 -14
  222. package/templates/apps/frontend-status/package.json +8 -8
  223. package/templates/apps/mobile-app/package.json +12 -11
  224. package/templates/apps/mobile-app/src/lib/deeplinks.ts +29 -17
  225. package/templates/apps/mobile-app/tests/deeplinks.test.ts +16 -0
  226. package/dist/agentsMd-SDDSkyl4.js +0 -2
  227. package/dist/apiBuild-BYBpL7Pz.js +0 -2
  228. package/dist/build-CPgcMQug.js +0 -793
  229. package/dist/codegen-CctkDO-1.js +0 -2
  230. package/dist/codegenCommand-DCdG2JN-.js +0 -137
  231. package/dist/dbCommand-DNb6yeOG.js +0 -2
  232. package/dist/doctorCommand-CqoWA2p5.js +0 -2
  233. package/dist/fileConventions-DOqD3lPS.js +0 -34
  234. package/dist/frameworkTableAssembly-C6ETawPR.js +0 -2
  235. package/dist/inspect-CuGDYES0.js +0 -2
  236. package/dist/manifestBuild-CPjhvM62.js +0 -2
  237. package/dist/renderModeScan-CcH2X1_D.js +0 -120
  238. package/dist/serveCommand-DLc-BznW.js +0 -2
  239. package/dist/start-DfL3fOiN.js +0 -3
  240. package/dist/updateCommand-5gFVfK5q.js +0 -2
  241. package/dist/webDev-CZbTsDcH.js +0 -2
  242. package/templates/apps/changelog/scripts/generate-rss.mjs +0 -38
  243. package/templates/apps/changelog/src/lib/releases.ts +0 -21
  244. package/templates/apps/frontend-static-blog/src/content/posts.ts +0 -64
@@ -0,0 +1,2 @@
1
+ import { p as e, x as t, y as n } from "./webDev-DSI9SOhs.js";
2
+ export { e as loadConfig, n as tryRunWebServe, t as walkPagesTree };
@@ -222,7 +222,7 @@ createVerifier({ secret: [process.env.WEBHOOK_SECRET, process.env.WEBHOOK_SECRET
222
222
  ...t === void 0 ? {} : { payload: t }
223
223
  };
224
224
  }, S = u({ scope: "voltro:webhooks" }), C = ["--out", "--name"], w = async (e) => {
225
- let { walk: t, loadDiscovered: n } = await import("./dev--A3nsxA3.js"), { outgoingFromEvents: r } = await import("./webhookDiscovery-il9ti-HE.js");
225
+ let { walk: t, loadDiscovered: n } = await import("./dev-cKUiZZsB.js"), { outgoingFromEvents: r } = await import("./webhookDiscovery-il9ti-HE.js");
226
226
  return r((await n(await t(e))).events.map((e) => ({
227
227
  file: e.file,
228
228
  descriptor: e.descriptor
@@ -0,0 +1,45 @@
1
+ import { dirname as e, join as t } from "node:path";
2
+ import { promises as n } from "node:fs";
3
+ //#region src/workspaceDeps.ts
4
+ var r = async (r, i) => {
5
+ let a = r;
6
+ for (;;) {
7
+ let r = t(a, "node_modules", i);
8
+ try {
9
+ if ((await n.stat(r)).isDirectory()) {
10
+ let e = await n.realpath(r);
11
+ return e.includes("/node_modules/") ? null : e;
12
+ }
13
+ } catch {}
14
+ let o = e(a);
15
+ if (o === a) return null;
16
+ a = o;
17
+ }
18
+ }, i = async (e) => {
19
+ let i;
20
+ try {
21
+ let r = await n.readFile(t(e, "package.json"), "utf8"), a = JSON.parse(r);
22
+ i = Object.keys({
23
+ ...a.dependencies,
24
+ ...a.devDependencies
25
+ });
26
+ } catch {
27
+ return [];
28
+ }
29
+ return (await Promise.all(i.map(async (i) => {
30
+ let a = await r(e, i);
31
+ if (a === null) return null;
32
+ let o = t(a, "src");
33
+ try {
34
+ if (!(await n.stat(o)).isDirectory()) return null;
35
+ } catch {
36
+ return null;
37
+ }
38
+ return {
39
+ pkg: i,
40
+ dir: o
41
+ };
42
+ }))).filter((e) => e !== null);
43
+ };
44
+ //#endregion
45
+ export { i as n, r as t };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voltro/cli",
3
- "version": "0.52.0",
3
+ "version": "0.54.0",
4
4
  "description": "The `voltro` CLI — dev server, codegen, migrations, project scaffolding, agent-docs seeding, and production serve.",
5
5
  "keywords": [
6
6
  "voltro",
@@ -730,6 +730,30 @@
730
730
  "title": "A throwing `onHttpRequest` interceptor now refuses the request (500) instead of being ignored",
731
731
  "kind": "manual"
732
732
  },
733
+ {
734
+ "version": "0.53.0",
735
+ "id": "0.53.0/01_conflict-columns-take-the-table",
736
+ "title": "`missingConflictColumns` takes the table and classifies absent vs NULL",
737
+ "kind": "manual"
738
+ },
739
+ {
740
+ "version": "0.53.0",
741
+ "id": "0.53.0/02_empty-struct-input-flipped",
742
+ "title": "`input: Schema.Struct({})` procedures flipped from accept-everything to reject-everything in 0.37.0",
743
+ "kind": "manual"
744
+ },
745
+ {
746
+ "version": "0.53.0",
747
+ "id": "0.53.0/03_validation-messages-are-ids",
748
+ "title": "Form validation messages are ids + catalog now, not effect developer text",
749
+ "kind": "manual"
750
+ },
751
+ {
752
+ "version": "0.53.0",
753
+ "id": "0.53.0/04_widget-kind-gained-array",
754
+ "title": "WidgetKind gained 'array' and 'reference' — total widget registries need two new entries",
755
+ "kind": "manual"
756
+ },
733
757
  {
734
758
  "version": "0.6.0",
735
759
  "id": "0.6.0/01_no-dev-session-secret",
@@ -796,6 +820,13 @@
796
820
  "title": "A `renderMode:'spa'` page under a layout now server-renders that layout",
797
821
  "kind": "manual"
798
822
  }
823
+ ],
824
+ "renames": [
825
+ {
826
+ "at": "0.52.0",
827
+ "from": "@voltro/plugin-versioning",
828
+ "to": "@voltro/plugin-row-history"
829
+ }
799
830
  ]
800
831
  },
801
832
  "engines": {
@@ -805,35 +836,45 @@
805
836
  "@effect/platform-node": "^0.108.0",
806
837
  "@effect/sql": "^0.52.0",
807
838
  "@effect/workflow": "^0.19.0",
808
- "@voltro/ai": "0.52.0",
809
- "@voltro/cache": "0.52.0",
810
- "@voltro/client": "0.52.0",
811
- "@voltro/data-transfer": "0.52.0",
812
- "@voltro/database": "0.52.0",
813
- "@voltro/env": "0.52.0",
814
- "@voltro/kv": "0.52.0",
815
- "@voltro/logger": "0.52.0",
816
- "@voltro/plugin-auth": "0.52.0",
817
- "@voltro/plugin-broadcast": "0.52.0",
818
- "@voltro/plugin-mail": "0.52.0",
819
- "@voltro/plugin-storage": "0.52.0",
820
- "@voltro/plugin-webhooks": "0.52.0",
821
- "@voltro/protocol": "0.52.0",
822
- "@voltro/runtime": "0.52.0",
823
- "@voltro/serverless": "0.52.0",
824
- "@voltro/workflow": "0.52.0",
839
+ "@voltro/ai": "0.54.0",
840
+ "@voltro/cache": "0.54.0",
841
+ "@voltro/client": "0.54.0",
842
+ "@voltro/content": "0.54.0",
843
+ "@voltro/data-transfer": "0.54.0",
844
+ "@voltro/database": "0.54.0",
845
+ "@voltro/env": "0.54.0",
846
+ "@voltro/kv": "0.54.0",
847
+ "@voltro/logger": "0.54.0",
848
+ "@voltro/plugin-auth": "0.54.0",
849
+ "@voltro/plugin-broadcast": "0.54.0",
850
+ "@voltro/plugin-mail": "0.54.0",
851
+ "@voltro/plugin-storage": "0.54.0",
852
+ "@voltro/plugin-webhooks": "0.54.0",
853
+ "@voltro/protocol": "0.54.0",
854
+ "@voltro/runtime": "0.54.0",
855
+ "@voltro/serverless": "0.54.0",
856
+ "@voltro/workflow": "0.54.0",
825
857
  "chokidar": "^5.0.0",
826
858
  "ioredis": "^5.11.1",
827
859
  "tinyglobby": "^0.2.17",
828
860
  "ts-morph": "^28.0.0"
829
861
  },
830
862
  "optionalDependencies": {
863
+ "@resvg/resvg-js": "^2.6.2",
831
864
  "@tailwindcss/vite": "^4.3.3",
832
865
  "@vercel/nft": "^1.10.2",
833
866
  "@vitejs/plugin-react": "^6.0.5",
834
867
  "esbuild": "^0.28.2",
868
+ "fontkit": "^2.0.4",
869
+ "satori": "0.29.0",
870
+ "sharp": "^0.35.3",
871
+ "subset-font": "^2.5.0",
835
872
  "tsx": "^4.23.12",
836
- "vite": "^8.2.1"
873
+ "vite": "^8.2.1",
874
+ "@grpc/grpc-js": "^1.12.6",
875
+ "@grpc/reflection": "^1.0.4",
876
+ "grpc-health-check": "^2.0.2",
877
+ "@grpc/proto-loader": "^0.7.13"
837
878
  },
838
879
  "peerDependencies": {
839
880
  "@effect/platform": "^0.97.0",
@@ -368,6 +368,8 @@ public REST routes, declared via `restRoutes` in `app.config.ts`).
368
368
  | `*.agent.tsx` + `*.agent.server.tsx` | server-side LLM chat |
369
369
  | `*.tool.tsx` | tool an agent can call |
370
370
  | `*.entity.ts` / `*.schema.ts` / `schema.ts` | one table per file |
371
+ | `*.collection.ts` | content collection (`defineCollection`) — schema-typed markdown/JSON |
372
+ | `content/<name>/**` | that collection's files (markdown + frontmatter, or `.json`) |
371
373
  | `page.tsx` (under `src/pages/`) | the route its DIRECTORY serves + `page.test.tsx` |
372
374
  | `*.component.tsx` | exactly ONE component (+ types) |
373
375
  | `*.component.ui.tsx` | presentational: one component, READS only — never writes |
@@ -368,6 +368,8 @@ public REST routes, declared via `restRoutes` in `app.config.ts`).
368
368
  | `*.agent.tsx` + `*.agent.server.tsx` | server-side LLM chat |
369
369
  | `*.tool.tsx` | tool an agent can call |
370
370
  | `*.entity.ts` / `*.schema.ts` / `schema.ts` | one table per file |
371
+ | `*.collection.ts` | content collection (`defineCollection`) — schema-typed markdown/JSON |
372
+ | `content/<name>/**` | that collection's files (markdown + frontmatter, or `.json`) |
371
373
  | `page.tsx` (under `src/pages/`) | the route its DIRECTORY serves + `page.test.tsx` |
372
374
  | `*.component.tsx` | exactly ONE component (+ types) |
373
375
  | `*.component.ui.tsx` | presentational: one component, READS only — never writes |
@@ -728,7 +730,7 @@ each plugin's own README.
728
730
 
729
731
  | Topic | Open | Summary |
730
732
  |---|---|---|
731
- | **What's new in 0.52.0** | `node_modules/@voltro/cli/templates/agent-docs/whats-new.md` | Everything that changed in this version. Read it before hand-rolling something the framework may now ship. |
733
+ | **What's new in 0.54.0** | `node_modules/@voltro/cli/templates/agent-docs/whats-new.md` | Everything that changed in this version. Read it before hand-rolling something the framework may now ship. |
732
734
  | AI | `node_modules/@voltro/cli/templates/agent-docs/ai.md` | How Voltro treats AI — agents, tools, streaming, RAG — all primitives over the same WebSocket as the rest of the framework. |
733
735
  | Authentication | `node_modules/@voltro/cli/templates/agent-docs/authentication.md` | How @voltro/plugin-auth wires password + session-cookie auth across api + web, plus the pluggable identity-strategy protocol. |
734
736
  | Caching | `node_modules/@voltro/cli/templates/agent-docs/caching.md` | Voltro's caching layer (@voltro/cache) — an always-on memory default, swappable Redis-compatible backends, a low-level wrap primitive, and automatic query-result invalidation. |
@@ -786,8 +788,9 @@ each plugin's own README.
786
788
  | auth-workos | `node_modules/@voltro/cli/templates/agent-docs/plugins/auth-workos.md` (or `node_modules/@voltro/plugin-auth-workos/README.md`) | WorkOS AuthStrategy — verifies WorkOS AuthKit / SSO JWTs via JWKS (no API key) and maps org_id → tenantId. |
787
789
  | billing | `node_modules/@voltro/cli/templates/agent-docs/plugins/billing.md` (or `node_modules/@voltro/plugin-billing/README.md`) | Subscriptions, plans, entitlements, and usage metering over a pluggable provider (Stripe + mock). Money is integer minor units. |
788
790
  | broadcast | `node_modules/@voltro/cli/templates/agent-docs/plugins/broadcast.md` (or `node_modules/@voltro/plugin-broadcast/README.md`) | Cross-replica reactivity over a pub/sub bus (Redis / NATS) for non-postgres dialects — closes the single-instance gap so a write on one pod surfaces on another. |
789
- | cdc-out | `node_modules/@voltro/cli/templates/agent-docs/plugins/cdc-out.md` (or `node_modules/@voltro/plugin-cdc-out/README.md`) | Declaratively mirror table changes outward to external sinks (webhook, plus a CdcSink interface for custom sinks) through a durable outbox — ordered per pipe, at-least-once from enqueue, retried with backoff, dead-lettered. |
791
+ | cdc-out | `node_modules/@voltro/cli/templates/agent-docs/plugins/cdc-out.md` (or `node_modules/@voltro/plugin-cdc-out/README.md`) | Declaratively mirror table changes outward to external sinks (webhook, Kafka, plus a CdcSink interface for custom sinks) through a durable outbox — ordered per pipe, at-least-once from enqueue, retried with backoff, dead-lettered. |
790
792
  | clickhouse | `node_modules/@voltro/cli/templates/agent-docs/plugins/clickhouse.md` (or `node_modules/@voltro/plugin-clickhouse/README.md`) | Production-grade OLAP AnalyticsSink over ClickHouse — self-hosted or ClickHouse Cloud — for billions of events with millisecond aggregates. |
793
+ | comments | `node_modules/@voltro/cli/templates/agent-docs/plugins/comments.md` (or `node_modules/@voltro/plugin-comments/README.md`) | Comment threads on any app entity — replies, resolve/reopen, @-mentions with notifications, reactions, unread counters — live over the reactive engine, with an ejectable thread UI. |
791
794
  | datadog | `node_modules/@voltro/cli/templates/agent-docs/plugins/datadog.md` (or `node_modules/@voltro/plugin-datadog/README.md`) | Agentless Datadog metrics exporter — pushes the unified Metrics-API to Datadog's /api/v2/series HTTP intake. |
792
795
  | deactivation | `node_modules/@voltro/cli/templates/agent-docs/plugins/deactivation.md` (or `node_modules/@voltro/plugin-deactivation/README.md`) | A schema mixin that marks a subject as deactivated (can't log in) while keeping its data fully visible — the deliberate opposite of soft-delete. |
793
796
  | duckdb | `node_modules/@voltro/cli/templates/agent-docs/plugins/duckdb.md` (or `node_modules/@voltro/plugin-duckdb/README.md`) | Embedded DuckDB AnalyticsSink — real column-store OLAP in-process, no external service to run. |
@@ -798,12 +801,13 @@ each plugin's own README.
798
801
  | mail | `node_modules/@voltro/cli/templates/agent-docs/plugins/mail.md` (or `node_modules/@voltro/plugin-mail/README.md`) | Transactional email — Resend / Postmark / SendGrid / SMTP, React-Email templates with auto-discovery, per-tenant suppression, bounce/complaint handling, durable delivery via workflows. |
799
802
  | moderation | `node_modules/@voltro/cli/templates/agent-docs/plugins/moderation.md` (or `node_modules/@voltro/plugin-moderation/README.md`) | Moderate user content before it commits — keyword denylist or AI provider, block or flag via rpc interceptor, plus an in-handler redact helper. |
800
803
  | multitenancy | `node_modules/@voltro/cli/templates/agent-docs/plugins/multitenancy.md` (or `node_modules/@voltro/plugin-multitenancy/README.md`) | The tenant() schema mixin (auto-scoped reads, auto-filled inserts, tenant-resolved keyed writes), the assertOwnTenant write-guard, and the typed TenantMismatch error. |
801
- | notifications | `node_modules/@voltro/cli/templates/agent-docs/plugins/notifications.md` (or `node_modules/@voltro/plugin-notifications/README.md`) | Unified notifications — one send API across email / Slack / SMS / push / in-app, with per-user channel preferences, an in-app inbox, and delivery records. |
804
+ | notifications | `node_modules/@voltro/cli/templates/agent-docs/plugins/notifications.md` (or `node_modules/@voltro/plugin-notifications/README.md`) | Unified notifications — one send API across email / Slack / SMS / mobile push / web push / in-app, with per-user channel preferences, an in-app inbox, and delivery records. |
802
805
  | openapi | `node_modules/@voltro/cli/templates/agent-docs/plugins/openapi.md` (or `node_modules/@voltro/plugin-openapi/README.md`) | Generate an OpenAPI 3.1 spec + Swagger-UI docs from your defineRestRoute descriptors and (opt-in) your rpc procedures. Your routes ARE the API docs — nothing hand-maintained. |
803
806
  | postgis | `node_modules/@voltro/cli/templates/agent-docs/plugins/postgis.md` (or `node_modules/@voltro/plugin-postgis/README.md`) | Postgres-native geography / geometry columns, geometry constructors (point/line/polygon/multi/GeoJSON), spatial predicates (ST_DWithin, ST_Within, ST_Contains, ST_Intersects, ST_Buffer, bbox &&), ST_Distance projection + <-> KNN ordering, and GiST indexes via expressionIndex. Postgres-only by design. |
804
807
  | posthog | `node_modules/@voltro/cli/templates/agent-docs/plugins/posthog.md` (or `node_modules/@voltro/plugin-posthog/README.md`) | Track-only AnalyticsSink that forwards events to PostHog's /capture endpoint — compose it with a second sink that owns reads. |
805
- | presence | `node_modules/@voltro/cli/templates/agent-docs/plugins/presence.md` (or `node_modules/@voltro/plugin-presence/README.md`) | Ephemeral realtime presence — who's online in a channel, with heartbeat, live roster, and per-member metadata (status, cursor). Works cross-instance. |
808
+ | presence | `node_modules/@voltro/cli/templates/agent-docs/plugins/presence.md` (or `node_modules/@voltro/plugin-presence/README.md`) | Ephemeral realtime presence — who's online in a channel, with heartbeat, live roster, and per-member metadata (status, cursor — client-supplied and relayed verbatim; identity fields belong in the server-side resolveMember hook). Works cross-instance. |
806
809
  | prometheus | `node_modules/@voltro/cli/templates/agent-docs/plugins/prometheus.md` (or `node_modules/@voltro/plugin-prometheus/README.md`) | Prometheus exporter — scrapes the unified Metrics-API at GET /metrics in text exposition format. |
810
+ | queue | `node_modules/@voltro/cli/templates/agent-docs/plugins/queue.md` (or `node_modules/@voltro/plugin-queue/README.md`) | Consume and produce against an existing Kafka — Schema-decoded consumers (at-least-once, serial per partition, retry + dead-letter), batched producing via a handler service or transactionally through the outbox, and a kafkaSink for cdc-out. |
807
811
  | ratelimit | `node_modules/@voltro/cli/templates/agent-docs/plugins/ratelimit.md` (or `node_modules/@voltro/plugin-ratelimit/README.md`) | Per-endpoint, per-subject and per-tenant request limits via the rpc interceptors. Sliding-window / fixed-window / token-bucket, memory / postgres / redis stores. |
808
812
  | rbac | `node_modules/@voltro/cli/templates/agent-docs/plugins/rbac.md` (or `node_modules/@voltro/plugin-rbac/README.md`) | Roles + permissions + the permission() handler guard. Roles compile to scopes. |
809
813
  | row-history | `node_modules/@voltro/cli/templates/agent-docs/plugins/row-history.md` (or `node_modules/@voltro/plugin-row-history/README.md`) | Full row history + time-travel. audit() records who/when; row-history records what-changed-to-what — a value snapshot of every row on every write, with as-of queries. |
@@ -9,7 +9,7 @@ each plugin's own README.
9
9
 
10
10
  | Topic | Open | Summary |
11
11
  |---|---|---|
12
- | **What's new in 0.52.0** | `node_modules/@voltro/cli/templates/agent-docs/whats-new.md` | Everything that changed in this version. Read it before hand-rolling something the framework may now ship. |
12
+ | **What's new in 0.54.0** | `node_modules/@voltro/cli/templates/agent-docs/whats-new.md` | Everything that changed in this version. Read it before hand-rolling something the framework may now ship. |
13
13
  | AI | `node_modules/@voltro/cli/templates/agent-docs/ai.md` | How Voltro treats AI — agents, tools, streaming, RAG — all primitives over the same WebSocket as the rest of the framework. |
14
14
  | Authentication | `node_modules/@voltro/cli/templates/agent-docs/authentication.md` | How @voltro/plugin-auth wires password + session-cookie auth across api + web, plus the pluggable identity-strategy protocol. |
15
15
  | Caching | `node_modules/@voltro/cli/templates/agent-docs/caching.md` | Voltro's caching layer (@voltro/cache) — an always-on memory default, swappable Redis-compatible backends, a low-level wrap primitive, and automatic query-result invalidation. |
@@ -67,8 +67,9 @@ each plugin's own README.
67
67
  | auth-workos | `node_modules/@voltro/cli/templates/agent-docs/plugins/auth-workos.md` (or `node_modules/@voltro/plugin-auth-workos/README.md`) | WorkOS AuthStrategy — verifies WorkOS AuthKit / SSO JWTs via JWKS (no API key) and maps org_id → tenantId. |
68
68
  | billing | `node_modules/@voltro/cli/templates/agent-docs/plugins/billing.md` (or `node_modules/@voltro/plugin-billing/README.md`) | Subscriptions, plans, entitlements, and usage metering over a pluggable provider (Stripe + mock). Money is integer minor units. |
69
69
  | broadcast | `node_modules/@voltro/cli/templates/agent-docs/plugins/broadcast.md` (or `node_modules/@voltro/plugin-broadcast/README.md`) | Cross-replica reactivity over a pub/sub bus (Redis / NATS) for non-postgres dialects — closes the single-instance gap so a write on one pod surfaces on another. |
70
- | cdc-out | `node_modules/@voltro/cli/templates/agent-docs/plugins/cdc-out.md` (or `node_modules/@voltro/plugin-cdc-out/README.md`) | Declaratively mirror table changes outward to external sinks (webhook, plus a CdcSink interface for custom sinks) through a durable outbox — ordered per pipe, at-least-once from enqueue, retried with backoff, dead-lettered. |
70
+ | cdc-out | `node_modules/@voltro/cli/templates/agent-docs/plugins/cdc-out.md` (or `node_modules/@voltro/plugin-cdc-out/README.md`) | Declaratively mirror table changes outward to external sinks (webhook, Kafka, plus a CdcSink interface for custom sinks) through a durable outbox — ordered per pipe, at-least-once from enqueue, retried with backoff, dead-lettered. |
71
71
  | clickhouse | `node_modules/@voltro/cli/templates/agent-docs/plugins/clickhouse.md` (or `node_modules/@voltro/plugin-clickhouse/README.md`) | Production-grade OLAP AnalyticsSink over ClickHouse — self-hosted or ClickHouse Cloud — for billions of events with millisecond aggregates. |
72
+ | comments | `node_modules/@voltro/cli/templates/agent-docs/plugins/comments.md` (or `node_modules/@voltro/plugin-comments/README.md`) | Comment threads on any app entity — replies, resolve/reopen, @-mentions with notifications, reactions, unread counters — live over the reactive engine, with an ejectable thread UI. |
72
73
  | datadog | `node_modules/@voltro/cli/templates/agent-docs/plugins/datadog.md` (or `node_modules/@voltro/plugin-datadog/README.md`) | Agentless Datadog metrics exporter — pushes the unified Metrics-API to Datadog's /api/v2/series HTTP intake. |
73
74
  | deactivation | `node_modules/@voltro/cli/templates/agent-docs/plugins/deactivation.md` (or `node_modules/@voltro/plugin-deactivation/README.md`) | A schema mixin that marks a subject as deactivated (can't log in) while keeping its data fully visible — the deliberate opposite of soft-delete. |
74
75
  | duckdb | `node_modules/@voltro/cli/templates/agent-docs/plugins/duckdb.md` (or `node_modules/@voltro/plugin-duckdb/README.md`) | Embedded DuckDB AnalyticsSink — real column-store OLAP in-process, no external service to run. |
@@ -79,12 +80,13 @@ each plugin's own README.
79
80
  | mail | `node_modules/@voltro/cli/templates/agent-docs/plugins/mail.md` (or `node_modules/@voltro/plugin-mail/README.md`) | Transactional email — Resend / Postmark / SendGrid / SMTP, React-Email templates with auto-discovery, per-tenant suppression, bounce/complaint handling, durable delivery via workflows. |
80
81
  | moderation | `node_modules/@voltro/cli/templates/agent-docs/plugins/moderation.md` (or `node_modules/@voltro/plugin-moderation/README.md`) | Moderate user content before it commits — keyword denylist or AI provider, block or flag via rpc interceptor, plus an in-handler redact helper. |
81
82
  | multitenancy | `node_modules/@voltro/cli/templates/agent-docs/plugins/multitenancy.md` (or `node_modules/@voltro/plugin-multitenancy/README.md`) | The tenant() schema mixin (auto-scoped reads, auto-filled inserts, tenant-resolved keyed writes), the assertOwnTenant write-guard, and the typed TenantMismatch error. |
82
- | notifications | `node_modules/@voltro/cli/templates/agent-docs/plugins/notifications.md` (or `node_modules/@voltro/plugin-notifications/README.md`) | Unified notifications — one send API across email / Slack / SMS / push / in-app, with per-user channel preferences, an in-app inbox, and delivery records. |
83
+ | notifications | `node_modules/@voltro/cli/templates/agent-docs/plugins/notifications.md` (or `node_modules/@voltro/plugin-notifications/README.md`) | Unified notifications — one send API across email / Slack / SMS / mobile push / web push / in-app, with per-user channel preferences, an in-app inbox, and delivery records. |
83
84
  | openapi | `node_modules/@voltro/cli/templates/agent-docs/plugins/openapi.md` (or `node_modules/@voltro/plugin-openapi/README.md`) | Generate an OpenAPI 3.1 spec + Swagger-UI docs from your defineRestRoute descriptors and (opt-in) your rpc procedures. Your routes ARE the API docs — nothing hand-maintained. |
84
85
  | postgis | `node_modules/@voltro/cli/templates/agent-docs/plugins/postgis.md` (or `node_modules/@voltro/plugin-postgis/README.md`) | Postgres-native geography / geometry columns, geometry constructors (point/line/polygon/multi/GeoJSON), spatial predicates (ST_DWithin, ST_Within, ST_Contains, ST_Intersects, ST_Buffer, bbox &&), ST_Distance projection + <-> KNN ordering, and GiST indexes via expressionIndex. Postgres-only by design. |
85
86
  | posthog | `node_modules/@voltro/cli/templates/agent-docs/plugins/posthog.md` (or `node_modules/@voltro/plugin-posthog/README.md`) | Track-only AnalyticsSink that forwards events to PostHog's /capture endpoint — compose it with a second sink that owns reads. |
86
- | presence | `node_modules/@voltro/cli/templates/agent-docs/plugins/presence.md` (or `node_modules/@voltro/plugin-presence/README.md`) | Ephemeral realtime presence — who's online in a channel, with heartbeat, live roster, and per-member metadata (status, cursor). Works cross-instance. |
87
+ | presence | `node_modules/@voltro/cli/templates/agent-docs/plugins/presence.md` (or `node_modules/@voltro/plugin-presence/README.md`) | Ephemeral realtime presence — who's online in a channel, with heartbeat, live roster, and per-member metadata (status, cursor — client-supplied and relayed verbatim; identity fields belong in the server-side resolveMember hook). Works cross-instance. |
87
88
  | prometheus | `node_modules/@voltro/cli/templates/agent-docs/plugins/prometheus.md` (or `node_modules/@voltro/plugin-prometheus/README.md`) | Prometheus exporter — scrapes the unified Metrics-API at GET /metrics in text exposition format. |
89
+ | queue | `node_modules/@voltro/cli/templates/agent-docs/plugins/queue.md` (or `node_modules/@voltro/plugin-queue/README.md`) | Consume and produce against an existing Kafka — Schema-decoded consumers (at-least-once, serial per partition, retry + dead-letter), batched producing via a handler service or transactionally through the outbox, and a kafkaSink for cdc-out. |
88
90
  | ratelimit | `node_modules/@voltro/cli/templates/agent-docs/plugins/ratelimit.md` (or `node_modules/@voltro/plugin-ratelimit/README.md`) | Per-endpoint, per-subject and per-tenant request limits via the rpc interceptors. Sliding-window / fixed-window / token-bucket, memory / postgres / redis stores. |
89
91
  | rbac | `node_modules/@voltro/cli/templates/agent-docs/plugins/rbac.md` (or `node_modules/@voltro/plugin-rbac/README.md`) | Roles + permissions + the permission() handler guard. Roles compile to scopes. |
90
92
  | row-history | `node_modules/@voltro/cli/templates/agent-docs/plugins/row-history.md` (or `node_modules/@voltro/plugin-row-history/README.md`) | Full row history + time-travel. audit() records who/when; row-history records what-changed-to-what — a value snapshot of every row on every write, with as-of queries. |
@@ -53,7 +53,7 @@
53
53
  "group": null,
54
54
  "description": "How Voltro's reactive data layer works — queries, mutations, actions, streams, all over one WebSocket with typed errors and tracked dependencies.",
55
55
  "path": "agent-docs/data.md",
56
- "files": 19
56
+ "files": 21
57
57
  },
58
58
  {
59
59
  "id": "database/advancedqueries",
@@ -242,7 +242,7 @@
242
242
  "group": null,
243
243
  "description": "Voltro's file-based router — pages, layouts, render modes, loaders, navigation, islands. The web side of the framework.",
244
244
  "path": "agent-docs/routing.md",
245
- "files": 9
245
+ "files": 13
246
246
  },
247
247
  {
248
248
  "id": "scheduling",
@@ -460,7 +460,7 @@
460
460
  {
461
461
  "slug": "cdc-out",
462
462
  "title": "CDC-out (reverse-ETL)",
463
- "description": "Declaratively mirror table changes outward to external sinks (webhook, plus a CdcSink interface for custom sinks) through a durable outbox — ordered per pipe, at-least-once from enqueue, retried with backoff, dead-lettered.",
463
+ "description": "Declaratively mirror table changes outward to external sinks (webhook, Kafka, plus a CdcSink interface for custom sinks) through a durable outbox — ordered per pipe, at-least-once from enqueue, retried with backoff, dead-lettered.",
464
464
  "pkg": "@voltro/plugin-cdc-out",
465
465
  "doc": "plugins/cdc-out.md",
466
466
  "module": "agent-docs/plugins/cdc-out.md"
@@ -473,6 +473,14 @@
473
473
  "doc": "plugins/clickhouse.md",
474
474
  "module": "agent-docs/plugins/clickhouse.md"
475
475
  },
476
+ {
477
+ "slug": "comments",
478
+ "title": "Comments",
479
+ "description": "Comment threads on any app entity — replies, resolve/reopen, @-mentions with notifications, reactions, unread counters — live over the reactive engine, with an ejectable thread UI.",
480
+ "pkg": "@voltro/plugin-comments",
481
+ "doc": "plugins/comments.md",
482
+ "module": "agent-docs/plugins/comments.md"
483
+ },
476
484
  {
477
485
  "slug": "datadog",
478
486
  "title": "Datadog",
@@ -556,7 +564,7 @@
556
564
  {
557
565
  "slug": "notifications",
558
566
  "title": "Notifications",
559
- "description": "Unified notifications — one send API across email / Slack / SMS / push / in-app, with per-user channel preferences, an in-app inbox, and delivery records.",
567
+ "description": "Unified notifications — one send API across email / Slack / SMS / mobile push / web push / in-app, with per-user channel preferences, an in-app inbox, and delivery records.",
560
568
  "pkg": "@voltro/plugin-notifications",
561
569
  "doc": "plugins/notifications.md",
562
570
  "module": "agent-docs/plugins/notifications.md"
@@ -588,7 +596,7 @@
588
596
  {
589
597
  "slug": "presence",
590
598
  "title": "Presence",
591
- "description": "Ephemeral realtime presence — who's online in a channel, with heartbeat, live roster, and per-member metadata (status, cursor). Works cross-instance.",
599
+ "description": "Ephemeral realtime presence — who's online in a channel, with heartbeat, live roster, and per-member metadata (status, cursor — client-supplied and relayed verbatim; identity fields belong in the server-side resolveMember hook). Works cross-instance.",
592
600
  "pkg": "@voltro/plugin-presence",
593
601
  "doc": "plugins/presence.md",
594
602
  "module": "agent-docs/plugins/presence.md"
@@ -601,6 +609,14 @@
601
609
  "doc": "plugins/prometheus.md",
602
610
  "module": "agent-docs/plugins/prometheus.md"
603
611
  },
612
+ {
613
+ "slug": "queue",
614
+ "title": "Queue (Kafka interop)",
615
+ "description": "Consume and produce against an existing Kafka — Schema-decoded consumers (at-least-once, serial per partition, retry + dead-letter), batched producing via a handler service or transactionally through the outbox, and a kafkaSink for cdc-out.",
616
+ "pkg": "@voltro/plugin-queue",
617
+ "doc": "plugins/queue.md",
618
+ "module": "agent-docs/plugins/queue.md"
619
+ },
604
620
  {
605
621
  "slug": "ratelimit",
606
622
  "title": "Rate limiting",
@@ -116,7 +116,7 @@ Env vars `providerFromEnv()` reads:
116
116
  | `AI_PROVIDER` | `mock` | `mock` \| `anthropic` \| `openai` \| `gateway`. |
117
117
  | `AI_MODEL` | per provider (see below) | Override the model. Defaults: `claude-opus-4-8` (anthropic), `gpt-5.5` (openai), `mock` (mock). **REQUIRED for `gateway`** — a `creator/model` id; boot throws if unset. |
118
118
 
119
- Provider API keys are read by the underlying `@ai-sdk/*` packages from their standard env vars — `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, `AI_GATEWAY_API_KEY`. The framework doesn't read a separate `AI_API_KEY`. (To give a SINGLE agent its own key from code instead of env, see [Per-config key + base URL](#per-config-key--base-url) below.)
119
+ Provider API keys are read by the underlying `@ai-sdk/*` packages from their standard env vars — `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, `AI_GATEWAY_API_KEY`. The framework doesn't read a separate `AI_API_KEY`. (To give a SINGLE agent its own key from code instead of env, see [Per-config key + base URL](#per-config-key-base-url) below.)
120
120
 
121
121
  ## Anthropic
122
122
 
@@ -141,7 +141,7 @@ AI_MODEL=gpt-5.5
141
141
  OPENAI_API_KEY=sk-…
142
142
  ```
143
143
 
144
- Backed by `@ai-sdk/openai`. `AI_MODEL` is a plain OpenAI model id (`gpt-5.5`, `gpt-4o-mini`, …). For a self-hosted / Azure-style / proxy endpoint, set `baseURL` on a `ProviderConfig` (see [Per-config key + base URL](#per-config-key--base-url)) rather than an env var.
144
+ Backed by `@ai-sdk/openai`. `AI_MODEL` is a plain OpenAI model id (`gpt-5.5`, `gpt-4o-mini`, …). For a self-hosted / Azure-style / proxy endpoint, set `baseURL` on a `ProviderConfig` (see [Per-config key + base URL](#per-config-key-base-url)) rather than an env var.
145
145
 
146
146
  ## Vercel AI Gateway
147
147
 
@@ -184,7 +184,7 @@ const r = yield* generateText({
184
184
  })
185
185
  ```
186
186
 
187
- This is the mechanism behind **per-agent keys** and **BYOK** (bring-your-own-key): a `defineAgentExecutor` can carry a static `model: { name, model, apiKey }`, OR a `model: (input) => ({ …, apiKey: input.apiKey })` function that derives the key from the request. The key stays server-side (the executor file never reaches the browser) and is NOT persisted — only the prompt is stored in `agent_messages`. The full pattern (dynamic model picker, cost/tier routing, BYOK, and the security footguns) lives in [Agents → Per-agent model + key](/docs/ai/agents#per-agent-model--key).
187
+ This is the mechanism behind **per-agent keys** and **BYOK** (bring-your-own-key): a `defineAgentExecutor` can carry a static `model: { name, model, apiKey }`, OR a `model: (input) => ({ …, apiKey: input.apiKey })` function that derives the key from the request. The key stays server-side (the executor file never reaches the browser) and is NOT persisted — only the prompt is stored in `agent_messages`. The full pattern (dynamic model picker, cost/tier routing, BYOK, and the security footguns) lives in [Agents → Per-agent model + key](/docs/ai/agents#per-agent-model-key).
188
188
 
189
189
  ## Mock (for tests)
190
190
 
@@ -239,7 +239,7 @@ export default (input: { prompt: string }) =>
239
239
  })
240
240
  ```
241
241
 
242
- `GenerateTextOptions` is `{ prompt, system?, provider?, fallbacks?, maxTokens? }`. There is no `messages`/`effort` shape — the prompt is a single string the SDK wraps as the user turn; `system` steers it. `fallbacks` is a [provider fallback chain](#fallback-chain--survive-a-provider-outage).
242
+ `GenerateTextOptions` is `{ prompt, system?, provider?, fallbacks?, maxTokens? }`. There is no `messages`/`effort` shape — the prompt is a single string the SDK wraps as the user turn; `system` steers it. `fallbacks` is a [provider fallback chain](#fallback-chain-survive-a-provider-outage).
243
243
 
244
244
  Structured output:
245
245
 
@@ -276,7 +276,7 @@ type ProviderConfig =
276
276
  | { name: 'gateway'; model: `${string}/${string}`; apiKey?: string; baseURL?: string } // creator/model
277
277
  ```
278
278
 
279
- The mock-only `mockText` / `script` can't appear on a real provider (the type rejects it), the gateway's `model` is a `creator/model`-typed string (a bare `'gpt-5.5'` is a compile error, not a boot crash), and the per-provider model-id types (`AnthropicModel` / `OpenAIModel`) are **open unions** — known ids autocomplete, but any string the provider ships tomorrow still type-checks. Use `provider` for per-request model selection (e.g. a cheaper model on a fallback path), or to pin a specific key/endpoint (see [Per-config key + base URL](#per-config-key--base-url)).
279
+ The mock-only `mockText` / `script` can't appear on a real provider (the type rejects it), the gateway's `model` is a `creator/model`-typed string (a bare `'gpt-5.5'` is a compile error, not a boot crash), and the per-provider model-id types (`AnthropicModel` / `OpenAIModel`) are **open unions** — known ids autocomplete, but any string the provider ships tomorrow still type-checks. Use `provider` for per-request model selection (e.g. a cheaper model on a fallback path), or to pin a specific key/endpoint (see [Per-config key + base URL](#per-config-key-base-url)).
280
280
 
281
281
  For runtime provider switching across a whole layer, bind an `AiServiceImpl` to the `AiService` Context tag at boot and read it with `yield* AiService` — `defaultAiService` (backed by `providerFromEnv`) is the default.
282
282
 
@@ -648,7 +648,7 @@ const { data: messages } = useSubscription('app', 'chat.messages', { threadId })
648
648
  const send = useAction('app', 'chat.send')
649
649
  ```
650
650
 
651
- The `agent_messages` row carries `streaming` (live typewriter flag), `order` (thread position), `stepOrder` (sub-position within a turn, for LLM↔tool steps), and `parts` (the structured `text` + `tool` payload the feed renders). Both `agent_threads` and `agent_messages` are tenant-scoped — the active org id is stamped on every row. Full worked example in [Streaming](/docs/ai/streaming#streaming-deltas-through-a-reactive-query-persisted-no-raw-socket).
651
+ The `agent_messages` row carries `streaming` (live typewriter flag), `order` (thread position), `stepOrder` (sub-position within a turn, for LLM↔tool steps), and `parts` (the structured `text` + `tool` payload the feed renders). Both `agent_threads` and `agent_messages` are tenant-scoped — the active org id is stamped on every row. Full worked example in [Streaming](/docs/ai/streaming).
652
652
 
653
653
  ## Constraining responses (JSON, schema)
654
654
 
@@ -2027,7 +2027,7 @@ Multi-key rotation ships. Set the new secret as `VOLTRO_SESSION_SECRET` and move
2027
2027
  ## What the framework does NOT defend against
2028
2028
 
2029
2029
  - **Server-side bugs that leak the cookie value** (e.g. logging `req.headers.cookie` to an aggregator). Audit your logging pipeline.
2030
- - **Compromised user device.** A keylogger on a user's machine sees the password. TOTP/MFA enrolment ships today (see [HTTP handlers](/docs/authentication/handlers#mfa--totp-handlers-shipped)); passkeys ship too (phishing-resistant, no shared secret to keylog — see the [auth plugin overview](/docs/plugins/auth#passkeys--webauthn)). Ultimately devices have to be trusted.
2030
+ - **Compromised user device.** A keylogger on a user's machine sees the password. TOTP/MFA enrolment ships today (see [HTTP handlers](/docs/authentication/handlers#mfa-totp-handlers-shipped)); passkeys ship too (phishing-resistant, no shared secret to keylog — see the [auth plugin overview](/docs/plugins/auth#passkeys-webauthn)). Ultimately devices have to be trusted.
2031
2031
  - **Compromised provider.** If your secrets manager leaks `AUTH_SECRET`, every session is forgeable. Rotate immediately + invalidate every session.
2032
2032
  - **Phishing.** A user signing in on an attacker-controlled site → attacker has the cookie. Defence is browser-level (SSL pinning, password managers refusing to autofill on wrong domain). Passkeys help here directly: WebAuthn binds the credential to the origin, so an assertion produced on a phishing domain fails the origin/rpId check server-side.
2033
2033
 
@@ -2602,6 +2602,78 @@ A membership that ends mid-subscription therefore stops serving rows — the
2602
2602
  caller's open ticket list drops the rows they can no longer see, without a
2603
2603
  refresh and without the subscription having to be torn down.
2604
2604
 
2605
+ **On every transport, and that list is complete: the WebSocket, an [SSE
2606
+ stream](/docs/data/rest-routes#live-updates-over-http-stream-sse), and a
2607
+ [gRPC](/docs/data/grpc) server-streaming rpc.** All three open their
2608
+ subscription through the same code and resolve the filter per delivery, so
2609
+ "stays open for hours" never becomes a way to hold a stale predicate. The same
2610
+ delivery also re-checks the query's `guards:`; a filter resolution that FAILS
2611
+ revokes the subscription on all three rather than falling back to an unfiltered
2612
+ or empty read.
2613
+
2614
+ ## Declare which tables it narrows — `tables:`
2615
+
2616
+ Optional, one line, and it buys back a feature the filter otherwise switches
2617
+ off for the whole app:
2618
+
2619
+ ```ts
2620
+ setRowFilter({
2621
+ load,
2622
+ predicate,
2623
+ tables: ['bookmarks', 'recentSearches', 'todoSchedules', 'todoTags'],
2624
+ })
2625
+ ```
2626
+
2627
+ **What it buys.** [Delta-resume](/docs/data/wire-protocol#reconnect-delta-resume)
2628
+ is excluded for a subscription whose row set is re-resolved per delivery —
2629
+ replaying deltas could serve rows the subject has since lost. Without a
2630
+ declaration the framework cannot tell which tables your predicate may reach, so
2631
+ it excludes them all: one registration disables cheap reconnects for every
2632
+ subscription in the process. A deployment measured a filter over 4 tables
2633
+ costing it on all 173 of their query descriptors, 55 of whose source tables the
2634
+ filter never touches. With the declaration, only subscriptions on the listed
2635
+ tables are excluded.
2636
+
2637
+ **Why a declaration and not a probe.** Resolving the scope at subscribe time
2638
+ and treating "returns `undefined` for this table" as safe is cheaper and
2639
+ unsound: your predicate is a function of freshly loaded context, so a table it
2640
+ does not narrow now may be narrowed on the next delivery — which is the entire
2641
+ reason the filter is re-resolved per delivery. A static list is a promise about
2642
+ every future resolution.
2643
+
2644
+ **It is verified, not trusted.** Returning a predicate for a table outside the
2645
+ list raises `RowFilterDeclarationViolated` at the read that did it — the
2646
+ request fails and a subscription is revoked. A declaration nobody checks is a
2647
+ comment, and this one is load-bearing: the resume grant is issued on its
2648
+ strength. Omit `tables:` entirely and nothing is verified and nothing resumes —
2649
+ the conservative default.
2650
+
2651
+ ## Eager loads are refused, not silently unfiltered
2652
+
2653
+ A relation pulled in with `.with(...)` is resolved **below** the seam that
2654
+ applies the filter: the stores expand the eager tree themselves (the memory
2655
+ store recurses through its own raw read; the SQL stores fold the relation into
2656
+ one join). So a filtered table reached through a relation would come back
2657
+ unfiltered.
2658
+
2659
+ Rather than serve those rows, the read **refuses**, naming the table and both
2660
+ ways out:
2661
+
2662
+ ```
2663
+ row filter: 'readers' is reached through an eager load on 'notes', and an eager
2664
+ relation is resolved BELOW the row filter — its rows would come back
2665
+ unfiltered. Refusing the read rather than serving it.
2666
+ → read 'readers' as its own query (it is filtered there), or
2667
+ → drop 'readers' from this .with(...) if the relation does not need
2668
+ row-level narrowing.
2669
+ ```
2670
+
2671
+ Only relations reaching a table your filter actually narrows are affected —
2672
+ every other eager load is untouched. Applying the filter inside eager
2673
+ compilation is the real fix and it is a per-dialect change; until then this is
2674
+ a refusal rather than a leak, for the same reason the module refuses to
2675
+ fail open.
2676
+
2605
2677
  ## Row filters vs. guards
2606
2678
 
2607
2679
  They answer different questions, and a complete policy usually wants both:
@@ -29,9 +29,10 @@ The dispatcher routes `voltro <command> [args]` to the matching subcommand and p
29
29
  | [Data transfer](/docs/cli/data) | `data` (`export` / `import` / `unpack` / `inspect` / `backup` / `restore`) — directory + single-file `.vbundle` bundles, streaming assets, masking, at-rest encryption |
30
30
  | Ops / infra | `cache` (`status` / `flush` / `invalidate`), `add` (`redis`), `baseline` (`list` / `status` / `set`), `schedule` (`run <name>` — fire a registered schedule on demand; `--process` / `--trigger manual\|external` / `--url`), `schedule-manifest`, [`storage`](/docs/plugins/storage) (`doctor` / `cors`) |
31
31
  | AI / data | `embeddings backfill <table> --text <field> --vector <field>` — (re)embed rows the `vectorEmbedding()` mixin missed (pre-existing rows / a model change); `--dry-run` to preview; [`eval`](/docs/ai/agents#evaluating-recorded-runs-voltro-eval) — replay recorded agent runs against golden cases + judge, exit 1 on regression (a deploy gate; `--json` / `--branch` / `--threshold`) |
32
- | Integrate | [`webhooks`](/docs/plugins/webhooks#voltro-webhooks-consumer--the-package-your-subscribers-install) (`consumer` / `events`) — generate the ZERO-dependency Standard-Webhooks verification package your subscribers install, from your own declared events (`--out` / `--name`); list the events a subscriber can register for (`--json`) |
32
+ | Integrate | [`webhooks`](/docs/plugins/webhooks#voltro-webhooks-consumer-the-package-your-subscribers-install) (`consumer` / `events`) — generate the ZERO-dependency Standard-Webhooks verification package your subscribers install, from your own declared events (`--out` / `--name`); list the events a subscriber can register for (`--json`) |
33
33
  | [Inspect & debug](/docs/cli/inspect) | `inspect`, `logs`, `traces`, `workflows`, `cluster`, `check` |
34
34
  | [Health & surface](/docs/cli/build-and-start) | [`doctor`](/docs/cli/build-and-start) — serve preflight + the hand-roll detector (names the shipped primitive at the spot you're rebuilding it); [`capabilities`](/docs/cli/build-and-start) (`--json`) — the export surface read from your installed `@voltro/*`, so it can be verified instead of recalled; `info` (`--json`) — CLI / node / package-manager / dialect + every installed `@voltro/*` version, flagging lockstep skew (exits 1 on skew) |
35
+ | Mobile | `mobile` (`codegen` / `links`) — the Expo app's build-time steps: the typed rpc client + deep-link table, and the `apple-app-site-association` / `assetlinks.json` a universal link needs. `voltro codegen` inside a mobile app runs the same generators. |
35
36
  | Harness | `test`, `e2e` |
36
37
  | Cloud | `cloud` (`login` / `whoami` / `projects` / `env` / `import`); `login` is a top-level alias of `cloud login` |
37
38
  | Secrets | `secret` (`generate [purpose]` — the right var+format per secret; `generate` alone → a generic secret; `list`) |
@@ -143,11 +144,13 @@ config value always winning:
143
144
  | `VOLTRO_HSTS` | `Strict-Transport-Security` value. `off` drops just this one. |
144
145
  | `VOLTRO_MAX_RPC_BODY_BYTES` | Cap on the buffered `POST /rpc` JSON body (default 8 MiB) — an oversized body is refused `413` and never buffered past the cap. File uploads ride plugin routes with their own limits. |
145
146
  | `VOLTRO_MAX_BODY_BYTES` | Cap on every OTHER body read — plugin HTTP routes, REST routes, incoming webhooks (default 8 MiB, matching the rpc cap). The config-file spelling is `http.maxBodyBytes` in `app.config.ts`; per-route overrides (`defineRestRoute({ maxBodyBytes })`, a webhook handler's `maxBodyBytes`) win over both. Oversize is `413` for `Content-Length` and chunked alike. |
147
+ | `VOLTRO_CRDT_COMPACT_MAX_BYTES` | Size above which a merged `crdtText()` / `crdtDoc()` blob is soft-compacted (default 512 KiB, `0` disables). The config-file spelling is `crdt.compactMaxBytes` in `app.config.ts`; this variable wins over it. See [local-first](/docs/local-first/overview#rich-text-crdtdoc-usecrdtdoc-usecrdteditor). |
148
+ | `VOLTRO_GRPC_DRAIN_MS` | How long the [gRPC surface](/docs/data/grpc) lets open calls finish on shutdown before force-closing them (default 5000, `0` forces immediately). The config-file spelling is `grpc.drainMs`; this variable wins over it. Keep it below your orchestrator's termination grace. |
146
149
 
147
150
  Response compression for the buffered non-rpc surfaces (and `voltro start`'s
148
151
  HTML) is configured in the same `http:` block — `http.compression.{enabled,minBytes}`
149
152
  (default on, 1 KiB threshold; `POST /rpc` is never compressed). Details + the
150
- BREACH reasoning: [Security → compression](/docs/security/overview#response-compression--and-where-breach-sits).
153
+ BREACH reasoning: [Security → compression](/docs/security/overview#response-compression-and-where-breach-sits).
151
154
 
152
155
  Generate any of the secret vars above with `voltro secret generate <purpose>` (see [`secret`](#command-quick-reference)) — it picks the right length and format. A lower environment's secrets must always differ from production's.
153
156
 
@@ -2031,7 +2034,7 @@ package's README.
2031
2034
 
2032
2035
  _voltro migrate — apply the declared schema through the declarative differ (an alias of voltro db apply)._
2033
2036
 
2034
- `voltro migrate` applies your declared schema (`*.entity.ts` / `*.schema.ts` / `schema.ts`) to the configured database. It is an **alias of [`voltro db apply`](#the-declarative-workflow)**: it diffs the declared schema against the live database and emits the ALTERs, so a changed column or a new index actually lands.
2037
+ `voltro migrate` applies your declared schema (`*.entity.ts` / `*.schema.ts` / `schema.ts`) to the configured database. It is an **alias of [`voltro db apply`](#the-declarative-diff-workflow-voltro-db)**: it diffs the declared schema against the live database and emits the ALTERs, so a changed column or a new index actually lands.
2035
2038
 
2036
2039
  > Before 0.11.4 this command was a create-only apply (`CREATE TABLE IF NOT EXISTS`, no diffing), which meant a column or type change reported success having applied **nothing**. If you need that bootstrap-only behaviour for a brand-new database, it is now `voltro migrate --create-only`.
2037
2040
 
@@ -126,6 +126,11 @@ just as secret and just as required, but inventing one produces a value that
126
126
  merely *looks* right and authenticates nobody — better that the boot gate fails
127
127
  and a human fetches the real one.
128
128
 
129
+ Encodings: `base64url` and `hex` mint 32 random bytes; `p256` mints a valid
130
+ P-256 private scalar (what web push's `VOLTRO_VAPID_PRIVATE_KEY` needs — a
131
+ plugin can declare its own mintables the same way, which is how a configured
132
+ `webPushChannel()` gets its key without you declaring anything).
133
+
129
134
  Minting is **development only**. `voltro serve`, `build` and `start` never mint:
130
135
  in production a missing secret is a boot failure, which is the whole point.
131
136
  Generate deployment values with `voltro secret generate <purpose>`.
@@ -302,6 +307,18 @@ This updates what code that reads a secret **per use** sees: outbound API keys r
302
307
  `setSecretsBackend(backend)` installs a backend for a test; `resetSecretsBackend()` restores the env default. `resolveSecretsBackend(config)` is the pure resolver the boot path uses to turn the `secrets` config value into a backend.
303
308
 
304
309
 
310
+ ## `VOLTRO_OG_SECRET` — conditional, multi-replica only
311
+
312
+ The signed on-demand OG-image route (ssr pages exporting
313
+ [`ogImage`](/docs/routing/loaders-and-meta#og-images-from-a-template-ogimage))
314
+ signs its URLs with `VOLTRO_OG_SECRET`. The requirement is CONDITIONAL: a
315
+ single process mints a per-boot secret and is self-consistent — only a
316
+ multi-replica deploy needs the env var (the signing replica and the fetching
317
+ replica differ), and a deploy boot with ssr `ogImage` pages and no secret
318
+ refuses loudly rather than serving images that 403 on every other replica.
319
+ Any long random value; same on every replica; never a default.
320
+
321
+
305
322
 
306
323
  ---
307
324