@voltro/cli 0.39.0 → 0.40.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 (106) hide show
  1. package/CHANGELOG.md +210 -0
  2. package/dist/agentsMd-Bu_XQgVf.js +2 -0
  3. package/dist/{agentsMd-7zI2h5l9.js → agentsMd-D6yD7IQv.js} +31 -14
  4. package/dist/apiBuild-BIyGkym8.js +2 -0
  5. package/dist/{apiBuild-DGalUk9v.js → apiBuild-C8xBEYbL.js} +2 -2
  6. package/dist/bin.js +1 -1
  7. package/dist/{build-CfF6t0UM.js → build-CkhFGnKA.js} +168 -165
  8. package/dist/{checkCommand-CBtynBKW.js → checkCommand-BcXJa2IS.js} +3 -3
  9. package/dist/{checkCommand-BV56Pcc1.js → checkCommand-QbHshOac.js} +1 -1
  10. package/dist/{cloudCmd-C42gaO8s.js → cloudCmd-F4YJeqM3.js} +1 -1
  11. package/dist/{codegenCommand-W7SDdiAQ.js → codegenCommand-DF2OJh3O.js} +2 -2
  12. package/dist/{codemodRunner-D-jTyvWo.js → codemodRunner-BcOvi4A2.js} +103 -0
  13. package/dist/{commands-BD9eBRY3.js → commands-7Z7-HOE3.js} +30 -30
  14. package/dist/{dashboardCommand-7qGylm0F.js → dashboardCommand-CUycH6dR.js} +5 -5
  15. package/dist/{dataCommand-C_F2DYxe.js → dataCommand-By78lscK.js} +1 -1
  16. package/dist/{dev-alhkKoEX.js → dev-D8JSKlBM.js} +1 -1
  17. package/dist/{dev-gqpnzVhI.js → dev-Dh1NuYx1.js} +1569 -1569
  18. package/dist/{doctorCommand-Bvs-BQrM.js → doctorCommand-C-8-Z9D6.js} +55 -42
  19. package/dist/doctorCommand-FhQ7EVYL.js +2 -0
  20. package/dist/{dormancyCommand-B-PPHc9Q.js → dormancyCommand-A79Odglm.js} +1 -1
  21. package/dist/{embeddingsCommand-OKY6XjUf.js → embeddingsCommand-CoiyHfkY.js} +1 -1
  22. package/dist/{envCommand-B1-Zm85H.js → envCommand-B6d0s-jZ.js} +8 -8
  23. package/dist/{evolveCommand-C4-NlFbd.js → evolveCommand-BC5a9lxX.js} +2 -2
  24. package/dist/frameworkTableAssembly-DgylYh9_.js +2 -0
  25. package/dist/index.js +1 -1
  26. package/dist/{infoCommand-CTo8Jnhq.js → infoCommand-ZqSVzJZ4.js} +1 -1
  27. package/dist/{metaCommands-DUYR--Ts.js → metaCommands-CfRLra0s.js} +1 -1
  28. package/dist/{projectScaffold-BEhhHjPr.js → projectScaffold-BIl97_E6.js} +1 -1
  29. package/dist/{projectScaffold-BvhLOrLq.js → projectScaffold-mIX_DpSe.js} +1 -1
  30. package/dist/{runtimeTrace-DzZOCd94.js → runtimeTrace-BslKJjDY.js} +1 -1
  31. package/dist/{sdkgen-C4roLErM.js → sdkgen-DQ8uEmmn.js} +1 -1
  32. package/dist/serveCommand-BmvEQCMS.js +2 -0
  33. package/dist/{serveCommand-B6TATyCj.js → serveCommand-Dqb9Ku1-.js} +657 -657
  34. package/dist/serveEntry.js +1 -1
  35. package/dist/{start-BNuTWdRd.js → start-BA7txHhY.js} +1 -1
  36. package/dist/{start-ft_KzFTd.js → start-dkNYKtgQ.js} +342 -318
  37. package/dist/startEntry.js +1 -1
  38. package/dist/updateCommand-BauSVxPp.js +2 -0
  39. package/dist/{updateCommand-CHBmCB17.js → updateCommand-eoGpZr3E.js} +1 -1
  40. package/dist/webDev-CBbKs4dZ.js +2 -0
  41. package/dist/{webDev-CTpSY-e_.js → webDev-bybjZA0o.js} +240 -186
  42. package/dist/{webhooksCommand-BVvOtZ1B.js → webhooksCommand-DubZWNOx.js} +1 -1
  43. package/package.json +29 -17
  44. package/templates/AGENTS.md +1 -1
  45. package/templates/agent-docs/_index.md +1 -1
  46. package/templates/agent-docs/authentication.md +13 -4
  47. package/templates/agent-docs/data.md +43 -0
  48. package/templates/agent-docs/routing.md +40 -0
  49. package/templates/agent-docs/whats-new.md +91 -157
  50. package/templates/apps/api-ai/package.json +7 -7
  51. package/templates/apps/api-auth/package.json +8 -8
  52. package/templates/apps/api-backend/package.json +7 -7
  53. package/templates/apps/api-backend-deactivation/package.json +7 -7
  54. package/templates/apps/api-backend-mail/package.json +8 -8
  55. package/templates/apps/api-backend-mariadb/package.json +9 -9
  56. package/templates/apps/api-backend-sqlite/package.json +8 -8
  57. package/templates/apps/api-backend-storage/package.json +8 -8
  58. package/templates/apps/api-cms/package.json +10 -10
  59. package/templates/apps/api-collab/package.json +8 -8
  60. package/templates/apps/api-data-advanced/package.json +8 -8
  61. package/templates/apps/api-durable/package.json +8 -8
  62. package/templates/apps/api-durable/startup/warm.startup.tsx +16 -2
  63. package/templates/apps/api-durable/tests/orders.place.test.ts +17 -4
  64. package/templates/apps/api-feature-flags/package.json +9 -9
  65. package/templates/apps/api-governance/package.json +8 -8
  66. package/templates/apps/api-kv/package.json +8 -8
  67. package/templates/apps/api-moderation/package.json +8 -8
  68. package/templates/apps/api-observability/package.json +8 -8
  69. package/templates/apps/api-ratelimit/package.json +8 -8
  70. package/templates/apps/api-rbac/package.json +8 -8
  71. package/templates/apps/api-rest/package.json +7 -7
  72. package/templates/apps/api-saas/package.json +11 -11
  73. package/templates/apps/api-saas-starter/package.json +10 -10
  74. package/templates/apps/api-search/package.json +8 -8
  75. package/templates/apps/api-status/package.json +8 -8
  76. package/templates/apps/api-versioning/package.json +8 -8
  77. package/templates/apps/api-webhooks/package.json +9 -9
  78. package/templates/apps/changelog/package.json +6 -6
  79. package/templates/apps/changelog/src/pages/[slug]/page.test.tsx +8 -1
  80. package/templates/apps/edge-functions/package.json +2 -2
  81. package/templates/apps/frontend-admin/package.json +8 -8
  82. package/templates/apps/frontend-app/package.json +9 -9
  83. package/templates/apps/frontend-auth/package.json +8 -8
  84. package/templates/apps/frontend-blank/package.json +7 -7
  85. package/templates/apps/frontend-cms/package.json +9 -9
  86. package/templates/apps/frontend-collab/package.json +10 -10
  87. package/templates/apps/frontend-contact/package.json +7 -7
  88. package/templates/apps/frontend-dashboard/package.json +7 -7
  89. package/templates/apps/frontend-docs/package.json +7 -7
  90. package/templates/apps/frontend-i18n/package.json +6 -6
  91. package/templates/apps/frontend-landing/package.json +7 -7
  92. package/templates/apps/frontend-portal/package.json +8 -8
  93. package/templates/apps/frontend-saas/package.json +8 -8
  94. package/templates/apps/frontend-spa/package.json +7 -7
  95. package/templates/apps/frontend-ssr/package.json +7 -7
  96. package/templates/apps/frontend-ssr-api/package.json +8 -8
  97. package/templates/apps/frontend-static-blog/package.json +6 -6
  98. package/templates/apps/frontend-status/package.json +8 -8
  99. package/templates/apps/mobile-app/package.json +4 -4
  100. package/dist/agentsMd-BFCXh2gl.js +0 -2
  101. package/dist/apiBuild-2GvK8CUB.js +0 -2
  102. package/dist/doctorCommand-CVXfRrng.js +0 -2
  103. package/dist/frameworkTableAssembly-YyVe32Cb.js +0 -2
  104. package/dist/serveCommand-CNR0gI9V.js +0 -2
  105. package/dist/updateCommand-6FMU2klq.js +0 -2
  106. package/dist/webDev-id3I5PvG.js +0 -2
