@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.
- package/CHANGELOG.md +424 -0
- package/THIRD-PARTY-NOTICES.md +8311 -3318
- package/dist/{agentsMd-0l980yhL.js → agentsMd-BaLC10Na.js} +110 -82
- package/dist/agentsMd-DCY1RSs8.js +2 -0
- package/dist/apiBuild-CeUN55uk.js +2 -0
- package/dist/{apiBuild-CSFI8QGq.js → apiBuild-DTWp0S_q.js} +11 -5
- package/dist/bin.js +1 -1
- package/dist/build-D4ygSbnV.js +843 -0
- package/dist/{checkCommand-COmqc2cB.js → checkCommand-Dg1G7Gwd.js} +6 -6
- package/dist/{checkCommand-2SbqzukH.js → checkCommand-L7DTlpIF.js} +1 -1
- package/dist/{cloudCmd-QUXh-b5w.js → cloudCmd-DzKcSYuy.js} +1 -1
- package/dist/{codegen-VF479Cnb.js → codegen-DSLM8Su9.js} +1 -1
- package/dist/codegen-DjgxEOnD.js +2 -0
- package/dist/codegenCommand-CG_Vx4lc.js +41 -0
- package/dist/{codemodRunner-r7J9lIa7.js → codemodRunner-Cd4xkC6u.js} +109 -11
- package/dist/{commands-Cc_nV8WI.js → commands-6Kzi92Np.js} +96 -73
- package/dist/{dashboardCommand-C-gKvwqh.js → dashboardCommand-Cq1PWvI1.js} +5 -5
- package/dist/{dataCommand-BgpBHnlB.js → dataCommand-DYzW8vkv.js} +3 -3
- package/dist/{dbCommand-sHedr-NJ.js → dbCommand-B4NWZtGL.js} +278 -237
- package/dist/dbCommand-CSFWs9ev.js +2 -0
- package/dist/{dev-CRHoCEiy.js → dev-CmuvUKRq.js} +2903 -2306
- package/dist/{dev--A3nsxA3.js → dev-cKUiZZsB.js} +1 -1
- package/dist/{doctorCommand-DtfJ3FA6.js → doctorCommand-DCiFVMtZ.js} +101 -69
- package/dist/doctorCommand-J3qu4E0Y.js +2 -0
- package/dist/{dormancyCommand-Drn7o0No.js → dormancyCommand-w1TrmgYP.js} +1 -1
- package/dist/{embeddingsCommand-Z-jO1fWN.js → embeddingsCommand-CMgPyRTr.js} +1 -1
- package/dist/{envCommand-D4gCrrTZ.js → envCommand-Cyynmcfa.js} +8 -8
- package/dist/{evolveCommand-CMROeKeA.js → evolveCommand-BwvQ8dVH.js} +2 -2
- package/dist/fileConventions-l-RIXbx8.js +36 -0
- package/dist/{fileTaxonomy-DvDUV9wq.js → fileTaxonomy-CbyMQYx_.js} +42 -42
- package/dist/fontPipeline-LxIHa1vo.js +2 -0
- package/dist/fontPipeline-Tsh8kZfA.js +152 -0
- package/dist/{frameworkTableAssembly-w-XnLa3q.js → frameworkTableAssembly-D7LJuALW.js} +5 -5
- package/dist/frameworkTableAssembly-IPD1pUnZ.js +2 -0
- package/dist/imagePipeline-B_GVJgm6.js +2 -0
- package/dist/imagePipeline-CBZmjT4i.js +127 -0
- package/dist/index.js +2 -2
- package/dist/{infoCommand-DXM868o_.js → infoCommand-DlYlUPqs.js} +1 -1
- package/dist/{inspect-CtL_xTbu.js → inspect-Bd8-9wsi.js} +1 -1
- package/dist/inspect-CuoDInfZ.js +2 -0
- package/dist/{inspectGateHint-BjnFubmH.js → inspectGateHint-4LxkNtrz.js} +1 -1
- package/dist/manifestBuild-C4-J1-m_.js +2 -0
- package/dist/{manifestBuild-CuU1VrSm.js → manifestBuild-Cqgsx2bM.js} +1 -1
- package/dist/{metaCommands-C6RFmF1r.js → metaCommands-x7RCi2AF.js} +9 -3
- package/dist/{migrate-D0F-eTlK.js → migrate-BK_Bbx-_.js} +2 -2
- package/dist/mobileCommand-D9O6iq3D.js +428 -0
- package/dist/mobileCommand-DAum7tsG.js +2 -0
- package/dist/{pageConvention-CzUiSbtU.js → pageConvention-CMpfDN6r.js} +1 -1
- package/dist/{privacyCommand-DGdopOI6.js → privacyCommand-BCa2OoZG.js} +1 -1
- package/dist/{probeCommand-C9gazU0H.js → probeCommand-_C0YU207.js} +83 -24
- package/dist/{projectScaffold-EzlErR4E.js → projectScaffold-CJfP-xbT.js} +1 -1
- package/dist/{projectScaffold-B4dmTlwT.js → projectScaffold-CSN0OzBV.js} +2 -2
- package/dist/renderModeScan-43yQ2opo.js +147 -0
- package/dist/{renderProfile-Ck32Fzxr.js → renderProfile-DvrhVJHa.js} +2 -2
- package/dist/{runtimeTrace-BPQyCmC5.js → runtimeTrace-CGWx1Q6l.js} +1 -1
- package/dist/{sdkgen-Se88ifTd.js → sdkgen-CDGHQUFj.js} +1 -1
- package/dist/serveCommand-BiPe8BJm.js +2 -0
- package/dist/{serveCommand-DkP3OT0W.js → serveCommand-Bje09q1v.js} +889 -825
- package/dist/serveEntry.js +1 -1
- package/dist/start-B0bnJgxI.js +3 -0
- package/dist/{start-jw89Xbqy.js → start-Clz-1BHB.js} +633 -455
- package/dist/startEntry.js +1 -1
- package/dist/{staticCommand-BwNEDlSU.js → staticCommand-ey0kYmOT.js} +1 -1
- package/dist/{subcommandNames-DpYs3DXr.js → subcommandNames-CDzfEtKV.js} +3 -3
- package/dist/{templates-BR-fb4SP.js → templates-BTWZkJJT.js} +41 -9
- package/dist/{test-f3amja6a.js → test-D_kW4KMj.js} +1 -1
- package/dist/updateCommand-CIoVDKnj.js +2 -0
- package/dist/{updateCommand-BMk2e4ky.js → updateCommand-CRJlAOaM.js} +139 -115
- package/dist/{webDev-BgWL9gKV.js → webDev-DSI9SOhs.js} +1598 -1028
- package/dist/webDev-DlvZO30c.js +2 -0
- package/dist/{webhooksCommand-CoIO3jbj.js → webhooksCommand-BvzXNHji.js} +1 -1
- package/dist/workspaceDeps-RKEkX92S.js +45 -0
- package/package.json +60 -19
- package/templates/AGENTS.core.md +2 -0
- package/templates/AGENTS.md +8 -4
- package/templates/agent-docs/_index.md +6 -4
- package/templates/agent-docs/_manifest.json +21 -5
- package/templates/agent-docs/ai.md +6 -6
- package/templates/agent-docs/authentication.md +73 -1
- package/templates/agent-docs/cli.md +6 -3
- package/templates/agent-docs/configuration.md +17 -0
- package/templates/agent-docs/data.md +522 -29
- package/templates/agent-docs/database/advancedqueries.md +8 -8
- package/templates/agent-docs/database/columntypes.md +2 -2
- package/templates/agent-docs/database/migrations.md +1 -1
- package/templates/agent-docs/database/querying.md +1 -1
- package/templates/agent-docs/database/schema.md +1 -1
- package/templates/agent-docs/database/seedsdialects.md +65 -3
- package/templates/agent-docs/database/transactions.md +3 -3
- package/templates/agent-docs/deployment.md +8 -0
- package/templates/agent-docs/internationalization.md +4 -2
- package/templates/agent-docs/introduction.md +32 -1
- package/templates/agent-docs/local-first-mobile.md +226 -30
- package/templates/agent-docs/observability.md +5 -1
- package/templates/agent-docs/plugins/atlassian.md +2 -2
- package/templates/agent-docs/plugins/audit.md +2 -2
- package/templates/agent-docs/plugins/auth.md +1 -1
- package/templates/agent-docs/plugins/billing.md +1 -1
- package/templates/agent-docs/plugins/cdc-out.md +8 -3
- package/templates/agent-docs/plugins/comments.md +164 -0
- package/templates/agent-docs/plugins/notifications.md +47 -4
- package/templates/agent-docs/plugins/presence.md +45 -3
- package/templates/agent-docs/plugins/prometheus.md +3 -1
- package/templates/agent-docs/plugins/queue.md +172 -0
- package/templates/agent-docs/plugins.md +17 -13
- package/templates/agent-docs/reference.md +35 -4
- package/templates/agent-docs/routing.md +585 -7
- package/templates/agent-docs/scheduling.md +1 -1
- package/templates/agent-docs/schema-driven-ui.md +226 -4
- package/templates/agent-docs/security.md +3 -3
- package/templates/agent-docs/templates/appshells.md +36 -4
- package/templates/agent-docs/whats-new.md +134 -66
- package/templates/apps/api-ai/package.json +6 -6
- package/templates/apps/api-auth/package.json +8 -8
- package/templates/apps/api-backend/package.json +7 -7
- package/templates/apps/api-backend-deactivation/package.json +7 -7
- package/templates/apps/api-backend-mail/package.json +8 -8
- package/templates/apps/api-backend-mariadb/package.json +9 -9
- package/templates/apps/api-backend-sqlite/package.json +8 -8
- package/templates/apps/api-backend-storage/package.json +8 -8
- package/templates/apps/api-cms/package.json +9 -9
- package/templates/apps/api-collab/README.md +3 -3
- package/templates/apps/api-collab/app.config.ts +1 -1
- package/templates/apps/api-collab/database/schema.ts +12 -8
- package/templates/apps/api-collab/mutations/documents.create.mutation.server.ts +1 -1
- package/templates/apps/api-collab/mutations/documents.setBody.mutation.server.ts +1 -1
- package/templates/apps/api-collab/mutations/documents.setBody.mutation.ts +1 -1
- package/templates/apps/api-collab/package.json +8 -8
- package/templates/apps/api-collab/template.json +1 -1
- package/templates/apps/api-collab/tests/documents.setBody.test.ts +10 -2
- package/templates/apps/api-data-advanced/package.json +8 -8
- package/templates/apps/api-durable/package.json +8 -8
- package/templates/apps/api-feature-flags/package.json +9 -9
- package/templates/apps/api-governance/package.json +8 -8
- package/templates/apps/api-kv/package.json +8 -8
- package/templates/apps/api-moderation/package.json +8 -8
- package/templates/apps/api-observability/package.json +8 -8
- package/templates/apps/api-ratelimit/package.json +8 -8
- package/templates/apps/api-rbac/package.json +8 -8
- package/templates/apps/api-rest/package.json +7 -7
- package/templates/apps/api-row-history/package.json +8 -8
- package/templates/apps/api-saas/package.json +11 -10
- package/templates/apps/api-saas-starter/package.json +10 -10
- package/templates/apps/api-search/package.json +8 -8
- package/templates/apps/api-status/package.json +8 -8
- package/templates/apps/api-webhooks/package.json +9 -9
- package/templates/apps/changelog/app.config.ts +26 -2
- package/templates/apps/changelog/content/releases/{0.1.0.mdx → v0-1-0.mdx} +0 -1
- package/templates/apps/changelog/content/releases/{0.2.0.mdx → v0-2-0.mdx} +0 -1
- package/templates/apps/changelog/package.json +9 -8
- package/templates/apps/changelog/src/collections/releases.collection.ts +48 -0
- package/templates/apps/changelog/src/globals.d.ts +1 -1
- package/templates/apps/changelog/src/locales/de.ts +1 -1
- package/templates/apps/changelog/src/locales/en.ts +1 -1
- package/templates/apps/changelog/src/pages/[locale]/[slug]/page.tsx +7 -4
- package/templates/apps/changelog/src/pages/[locale]/mirrors.test.tsx +13 -41
- package/templates/apps/changelog/src/pages/[slug]/page.test.tsx +26 -51
- package/templates/apps/changelog/src/pages/[slug]/page.tsx +21 -19
- package/templates/apps/changelog/src/pages/page.test.tsx +14 -37
- package/templates/apps/changelog/src/pages/page.tsx +18 -12
- package/templates/apps/edge-functions/package.json +2 -2
- package/templates/apps/frontend-admin/package.json +8 -8
- package/templates/apps/frontend-app/package.json +9 -9
- package/templates/apps/frontend-auth/package.json +8 -8
- package/templates/apps/frontend-blank/package.json +7 -7
- package/templates/apps/frontend-cms/package.json +9 -9
- package/templates/apps/frontend-collab/README.md +43 -24
- package/templates/apps/frontend-collab/app.config.ts +3 -3
- package/templates/apps/frontend-collab/package.json +14 -10
- package/templates/apps/frontend-collab/src/locales/de.ts +1 -2
- package/templates/apps/frontend-collab/src/locales/en.ts +1 -2
- package/templates/apps/frontend-collab/src/pages/page.test.tsx +72 -76
- package/templates/apps/frontend-collab/src/pages/page.tsx +45 -22
- package/templates/apps/frontend-collab/template.json +2 -2
- package/templates/apps/frontend-contact/package.json +7 -7
- package/templates/apps/frontend-dashboard/package.json +7 -7
- package/templates/apps/frontend-docs/content/docs/de/guides/first-page.md +4 -0
- package/templates/apps/frontend-docs/content/docs/de/intro/getting-started.md +4 -0
- package/templates/apps/frontend-docs/content/docs/en/guides/first-page.md +4 -0
- package/templates/apps/frontend-docs/content/docs/en/intro/getting-started.md +4 -0
- package/templates/apps/frontend-docs/package.json +9 -6
- package/templates/apps/frontend-docs/src/collections/docs.collection.ts +21 -0
- package/templates/apps/frontend-docs/src/locales/de.ts +0 -5
- package/templates/apps/frontend-docs/src/locales/en.ts +0 -5
- package/templates/apps/frontend-docs/src/pages/[locale]/docs/[...slug]/page.tsx +17 -9
- package/templates/apps/frontend-docs/src/pages/[locale]/mirrors.test.tsx +10 -5
- package/templates/apps/frontend-docs/src/pages/[locale]/page.tsx +8 -0
- package/templates/apps/frontend-docs/src/pages/docs/[...slug]/page.test.tsx +53 -15
- package/templates/apps/frontend-docs/src/pages/docs/[...slug]/page.tsx +27 -33
- package/templates/apps/frontend-docs/src/pages/page.test.tsx +17 -3
- package/templates/apps/frontend-docs/src/pages/page.tsx +16 -12
- package/templates/apps/frontend-i18n/package.json +6 -6
- package/templates/apps/frontend-landing/README.md +48 -0
- package/templates/apps/frontend-landing/app.config.ts +28 -0
- package/templates/apps/frontend-landing/package.json +7 -6
- package/templates/apps/frontend-landing/src/assets/hero.jpg +0 -0
- package/templates/apps/frontend-landing/src/fonts/Geist-Variable.woff2 +0 -0
- package/templates/apps/frontend-landing/src/fonts/LICENSE-Geist.txt +92 -0
- package/templates/apps/frontend-landing/src/globals.css +15 -0
- package/templates/apps/frontend-landing/src/globals.d.ts +17 -0
- package/templates/apps/frontend-landing/src/locales/de.ts +3 -2
- package/templates/apps/frontend-landing/src/locales/en.ts +5 -2
- package/templates/apps/frontend-landing/src/pages/page.test.tsx +79 -0
- package/templates/apps/frontend-landing/src/pages/page.tsx +26 -3
- package/templates/apps/frontend-landing/template.json +2 -2
- package/templates/apps/frontend-portal/package.json +8 -8
- package/templates/apps/frontend-saas/package.json +8 -8
- package/templates/apps/frontend-spa/package.json +7 -7
- package/templates/apps/frontend-ssr/package.json +7 -7
- package/templates/apps/frontend-ssr-api/package.json +8 -8
- package/templates/apps/frontend-static-blog/content/posts/cms-to-ssg.md +12 -0
- package/templates/apps/frontend-static-blog/content/posts/hello-static.md +12 -0
- package/templates/apps/frontend-static-blog/content/posts/islands-not-hydration.md +14 -0
- package/templates/apps/frontend-static-blog/package.json +9 -6
- package/templates/apps/frontend-static-blog/src/collections/posts.collection.ts +27 -0
- package/templates/apps/frontend-static-blog/src/pages/[locale]/blog/[slug]/page.tsx +7 -4
- package/templates/apps/frontend-static-blog/src/pages/[locale]/mirrors.test.tsx +7 -1
- package/templates/apps/frontend-static-blog/src/pages/blog/[slug]/page.test.tsx +19 -9
- package/templates/apps/frontend-static-blog/src/pages/blog/[slug]/page.tsx +19 -21
- package/templates/apps/frontend-static-blog/src/pages/page.test.tsx +18 -5
- package/templates/apps/frontend-static-blog/src/pages/page.tsx +21 -14
- package/templates/apps/frontend-status/package.json +8 -8
- package/templates/apps/mobile-app/package.json +12 -11
- package/templates/apps/mobile-app/src/lib/deeplinks.ts +29 -17
- package/templates/apps/mobile-app/tests/deeplinks.test.ts +16 -0
- package/dist/agentsMd-SDDSkyl4.js +0 -2
- package/dist/apiBuild-BYBpL7Pz.js +0 -2
- package/dist/build-CPgcMQug.js +0 -793
- package/dist/codegen-CctkDO-1.js +0 -2
- package/dist/codegenCommand-DCdG2JN-.js +0 -137
- package/dist/dbCommand-DNb6yeOG.js +0 -2
- package/dist/doctorCommand-CqoWA2p5.js +0 -2
- package/dist/fileConventions-DOqD3lPS.js +0 -34
- package/dist/frameworkTableAssembly-C6ETawPR.js +0 -2
- package/dist/inspect-CuGDYES0.js +0 -2
- package/dist/manifestBuild-CPjhvM62.js +0 -2
- package/dist/renderModeScan-CcH2X1_D.js +0 -120
- package/dist/serveCommand-DLc-BznW.js +0 -2
- package/dist/start-DfL3fOiN.js +0 -3
- package/dist/updateCommand-5gFVfK5q.js +0 -2
- package/dist/webDev-CZbTsDcH.js +0 -2
- package/templates/apps/changelog/scripts/generate-rss.mjs +0 -38
- package/templates/apps/changelog/src/lib/releases.ts +0 -21
- package/templates/apps/frontend-static-blog/src/content/posts.ts +0 -64
|
@@ -379,7 +379,7 @@ Emits `CONSTRAINT <name> UNIQUE (col1, col2, ...)` inline in CREATE
|
|
|
379
379
|
TABLE on every dialect. Standard SQL.
|
|
380
380
|
|
|
381
381
|
This is what backs `ctx.store.upsert(..., { conflictColumns: ['a', 'b'] })`
|
|
382
|
-
— see [Bulk operations](/docs/database/bulk-operations#upsert).
|
|
382
|
+
— see [Bulk operations](/docs/database/bulk-operations#upsert-insert-or-update-on-conflict).
|
|
383
383
|
|
|
384
384
|
## GiST indexes (PostGIS spatial)
|
|
385
385
|
|
|
@@ -627,7 +627,7 @@ await ctx.store.update('notes', id, {
|
|
|
627
627
|
|
|
628
628
|
Two layers of validation apply:
|
|
629
629
|
|
|
630
|
-
1. **JSON validity** — that the stored bytes are well-formed JSON — is enforced automatically by the database on every dialect (see [Storage + validation per dialect](#storage
|
|
630
|
+
1. **JSON validity** — that the stored bytes are well-formed JSON — is enforced automatically by the database on every dialect (see [Storage + validation per dialect](#storage-validation-per-dialect)). You don't declare anything.
|
|
631
631
|
2. **JSON *shape*** — that the value matches your expected structure — is up to you: enforce it at the table level with `table().validate(Schema)`:
|
|
632
632
|
|
|
633
633
|
```ts
|
|
@@ -689,7 +689,7 @@ mssql, sqlite 3.8+).
|
|
|
689
689
|
depend on?"
|
|
690
690
|
|
|
691
691
|
When you don't have a recursive structure, plain
|
|
692
|
-
[`withCte()`](/docs/database/query-builder#ctes) is enough.
|
|
692
|
+
[`withCte()`](/docs/database/query-builder#ctes-common-table-expressions) is enough.
|
|
693
693
|
|
|
694
694
|
## Shape
|
|
695
695
|
|
|
@@ -820,7 +820,7 @@ eager-load with `.with({...})` to get the per-field pre-filter.
|
|
|
820
820
|
|
|
821
821
|
## See also
|
|
822
822
|
|
|
823
|
-
- [Plain CTEs](/docs/database/query-builder#ctes) — `withCte()` for
|
|
823
|
+
- [Plain CTEs](/docs/database/query-builder#ctes-common-table-expressions) — `withCte()` for
|
|
824
824
|
non-recursive named sub-queries
|
|
825
825
|
- [Self-joins](/docs/database/self-joins) — for single-level
|
|
826
826
|
parent/child queries
|
|
@@ -830,7 +830,7 @@ eager-load with `.with({...})` to get the per-field pre-filter.
|
|
|
830
830
|
`unionAll`, the mechanism a recursive CTE is built on
|
|
831
831
|
- [Joins](/docs/database/joins) — relation-based traversal when the
|
|
832
832
|
graph depth is fixed (e.g. parent + immediate children)
|
|
833
|
-
- [Aggregations](/docs/database/query-builder#
|
|
833
|
+
- [Aggregations](/docs/database/query-builder#aggregates) —
|
|
834
834
|
COUNT/SUM/AVG over a recursive CTE's result set
|
|
835
835
|
|
|
836
836
|
|
|
@@ -940,7 +940,7 @@ keep the branches' result sets bounded.
|
|
|
940
940
|
the column-level "in A but not in B" case
|
|
941
941
|
- [Aggregations](/docs/database/aggregations) — `count()` etc. on
|
|
942
942
|
a set-op result is a common pattern
|
|
943
|
-
- [CTEs](/docs/database/query-builder#ctes) — name a complex set-op
|
|
943
|
+
- [CTEs](/docs/database/query-builder#ctes-common-table-expressions) — name a complex set-op
|
|
944
944
|
result so you can reference it in a larger query
|
|
945
945
|
|
|
946
946
|
|
|
@@ -1044,7 +1044,7 @@ Non-correlated only. The inner query can NOT reference outer-row
|
|
|
1044
1044
|
columns like `WHERE inner.userId = users.id`. For correlated
|
|
1045
1045
|
sub-queries (a common shape: "user who has at least one post created
|
|
1046
1046
|
in the last hour") use a [Self-join](/docs/database/self-joins) or
|
|
1047
|
-
an [Eager-load](/docs/database/joins#eager-loading) — both can
|
|
1047
|
+
an [Eager-load](/docs/database/joins#eager-loading-via-with-spec) — both can
|
|
1048
1048
|
express the same query without the correlation reference.
|
|
1049
1049
|
|
|
1050
1050
|
## Reactivity
|
|
@@ -1069,7 +1069,7 @@ every dialect we ship. No per-dialect dispatch.
|
|
|
1069
1069
|
with `count()` etc. for "count of X where Y belongs to Z"
|
|
1070
1070
|
- [Self-joins](/docs/database/self-joins) — when the relationship
|
|
1071
1071
|
can be expressed as a join instead
|
|
1072
|
-
- [CTEs](/docs/database/query-builder#ctes) — for naming a
|
|
1072
|
+
- [CTEs](/docs/database/query-builder#ctes-common-table-expressions) — for naming a
|
|
1073
1073
|
sub-query you reuse multiple times in the same outer query
|
|
1074
1074
|
|
|
1075
1075
|
|
|
@@ -350,7 +350,7 @@ on it explicitly via `.expressionIndex(name, [...], { ... })`.
|
|
|
350
350
|
|
|
351
351
|
- [Columns](/docs/database/columns) — `.computed(row => ...)` and
|
|
352
352
|
`.default(() => ...)` for the app-side variants
|
|
353
|
-
- [Indexes](/docs/database/indexes#expression) — `.expressionIndex()`
|
|
353
|
+
- [Indexes](/docs/database/indexes#expression-indexes) — `.expressionIndex()`
|
|
354
354
|
for indexing a generated column
|
|
355
355
|
- [Full-text search](/docs/database/full-text-search) — the FTS
|
|
356
356
|
pattern uses STORED tsvector generated columns
|
|
@@ -989,7 +989,7 @@ behavioural gaps are too large to paper over.
|
|
|
989
989
|
## See also
|
|
990
990
|
|
|
991
991
|
- [Columns](/docs/database/columns) — the regular schema-DSL types
|
|
992
|
-
- [Expression indexes](/docs/database/indexes#expression) — the
|
|
992
|
+
- [Expression indexes](/docs/database/indexes#expression-indexes) — the
|
|
993
993
|
framework's index API (`kind: 'gist' | 'gin'` on postgres)
|
|
994
994
|
- [PostGIS docs](https://postgis.net/docs/) — the official manual,
|
|
995
995
|
authoritative for every spatial function the framework re-exports
|
|
@@ -700,7 +700,7 @@ A handler that a `voltro dev` session or a test actually ran is reported with wh
|
|
|
700
700
|
|
|
701
701
|
### What the codemod does per kind
|
|
702
702
|
|
|
703
|
-
- **`rename-column`** gets a real `transform`: it renames the field in the `*.entity.ts` AND chains **`.renamedFrom('old')`** (so the differ plans a catalog RENAME, not the lossy drop+create described [above](#
|
|
703
|
+
- **`rename-column`** gets a real `transform`: it renames the field in the `*.entity.ts` AND chains **`.renamedFrom('old')`** (so the differ plans a catalog RENAME, not the lossy drop+create described [above](#renamedfrom-oldname)), then annotates the handler sites the blast radius found.
|
|
704
704
|
- **`retype-column` / `split-column` / `drop-column` / `rename-table`** are reshaping changes with no single mechanical rewrite, so they get a **`manual`** codemod: a generated, numbered checklist of the edits + the annotation to add, printed for you to apply.
|
|
705
705
|
|
|
706
706
|
`voltro evolve` produces the plan; it does not apply the schema change. **`voltro check` is the gate on the result**, and `voltro db apply` lands it — after `--write`, review the annotated handlers, then run those two.
|
|
@@ -309,7 +309,7 @@ The query builder has dedicated pages for the deeper topics:
|
|
|
309
309
|
- **[Self-joins](/docs/database/self-joins)** — `.as(alias)` +
|
|
310
310
|
`.innerJoin(table, alias, on)` + `.selectJoined({...})` for parent/
|
|
311
311
|
child trees and CTE references.
|
|
312
|
-
- **[CTEs](/docs/database/query-builder#ctes)** — `.withCte(name, sub)`
|
|
312
|
+
- **[CTEs](/docs/database/query-builder#ctes-common-table-expressions)** — `.withCte(name, sub)`
|
|
313
313
|
for named sub-queries reusable inside the outer SELECT.
|
|
314
314
|
- **[Recursive CTEs](/docs/database/recursive-cte)** — `.recursiveCte`
|
|
315
315
|
for tree walks (org hierarchy, comment threads, file folders).
|
|
@@ -445,7 +445,7 @@ framework runs on the post-commit change channel.
|
|
|
445
445
|
**Reach for `reference()` first.** A real foreign key across a plugin boundary
|
|
446
446
|
works and survives the plugin renaming its table, because `reference()` takes
|
|
447
447
|
the table as a VALUE — see
|
|
448
|
-
[plugins/overview](/docs/plugins/overview
|
|
448
|
+
[plugins/overview](/docs/plugins/overview).
|
|
449
449
|
`pluginRef` is for the case where you have deliberately chosen NOT to have a
|
|
450
450
|
key: it enforces nothing at the database level.
|
|
451
451
|
|
|
@@ -391,7 +391,7 @@ What the framework hides for you vs what's worth knowing. Per-dialect pages dril
|
|
|
391
391
|
| `RETURNING *` on DELETE | yes | no | yes (10.0+) | OUTPUT DELETED.* | yes | yes |
|
|
392
392
|
| Parameterized `LIMIT ?` | yes | no — integer-literal inlined | yes | no — integer-literal inlined | yes | yes |
|
|
393
393
|
| `LIMIT N OFFSET N` syntax | yes | yes | yes | no — `OFFSET … ROWS FETCH NEXT … ROWS ONLY` | yes | yes |
|
|
394
|
-
| DEFAULT on TEXT columns | yes | **NO** —
|
|
394
|
+
| DEFAULT on TEXT columns | yes | **NO** — framework emits VARCHAR(255) | yes — framework still emits VARCHAR(255) (engine parity) | yes — framework emits NVARCHAR(450) (indexable) | yes | yes |
|
|
395
395
|
| Native JSON column type | JSONB | JSON | JSON | NVARCHAR(MAX) | TEXT | TEXT |
|
|
396
396
|
| JSON columns returned as objects | yes | yes | yes | **no — strings** — framework auto-parses | **no — strings** — framework auto-parses | **no — strings** — framework auto-parses |
|
|
397
397
|
| Booleans | proper booleans | 0/1 (TINYINT) | 0/1 | BIT (proper bool) | 0/1 (INTEGER) | 0/1 (INTEGER) |
|
|
@@ -796,11 +796,48 @@ The framework's DDL emitter detects the case and switches to `VARCHAR(255)`:
|
|
|
796
796
|
```typescript
|
|
797
797
|
text().default('json') // → VARCHAR(255) DEFAULT 'json'
|
|
798
798
|
text().oneOf(['a', 'b', 'c']).default('a') // → VARCHAR(255) DEFAULT 'a' CHECK (col IN ('a','b','c'))
|
|
799
|
-
text().nullable() // →
|
|
799
|
+
text().nullable() // → LONGTEXT (unchanged — no default to trip up)
|
|
800
800
|
```
|
|
801
801
|
|
|
802
802
|
VARCHAR(255) is the framework's heuristic — enough for typical enum-like values, short status strings, format identifiers. If you need longer defaulted text, declare the column as `text().nullable()` + handle the missing-default case in application code, OR drop down to `unsafe()`.
|
|
803
803
|
|
|
804
|
+
### Adding a default to an existing column
|
|
805
|
+
|
|
806
|
+
The rule holds for a migration too, not only for `CREATE TABLE`. Adding
|
|
807
|
+
`.default(…)` to a `text()` column that already exists **reshapes** the column
|
|
808
|
+
rather than setting a default on it:
|
|
809
|
+
|
|
810
|
+
```sql
|
|
811
|
+
ALTER TABLE tickets MODIFY COLUMN `status` VARCHAR(255) NOT NULL DEFAULT 'active'
|
|
812
|
+
```
|
|
813
|
+
|
|
814
|
+
That is deliberate, and it is what makes the change appliable at all: a plain
|
|
815
|
+
`ALTER TABLE … ALTER COLUMN status SET DEFAULT 'active'` is answered by MySQL
|
|
816
|
+
with `BLOB, TEXT, GEOMETRY or JSON column 'status' can't have a default value`,
|
|
817
|
+
so the migration would stop half-applied. Reshaping means the column has the same
|
|
818
|
+
type whether the default was declared before or after the table existed.
|
|
819
|
+
|
|
820
|
+
Two consequences worth knowing before you run it:
|
|
821
|
+
|
|
822
|
+
- **It is a narrowing.** If a row already holds more than 255 characters, the
|
|
823
|
+
ALTER fails (`Data too long for column 'status'`) and the migration stops
|
|
824
|
+
before it. Check first, and pick the width yourself with
|
|
825
|
+
`text().maxLength(n).default(…)` if 255 is too small:
|
|
826
|
+
|
|
827
|
+
```sql
|
|
828
|
+
SELECT COUNT(*) FROM tickets WHERE CHAR_LENGTH(status) > 255
|
|
829
|
+
```
|
|
830
|
+
|
|
831
|
+
- **Removing a default does not reshape back.** `DROP DEFAULT` is legal on any
|
|
832
|
+
mysql type, and widening a `VARCHAR(255)` back to `LONGTEXT` would fail for an
|
|
833
|
+
indexed column — so the column keeps its bounded type. Declare
|
|
834
|
+
`text().maxLength(255)` if you want that to be visible in the schema.
|
|
835
|
+
|
|
836
|
+
SQL Server does the same thing for its own reason (an `NVARCHAR(MAX)` column
|
|
837
|
+
cannot be indexed, so a defaulted text column is `NVARCHAR(450)`). On postgres a
|
|
838
|
+
`TEXT` column takes a `DEFAULT` directly, and SQLite rebuilds the table to the
|
|
839
|
+
declared shape — neither reshapes anything.
|
|
840
|
+
|
|
804
841
|
## JSON columns
|
|
805
842
|
|
|
806
843
|
`json()` columns emit `JSON` (mysql's native binary JSON type since 5.7+). The driver auto-parses on read; same shape as postgres. No coercion overhead.
|
|
@@ -1055,7 +1092,7 @@ MariaDB's GTID format differs from MySQL's: `0-1-100` (domain-server-sequence) v
|
|
|
1055
1092
|
- **`mariadb` schema package is wire-compatible with `mysql`**. If you migrate from MySQL → MariaDB, the framework re-emits DDL cleanly via `applySchema(..., 'mariadb')`. Production data round-trips through `mysqldump` without translation.
|
|
1056
1093
|
- **`sql_mode=NO_BACKSLASH_ESCAPES`** is sometimes set on MariaDB deploys. The framework's identifier escaping handles it, but user-written `unsafe()` strings that hand-escape backslashes may produce wrong output. Leave that mode off if you can.
|
|
1057
1094
|
- **Sequence-based ID columns**. MariaDB has true CREATE SEQUENCE; the framework doesn't use it (TypeID / ULID / Snowflake are client-side). If you reach for sequences for legacy reasons, they're outside the framework's auto-injection path.
|
|
1058
|
-
- **Hand-rolled `AUTO_INCREMENT` primary keys** work the same as on mysql: an `insert` / `insertMany` with no client-side `id` recovers the DB-generated id via `LAST_INSERT_ID()` (connection-pinned; `insertMany` recovers the whole consecutive range). See the [mysql page](/docs/database/dialects/mysql
|
|
1095
|
+
- **Hand-rolled `AUTO_INCREMENT` primary keys** work the same as on mysql: an `insert` / `insertMany` with no client-side `id` recovers the DB-generated id via `LAST_INSERT_ID()` (connection-pinned; `insertMany` recovers the whole consecutive range). See the [mysql page](/docs/database/dialects/mysql) for the worked example — the recovery path is identical on both engines.
|
|
1059
1096
|
|
|
1060
1097
|
## Where it lives
|
|
1061
1098
|
|
|
@@ -1187,6 +1224,31 @@ When the caller didn't supply an `orderBy` but did set `skip` (uncommon but lega
|
|
|
1187
1224
|
|
|
1188
1225
|
The compiler inlines integer literals for TOP/OFFSET/FETCH NEXT values rather than parameter binding. Same rationale as MySQL — tedious has bind-as-INT issues with large or unexpected-typed numeric params.
|
|
1189
1226
|
|
|
1227
|
+
## Text columns with a DEFAULT — NVARCHAR(450)
|
|
1228
|
+
|
|
1229
|
+
A plain `text()` column is `NVARCHAR(MAX)`, which SQL Server cannot index. A text
|
|
1230
|
+
column that carries a literal default or a closed value set is therefore emitted
|
|
1231
|
+
bounded, at `NVARCHAR(450)` — under the 900-byte single-column key limit, so it
|
|
1232
|
+
stays indexable:
|
|
1233
|
+
|
|
1234
|
+
```typescript
|
|
1235
|
+
text().default('open') // → NVARCHAR(450) + a DEFAULT constraint
|
|
1236
|
+
text().oneOf(['open', 'closed']) // → NVARCHAR(450) + a CHECK constraint
|
|
1237
|
+
text().nullable() // → NVARCHAR(MAX)
|
|
1238
|
+
```
|
|
1239
|
+
|
|
1240
|
+
This holds for migrations as well as for `CREATE TABLE`: adding `.default(…)` to
|
|
1241
|
+
an existing text column retypes it to `NVARCHAR(450)` and then adds the default
|
|
1242
|
+
constraint, so the column has the same type whether the default was declared
|
|
1243
|
+
before or after the table existed. It is a narrowing — if a row already holds
|
|
1244
|
+
more than 450 characters, the `ALTER COLUMN` fails ("String or binary data would
|
|
1245
|
+
be truncated") and the migration stops there. Check first, and use
|
|
1246
|
+
`text().maxLength(n).default(…)` to choose a different width:
|
|
1247
|
+
|
|
1248
|
+
```sql
|
|
1249
|
+
SELECT COUNT(*) FROM tickets WHERE LEN(status) > 450
|
|
1250
|
+
```
|
|
1251
|
+
|
|
1190
1252
|
## JSON columns — NVARCHAR(MAX) + auto-parse
|
|
1191
1253
|
|
|
1192
1254
|
`json()` columns emit `NVARCHAR(MAX)` in DDL — mssql has no native JSON type pre-2025. Validation goes through `ISJSON(col) = 1` CHECK constraints; serialization is application-side.
|
|
@@ -261,7 +261,7 @@ await ctx.store.upsert('orgSlugs', {
|
|
|
261
261
|
```
|
|
262
262
|
|
|
263
263
|
Requires a composite UNIQUE constraint on the table — declare it via
|
|
264
|
-
`.unique([cols])` in the schema (see [Indexes](/docs/database/indexes#composite-unique)).
|
|
264
|
+
`.unique([cols])` in the schema (see [Indexes](/docs/database/indexes#composite-unique-constraints)).
|
|
265
265
|
|
|
266
266
|
## `insertIgnore` — keep existing on conflict
|
|
267
267
|
|
|
@@ -314,7 +314,7 @@ const count = await ctx.store.updateMany('posts', { hidden: true }, {
|
|
|
314
314
|
// count: number of rows actually updated
|
|
315
315
|
```
|
|
316
316
|
|
|
317
|
-
The `where` predicate is a regular [Predicate](/docs/database/query-builder
|
|
317
|
+
The `where` predicate is a regular [Predicate](/docs/database/query-builder)
|
|
318
318
|
AST — same shape `.where()` uses. Sub-queries via `inSubquery` /
|
|
319
319
|
`exists` are supported.
|
|
320
320
|
|
|
@@ -395,5 +395,5 @@ matters.
|
|
|
395
395
|
`aggregate()` for read-side bulk reads
|
|
396
396
|
- [Sub-queries](/docs/database/sub-queries) — `inSubquery` /
|
|
397
397
|
`exists` in `updateMany` `where:` clauses
|
|
398
|
-
- [Composite UNIQUE](/docs/database/indexes#composite-unique) — for
|
|
398
|
+
- [Composite UNIQUE](/docs/database/indexes#composite-unique-constraints) — for
|
|
399
399
|
the constraint that backs `upsert`'s `conflictColumns: ['a', 'b']`
|
|
@@ -1119,6 +1119,14 @@ VOLTRO_LOG_FORMAT=json
|
|
|
1119
1119
|
VOLTRO_LOG_LEVEL=info
|
|
1120
1120
|
```
|
|
1121
1121
|
|
|
1122
|
+
In `json` mode (the default off a TTY, so a pod gets it without configuration)
|
|
1123
|
+
EVERY line the framework emits is a parseable record — the boot banner, the app
|
|
1124
|
+
surface, `voltro db apply`'s plan summary, refusal detail, and every subsystem
|
|
1125
|
+
logger (schedule, broadcast, workflow, flow-control). A hand-formatted table or
|
|
1126
|
+
a `[tag]`-prefixed adapter line between JSON records is a bug, not a style: log
|
|
1127
|
+
collectors show it as unparsed noise. On a TTY the same surfaces render as the
|
|
1128
|
+
human-readable banners and tables.
|
|
1129
|
+
|
|
1122
1130
|
**Error reporting** — add `sentryPlugin()` from `@voltro/plugin-sentry`. It stays inert until `SENTRY_DSN` is set, and reported errors correlate to the request `traceId`:
|
|
1123
1131
|
|
|
1124
1132
|
```ts
|
|
@@ -78,6 +78,8 @@ Server-side, the active locale is determined by, in priority order:
|
|
|
78
78
|
2. **`Accept-Language` header** — the browser/OS preference, q-weighted and sorted per RFC 4647.
|
|
79
79
|
3. **`defaultLocale`** — last-resort fallback.
|
|
80
80
|
|
|
81
|
+
**One resolver decides, and everything the server renders for that request reads its answer** — the page render and its `<I18nProvider>`, the `<html lang>` attribute, the ISR cache key (so a language switch cannot re-serve the previous locale's cached HTML), and the validation errors an [`<AutoForm>` renders on the no-JavaScript form-POST path](/docs/ui/forms-and-tables#forms-without-javascript). That last one is worth naming because a server has no `<html lang>` to read yet at the time it validates; deriving the locale a second way there would answer `en` for every request.
|
|
82
|
+
|
|
81
83
|
The resolved locale is **guaranteed** to be one of the codes in `locales`. Any unsupported value (a cookie pointing at a code you no longer ship, a browser asking for `xx-YY`) falls through to the next signal. RFC 4647 lookup strips subtags one segment at a time — `de-CH-1996` → `de-CH` → `de` — so a `de` catalog serves a `de-CH` browser.
|
|
82
84
|
|
|
83
85
|
The client **adopts what the server resolved**, reading it from the `<html lang>` attribute the server render sets, then falling back to the cookie and the default. `Accept-Language` is never read in the browser: `navigator.languages` can diverge from what the server saw.
|
|
@@ -96,7 +98,7 @@ The client **adopts what the server resolved**, reading it from the `<html lang>
|
|
|
96
98
|
| `data-voltro-tz` | the IANA zone every date/time formatter renders in — set `timeZone` in `app.config.ts` |
|
|
97
99
|
| `data-voltro-now` | the server's render instant, so `useRelativeTime` produces the same string in the hydration pass |
|
|
98
100
|
|
|
99
|
-
Locale was already agreed; the zone and the clock were each read from the ambient runtime, which meant a server-rendered timestamp was a hydration mismatch waiting for a wide enough offset or a slow enough connection. See [Plurals & formatting → Timezones under SSR](/docs/i18n/formatting#timezones-under-ssr
|
|
101
|
+
Locale was already agreed; the zone and the clock were each read from the ambient runtime, which meant a server-rendered timestamp was a hydration mismatch waiting for a wide enough offset or a slow enough connection. See [Plurals & formatting → Timezones under SSR](/docs/i18n/formatting#timezones-under-ssr-the-setting-that-is-not-a-preference) — that is the page to read before you migrate hand-rolled `toLocaleString()` calls onto the hooks.
|
|
100
102
|
|
|
101
103
|
See [Catalogs](/docs/i18n/catalogs) for the type-safe catalog convention and the component hooks, [Plurals & formatting](/docs/i18n/formatting) for CLDR plural selection and the `Intl`-backed date / number / relative-time hooks, and [URL strategies](/docs/i18n/url-strategies) for cookie-only vs URL-prefix routing.
|
|
102
104
|
|
|
@@ -951,7 +953,7 @@ default, so call sites don't handle a missing-service error.
|
|
|
951
953
|
> request, publishes it on the document, and every `@voltro/i18n` formatter on
|
|
952
954
|
> both sides of the hydration boundary uses it. Read it with `useTimeZone()`
|
|
953
955
|
> from `@voltro/i18n`. See
|
|
954
|
-
> [Formatting → Timezones under SSR](/docs/i18n/formatting#timezones-under-ssr
|
|
956
|
+
> [Formatting → Timezones under SSR](/docs/i18n/formatting#timezones-under-ssr-the-setting-that-is-not-a-preference).
|
|
955
957
|
>
|
|
956
958
|
> The **server-side compute zone** — what `startOfDay` or a workflow's "same
|
|
957
959
|
> time tomorrow" resolves against inside a handler — is still the SEAM only.
|
|
@@ -505,7 +505,7 @@ export default defineWebSocket({
|
|
|
505
505
|
})
|
|
506
506
|
```
|
|
507
507
|
|
|
508
|
-
`auth: 'subject'` authenticates through the same chain as rpc/SSR **before** the upgrade (401 while it is still http) and binds the connection to the credential's expiry (close code `4001`); every gateway path is origin-checked at upgrade. Two gateways on one path refuse the boot; a plain GET on a gateway path answers `426`. App realtime stays [subscriptions](/docs/data/subscriptions) — full detail under [Raw WebSocket gateways](/docs/data/subscriptions#raw-websocket-gateways
|
|
508
|
+
`auth: 'subject'` authenticates through the same chain as rpc/SSR **before** the upgrade (401 while it is still http) and binds the connection to the credential's expiry (close code `4001`); every gateway path is origin-checked at upgrade. Two gateways on one path refuse the boot; a plain GET on a gateway path answers `426`. App realtime stays [subscriptions](/docs/data/subscriptions) — full detail under [Raw WebSocket gateways](/docs/data/subscriptions#raw-websocket-gateways-definewebsocket).
|
|
509
509
|
|
|
510
510
|
## The web side (`apps/*/web/`)
|
|
511
511
|
|
|
@@ -518,6 +518,10 @@ export default defineWebSocket({
|
|
|
518
518
|
| `src/pages/loading.tsx` | Pending UI shown while loaders resolve. |
|
|
519
519
|
| `src/pages/(group)/` | Route group — does not contribute a URL segment, but layout/error files inside still apply. |
|
|
520
520
|
| `src/*.island.tsx` | A client-side hydration island (split chunk). Used in `interactive: 'islands'` pages. |
|
|
521
|
+
| `src/**/*.collection.ts` | A content collection declaration (`defineCollection`) — schema-typed markdown/JSON under `content/<name>/**`. See [Content collections](/docs/data/content-collections). |
|
|
522
|
+
| `src/**/*.consumer.ts` | A queue consumer (`defineQueueConsumer`, @voltro/plugin-queue) — Schema-decoded, at-least-once, serial per partition. See [Queue](/docs/plugins/queue). |
|
|
523
|
+
| `grpc.manifest.json` | The gRPC field-number manifest (checked in — append-only wire identity; deletes go `reserved`). See [gRPC surface](/docs/data/grpc). |
|
|
524
|
+
| `content/<name>/**` | A collection's content files (markdown with frontmatter, or `.json` for data collections). Read by `getCollection`/`getEntry`. |
|
|
521
525
|
|
|
522
526
|
Each page can opt into a render strategy via two exports:
|
|
523
527
|
|
|
@@ -540,6 +544,16 @@ export const searchParams = Schema.Struct({
|
|
|
540
544
|
|
|
541
545
|
- `searchParams` (an `effect/Schema` struct — every field optional or with a default) types the page's query string: `useSearchParams(searchParams)` returns the decoded shape, and links built with `withQuery` type-check against it. Details: [Pages → Query strings](/docs/routing/pages#query-strings).
|
|
542
546
|
|
|
547
|
+
Two more page exports change what the framework produces for a route:
|
|
548
|
+
|
|
549
|
+
```tsx
|
|
550
|
+
export const ogImage = ({ params, loaderData, locale }) => ({ type: 'div', props: { /* satori JSX */ } })
|
|
551
|
+
export const intercept = { from: '/photos' }
|
|
552
|
+
```
|
|
553
|
+
|
|
554
|
+
- `ogImage` declares the page's `og:image` as a satori JSX template. `static` pages render the PNG at BUILD time into `dist/assets/og/`; `ssr` pages render it on demand over a signed route. The `og:image` / `twitter:image` / `twitter:card` tags are injected automatically unless your own `meta` already sets them. A declared font is REQUIRED (there is no bundled default), and an `ssr` route exporting it needs `VOLTRO_OG_SECRET` — `voltro start` refuses the boot otherwise. Details: [Loaders and meta → OG images](/docs/routing/loaders-and-meta#og-images-from-a-template-ogimage).
|
|
555
|
+
- `intercept` makes the page an **intercepting route**: `from` names one or more ROUTE PATTERNS (`'/photos'`, `['/photos', '/albums/[id]']`), and a soft navigation arriving from one of them renders this page as an overlay above the still-mounted origin. Every hard load — and a soft navigation from anywhere else — renders it standalone. Details: [Intercepting routes](/docs/routing/intercepting-routes).
|
|
556
|
+
|
|
543
557
|
## Discovery in practice
|
|
544
558
|
|
|
545
559
|
```text
|
|
@@ -599,11 +613,28 @@ If yes, the promise belongs in the name — you cannot see a contract before you
|
|
|
599
613
|
| `*.tracking.ts` | analytics happens nowhere else | `tracking/outside-tracking-file` |
|
|
600
614
|
| `*.client.ts` | it and its imports are browser-safe | boot-time import walk, `client/not-browser-safe` |
|
|
601
615
|
| `*.store.ts` | exactly one `defineStore`, no server state | `store/one-per-file`, `store/mirrors-server-state` |
|
|
616
|
+
| `*.collection.ts` | declares content collections (`defineCollection`); frontmatter schema violations fail the build naming the file | the build's collection decode + reference validation |
|
|
617
|
+
| `*.consumer.ts` | declares queue consumers (`defineQueueConsumer`, @voltro/plugin-queue); loading registers, the plugin's activation starts them | the queue runner (decode→DLQ, retry→DLQ, commit-per-message) |
|
|
618
|
+
| `*.ws.ts` | default-exports one raw WebSocket gateway (`defineWebSocket`), mounting its own upgrade path beside the rpc socket | boot discovery on BOTH paths (`voltro dev` and `voltro serve`); two gateways on one path refuse the boot |
|
|
602
619
|
|
|
603
620
|
A `*.component.tsx` promises exactly ONE component. It does not promise to export nothing else: types, and plain module-local values a `const COLUMNS = […]` beside the table that renders them, are fine and always were. What the rule counts is components — a declaration that renders — so an object, an array, a string or a `new` beside your component is not a second one, and neither is `export default Card` next to `export const Card`.
|
|
604
621
|
|
|
605
622
|
The BOUNDARY rules (`internal/foreign-import`, `fixture/production-import`, `ui/unlinked`) are assertions about your import graph, so it is worth knowing which edges they follow: relative specifiers, your tsconfig `paths` aliases (read from the nearest `tsconfig.json`, so a per-app `@/*` works when you run `voltro doctor` at the repo root), `export … from` re-exports, and dynamic `import()`. A package import is a leaf — the walk stops at the edge of your app.
|
|
606
623
|
|
|
624
|
+
## Contracts that are not suffixes
|
|
625
|
+
|
|
626
|
+
The admission test above is about the PROMISE, not about the spelling — and three of the framework's conventions carry one without being a suffix on a filename. They are listed here because a reader looking for "what does the framework read out of my tree" would otherwise stop at the table:
|
|
627
|
+
|
|
628
|
+
| Convention | Promise | Read by |
|
|
629
|
+
|---|---|---|
|
|
630
|
+
| `searchParams` page export | the page's query string decodes through this `effect/Schema` struct — every field optional or with a default | `useSearchParams(searchParams)`, `withQuery` link typing, and the render-mode scan (a page declaring BOTH `renderMode: 'isr'` and `searchParams` is refused) |
|
|
631
|
+
| `ogImage` page export | this route's `og:image` is a satori JSX template, not a file you ship | the build (`static` → a hashed PNG in `dist/assets/og/`) and `voltro start` (`ssr` → a signed on-demand route, which needs `VOLTRO_OG_SECRET`) |
|
|
632
|
+
| `intercept` page export | `from` names the routes a soft navigation may arrive from for this page to render as an overlay above them | the client router; a hard load renders the page standalone regardless |
|
|
633
|
+
| `grpc.manifest.json` (app root) | field numbers are checked in and append-only — a deleted field goes `reserved`, never re-used | `voltro grpc proto` and the gRPC surface wiring, which derive wire identity from it rather than from declaration order |
|
|
634
|
+
| `content/<name>/**` | the files a `*.collection.ts` declares — markdown with frontmatter, or `.json` for a data collection | `getCollection` / `getEntry`, the build's collection artifacts, and the dev server's watcher |
|
|
635
|
+
|
|
636
|
+
The page exports are per-ROUTE and the last two are per-APP, which is the only reason they cannot be spellings: there is nothing to rename.
|
|
637
|
+
|
|
607
638
|
## `*.component.ui.tsx` — reads, never writes
|
|
608
639
|
|
|
609
640
|
```tsx
|