@@ -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-alhkKoEX.js"), { outgoingFromEvents: r } = await import("./webhookDiscovery-il9ti-HE.js");
225
+ let { walk: t, loadDiscovered: n } = await import("./dev-D8JSKlBM.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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voltro/cli",
3
- "version": "0.39.0",
3
+ "version": "0.40.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",
@@ -610,6 +610,18 @@
610
610
  "title": "@voltro/plugin-rbac/web removed — the UI gate lives in @voltro/client",
611
611
  "kind": "transform"
612
612
  },
613
+ {
614
+ "version": "0.40.0",
615
+ "id": "0.40.0/01_cold-start-failure-is-its-own-state",
616
+ "title": "a failed cold start is `failed: true`, not `loading: true`",
617
+ "kind": "manual"
618
+ },
619
+ {
620
+ "version": "0.40.0",
621
+ "id": "0.40.0/02_rejected-credential-widens-guard-results",
622
+ "title": "a rejected credential answers `Unauthenticated`, widening three guard results",
623
+ "kind": "manual"
624
+ },
613
625
  {
614
626
  "version": "0.5.0",
615
627
  "id": "0.5.0/01_one-terminal-exactly-one",
@@ -697,22 +709,22 @@
697
709
  "@effect/platform-node": "^0.108.0",
698
710
  "@effect/sql": "^0.52.0",
699
711
  "@effect/workflow": "^0.19.0",
700
- "@voltro/ai": "0.39.0",
701
- "@voltro/cache": "0.39.0",
702
- "@voltro/data-transfer": "0.39.0",
703
- "@voltro/database": "0.39.0",
704
- "@voltro/env": "0.39.0",
705
- "@voltro/kv": "0.39.0",
706
- "@voltro/logger": "0.39.0",
707
- "@voltro/plugin-auth": "0.39.0",
708
- "@voltro/plugin-broadcast": "0.39.0",
709
- "@voltro/plugin-mail": "0.39.0",
710
- "@voltro/plugin-storage": "0.39.0",
711
- "@voltro/plugin-webhooks": "0.39.0",
712
- "@voltro/protocol": "0.39.0",
713
- "@voltro/runtime": "0.39.0",
714
- "@voltro/serverless": "0.39.0",
715
- "@voltro/workflow": "0.39.0",
712
+ "@voltro/ai": "0.40.0",
713
+ "@voltro/cache": "0.40.0",
714
+ "@voltro/data-transfer": "0.40.0",
715
+ "@voltro/database": "0.40.0",
716
+ "@voltro/env": "0.40.0",
717
+ "@voltro/kv": "0.40.0",
718
+ "@voltro/logger": "0.40.0",
719
+ "@voltro/plugin-auth": "0.40.0",
720
+ "@voltro/plugin-broadcast": "0.40.0",
721
+ "@voltro/plugin-mail": "0.40.0",
722
+ "@voltro/plugin-storage": "0.40.0",
723
+ "@voltro/plugin-webhooks": "0.40.0",
724
+ "@voltro/protocol": "0.40.0",
725
+ "@voltro/runtime": "0.40.0",
726
+ "@voltro/serverless": "0.40.0",
727
+ "@voltro/workflow": "0.40.0",
716
728
  "chokidar": "^5.0.0",
717
729
  "ioredis": "^5.11.1",
718
730
  "tinyglobby": "^0.2.17",
@@ -707,7 +707,7 @@ each plugin's own README.
707
707
 
708
708
  | Topic | Open | Summary |
709
709
  |---|---|---|
710
- | **What's new in 0.39.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. |
710
+ | **What's new in 0.40.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. |
711
711
  | 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. |
712
712
  | 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. |
713
713
  | 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. |
@@ -9,7 +9,7 @@ each plugin's own README.
9
9
 
10
10
  | Topic | Open | Summary |
11
11
  |---|---|---|
12
- | **What's new in 0.39.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.40.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. |
@@ -2143,10 +2143,19 @@ setTupleSource((req) =>
2143
2143
  loadResourceTuples(store, req.subjectId, req.resourceType, req.resourceId))
2144
2144
  ```
2145
2145
 
2146
- `voltro dev` / `voltro serve` register exactly this default for you, reading
2147
- `_voltro_rebac_tuples`. Register your own when your relationships already live
2148
- in your own tables a `teamMembers` row is a relation; you should not have to
2149
- copy it into a framework table to authorize against it.
2146
+ `voltro dev` / `voltro serve` register this default for you, reading
2147
+ `_voltro_rebac_tuples` **but only if you registered nothing**. Register your
2148
+ own when your relationships already live in your own tables: a `teamMembers` row
2149
+ is a relation; you should not have to copy it into a framework table to authorize
2150
+ against it. Yours wins, wherever you register it (`app.config.ts` or a
2151
+ `*.startup.tsx`), and the boot says so:
2152
+
2153
+ ```
2154
+ policy guards: using the app-registered tuple source
2155
+ ```
2156
+
2157
+ That line is worth knowing, because its absence is the diagnosis when your own
2158
+ source is not being consulted.
2150
2159
 
2151
2160
  **Every unanswerable case denies.** No tuple source registered, no policy for
2152
2161
  that `resourceType`, an input that doesn't identify a resource, a tuple source
@@ -1728,6 +1728,33 @@ const { data } = useSubscription(
1728
1728
 
1729
1729
  While skipped, no WebSocket subscription opens and `data` stays `undefined`.
1730
1730
 
1731
+ ### A failed cold start is `failed`, not `loading`
1732
+
1733
+ If the first snapshot never arrives because the subscription ERRORED, the state
1734
+ is **`failed: true`, `loading: false`, `data: undefined`** — nothing is in
1735
+ flight and nothing more is coming, so "it is loading" would be a false
1736
+ statement. `error` carries the cause.
1737
+
1738
+ ```tsx
1739
+ const s = useSubscription<Team[]>('app', 'teams.list')
1740
+
1741
+ if (s.loading) return <Skeleton/>
1742
+ if (s.failed) return <RetryPanel error={s.error}/>
1743
+ return <TeamsTable teams={s.data}/>
1744
+ ```
1745
+
1746
+ `failed` is a positive check on purpose. The state used to be `loading: true`
1747
+ with `error` set, and the escape hatch was reading that second field — which the
1748
+ natural shape of a wrapper (`{ data, loading }` passed through) drops. **If you
1749
+ wrap this state, carry `failed` with it**, or your callers inherit an infinite
1750
+ skeleton through your hook.
1751
+
1752
+ Two things this does NOT cover. A failure AFTER the first snapshot leaves the
1753
+ good data on screen and sets `error` — replacing working rows with an error
1754
+ because the socket hiccuped is worse than the hiccup. And the failed state is
1755
+ terminal for one TRANSPORT, not forever: a reconnect discards the error and
1756
+ re-subscribes, so the entry returns to loading on its own.
1757
+
1731
1758
  ### Skipped is `idle`, not `loading`
1732
1759
 
1733
1760
  A skipped subscription reports **`idle: true`, `loading: false`**. The two are
@@ -4711,6 +4738,22 @@ To distinguish "we know about this" from "this surprised us":
4711
4738
  | `throw new Error('oops')` | Surfaces as a defect — full log + trace, generic failure to client |
4712
4739
  | Unexpected exception from a library | Same — caught, logged, generic failure |
4713
4740
 
4741
+ ## What the framework merges into your `error:` union
4742
+
4743
+ You do not declare the errors the framework itself can raise for a procedure —
4744
+ they are unioned into the wire contract for you, and only when the procedure can
4745
+ actually produce them:
4746
+
4747
+ | Merged | Into | When |
4748
+ |---|---|---|
4749
+ | `ScopeError` | query · mutation · action · event | the procedure declares a real `guards:` entry. NOT for `openAccess:` — a procedure advertising a denial it cannot produce is what makes an error union stop meaning anything |
4750
+ | `Unauthenticated` | query · mutation · action · event | the same condition. A guard can refuse for TWO reasons and they mean different things: the caller is known and lacks the scope (`ScopeError`), or they presented a credential that was REJECTED and so arrived anonymous (`Unauthenticated`). Without the second, an expired session reads as a permissions problem |
4751
+ | `BusinessRuleViolation` | mutation | always. A cross-table `rule()` on any table the mutation writes can fail it, and the descriptor cannot know which tables carry rules |
4752
+ | `ApprovalRequired` · `ApprovalExpired` · `ApprovalUnavailable` | mutation · action | the procedure declares `requiresApproval:` |
4753
+
4754
+ So a guarded mutation does **not** need `ScopeError` in its own `error:`. If you
4755
+ declared it anyway, that is harmless — the union is the same either way.
4756
+
4714
4757
  ## Framework-shipped error variants
4715
4758
 
4716
4759
  `@voltro/protocol` exports exactly two tagged errors. The rest of your typed errors are ones you declare yourself (above) or ones a plugin / the runtime contributes.
@@ -1150,6 +1150,46 @@ Auth is resolved by the **api**, never the web app. Two rules follow:
1150
1150
 
1151
1151
  So a cookie-mode Supabase app configures `supabaseStrategy({ cookieName: 'sb-<ref>-auth-token' })` on the **api**; the web loader's `ctx.query` then authenticates for free. See [Supabase Auth](/docs/plugins/auth-supabase).
1152
1152
 
1153
+ ### Renewing an EXPIRED session — `middleware.ts`
1154
+
1155
+ The rules above assume the cookie is still valid. When it is not — a token older
1156
+ than the IdP's lifetime, which for a 1-hour token is practically every first
1157
+ page view of the day — the api resolves the caller to anonymous and every loader
1158
+ and `preload` on the page fails.
1159
+
1160
+ You cannot fix that in a loader. `ctx.query` and every `preload` entry are bound
1161
+ from ONE cookie string **before any loader runs**, so a layout loader that
1162
+ renews the session cannot reach them. `middleware.ts` at the web app root runs
1163
+ earlier than both:
1164
+
1165
+ ```ts
1166
+ // middleware.ts — server-only. NOT app.config.ts, which is imported into the
1167
+ // client bundle whenever an api declares `authHeaders`.
1168
+ export default async (req) => {
1169
+ const fresh = await refreshSession(req.cookies['sb-session'])
1170
+ if (!fresh) return
1171
+ return {
1172
+ headers: { authorization: `Bearer ${fresh.accessToken}` },
1173
+ setCookies: [{ name: 'sb-session', value: fresh.cookie, maxAge: 3600 }],
1174
+ }
1175
+ }
1176
+ ```
1177
+
1178
+ **Write the cookie back.** An IdP that rotates refresh tokens (Supabase does, and
1179
+ detects reuse) will invalidate the session if you renew server-side and leave the
1180
+ browser holding the consumed one. `setCookies` is not an optimisation.
1181
+
1182
+ **It cannot redirect or refuse a request**, deliberately: authorization belongs
1183
+ on the api, which is the only thing that sees the data, and a web-side hook that
1184
+ could refuse would be a second authorization layer beside the real one. For a
1185
+ login redirect, throw `RedirectError` from the loader.
1186
+
1187
+ Headers you return are merged over the request's, and only auth-shaped ones
1188
+ (`authorization`, `x-tenant`, `x-voltro-*`) are forwarded to the api. Cookies
1189
+ default to `HttpOnly`, `Path=/`, `SameSite=lax`. The file is loaded once per
1190
+ boot; if it throws, the request fails rather than rendering with the credential
1191
+ you asked to replace.
1192
+
1153
1193
  ## Errors from loaders
1154
1194
 
1155
1195
  If the loader throws, the framework: