@voltro/cli 0.16.0 → 0.17.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 +29 -0
- package/dist/{apiBuild-BJtiqoOu.js → apiBuild-ChdlLqGv.js} +2 -2
- package/dist/apiBuild-D5WB119b.js +2 -0
- package/dist/bin.js +3 -3
- package/dist/{commands-8A5RU3MW.js → commands-DYQbv-DG.js} +2328 -2292
- package/dist/dbCommand-C3R5LrBZ.js +2 -0
- package/dist/{dbCommand-OPMtO0sX.js → dbCommand-DJSxnjPt.js} +2 -2
- package/dist/{dev-HdG7JdSk.js → dev-BCmoJfBm.js} +1 -1
- package/dist/{dev-DGjEvvkV.js → dev-DB7pbLob.js} +89 -89
- package/dist/{fileConventions-Cu0tM-Bl.js → fileConventions-CBNPZVOV.js} +1 -1
- package/dist/frameworkTableAssembly-DRJrAVr1.js +2 -0
- package/dist/{frameworkTableAssembly-BA2bAmYN.js → frameworkTableAssembly-sg41s14F.js} +1 -1
- package/dist/index.js +1 -1
- package/dist/{inspectMetrics-CYDSO3za.js → inspectMetrics-DWh56Qas.js} +205 -205
- package/dist/{serveCommand-C3lq3mFd.js → serveCommand-DO6qr5Ok.js} +5 -5
- package/dist/serveEntry.js +2 -2
- package/dist/{start-BtgatJN3.js → start-ksY0wMZG.js} +379 -379
- package/dist/startEntry.js +2 -2
- package/package.json +17 -17
- package/templates/AGENTS.core.md +24 -9
- package/templates/AGENTS.md +25 -10
- package/templates/agent-docs/_index.md +1 -1
- package/templates/agent-docs/cli.md +2 -2
- package/templates/agent-docs/database/migrations.md +3 -3
- package/templates/agent-docs/internationalization.md +3 -3
- package/templates/agent-docs/introduction.md +2 -2
- package/templates/agent-docs/reference.md +4 -4
- package/templates/agent-docs/routing.md +40 -40
- package/templates/agent-docs/templates/appshells.md +24 -24
- package/templates/agent-docs/whats-new.md +31 -95
- package/templates/apps/api-ai/package.json +7 -7
- 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-storage/package.json +8 -8
- 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-saas/package.json +11 -11
- package/templates/apps/api-search/package.json +8 -8
- package/templates/apps/api-versioning/package.json +8 -8
- package/templates/apps/api-webhooks/package.json +9 -9
- package/templates/apps/changelog/package.json +6 -6
- package/templates/apps/changelog/src/pages/[locale]/{[slug].page.tsx → [slug]/page.tsx} +4 -4
- package/templates/apps/changelog/src/pages/[locale]/mirrors.test.tsx +2 -2
- package/templates/apps/changelog/src/pages/[locale]/{index.page.tsx → page.tsx} +2 -2
- package/templates/apps/changelog/src/pages/{[slug].page.test.tsx → [slug]/page.test.tsx} +1 -1
- package/templates/apps/changelog/src/pages/{[slug].page.tsx → [slug]/page.tsx} +2 -2
- package/templates/apps/changelog/src/pages/{index.page.test.tsx → page.test.tsx} +1 -1
- package/templates/apps/edge-functions/package.json +2 -2
- package/templates/apps/frontend-admin/package.json +8 -8
- package/templates/apps/frontend-admin/src/pages/(marketing)/{login.page.test.tsx → login/page.test.tsx} +2 -2
- package/templates/apps/frontend-admin/src/pages/(marketing)/{login.page.tsx → login/page.tsx} +2 -2
- package/templates/apps/frontend-admin/src/pages/(marketing)/{index.page.test.tsx → page.test.tsx} +1 -1
- package/templates/apps/frontend-admin/src/pages/admin/{[entity].page.tsx → [entity]/page.tsx} +1 -1
- package/templates/apps/frontend-admin/src/pages/admin/entity.test.tsx +1 -1
- package/templates/apps/frontend-admin/src/pages/admin/{index.page.test.tsx → page.test.tsx} +1 -1
- package/templates/apps/frontend-app/package.json +8 -8
- package/templates/apps/frontend-app/src/pages/{index.page.test.tsx → page.test.tsx} +1 -1
- package/templates/apps/frontend-app/src/pages/{schema-ui.page.test.tsx → schema-ui/page.test.tsx} +2 -2
- package/templates/apps/frontend-app/src/pages/{schema-ui.page.tsx → schema-ui/page.tsx} +1 -1
- package/templates/apps/frontend-blank/package.json +7 -7
- package/templates/apps/frontend-blank/src/pages/{index.page.test.tsx → page.test.tsx} +1 -1
- package/templates/apps/frontend-contact/package.json +7 -7
- package/templates/apps/frontend-contact/src/pages/[locale]/{index.page.tsx → page.tsx} +2 -2
- package/templates/apps/frontend-contact/src/pages/{index.page.test.tsx → page.test.tsx} +1 -1
- package/templates/apps/frontend-dashboard/package.json +7 -7
- package/templates/apps/frontend-dashboard/src/pages/(marketing)/{login.page.test.tsx → login/page.test.tsx} +2 -2
- package/templates/apps/frontend-dashboard/src/pages/(marketing)/{login.page.tsx → login/page.tsx} +2 -2
- package/templates/apps/frontend-dashboard/src/pages/(marketing)/{index.page.test.tsx → page.test.tsx} +1 -1
- package/templates/apps/frontend-dashboard/src/pages/dashboard/{index.page.test.tsx → page.test.tsx} +1 -1
- package/templates/apps/frontend-dashboard/src/pages/dashboard/{settings.page.test.tsx → settings/page.test.tsx} +2 -2
- package/templates/apps/frontend-dashboard/src/pages/dashboard/{settings.page.tsx → settings/page.tsx} +2 -2
- package/templates/apps/frontend-docs/package.json +7 -7
- package/templates/apps/frontend-docs/src/pages/[locale]/docs/{[...slug].page.tsx → [...slug]/page.tsx} +4 -4
- package/templates/apps/frontend-docs/src/pages/[locale]/mirrors.test.tsx +2 -2
- package/templates/apps/frontend-docs/src/pages/[locale]/{index.page.tsx → page.tsx} +2 -2
- package/templates/apps/frontend-docs/src/pages/docs/{[...slug].page.test.tsx → [...slug]/page.test.tsx} +3 -3
- package/templates/apps/frontend-docs/src/pages/docs/{[...slug].page.tsx → [...slug]/page.tsx} +1 -1
- package/templates/apps/frontend-docs/src/pages/{index.page.test.tsx → page.test.tsx} +1 -1
- package/templates/apps/frontend-i18n/package.json +6 -6
- package/templates/apps/frontend-i18n/src/pages/[locale]/{about.page.tsx → about/page.tsx} +3 -3
- package/templates/apps/frontend-i18n/src/pages/[locale]/mirrors.test.tsx +2 -2
- package/templates/apps/frontend-i18n/src/pages/[locale]/{index.page.tsx → page.tsx} +2 -2
- package/templates/apps/frontend-i18n/src/pages/{about.page.test.tsx → about/page.test.tsx} +3 -3
- package/templates/apps/frontend-i18n/src/pages/{about.page.tsx → about/page.tsx} +1 -1
- package/templates/apps/frontend-i18n/src/pages/{index.page.test.tsx → page.test.tsx} +1 -1
- package/templates/apps/frontend-landing/package.json +7 -7
- package/templates/apps/frontend-landing/src/pages/[locale]/mirrors.test.tsx +1 -1
- package/templates/apps/frontend-landing/src/pages/[locale]/{index.page.tsx → page.tsx} +2 -2
- package/templates/apps/frontend-landing/src/pages/{index.page.test.tsx → page.test.tsx} +1 -1
- package/templates/apps/frontend-spa/package.json +7 -7
- package/templates/apps/frontend-spa/src/pages/{index.page.test.tsx → page.test.tsx} +1 -1
- package/templates/apps/frontend-ssr/package.json +7 -7
- package/templates/apps/frontend-ssr/src/pages/{feed.page.test.tsx → feed/page.test.tsx} +2 -2
- package/templates/apps/frontend-ssr/src/pages/{feed.page.tsx → feed/page.tsx} +1 -1
- package/templates/apps/frontend-ssr/src/pages/{feed-swr.page.test.tsx → feed-swr/page.test.tsx} +2 -2
- package/templates/apps/frontend-ssr/src/pages/{feed-swr.page.tsx → feed-swr/page.tsx} +1 -1
- package/templates/apps/frontend-ssr/src/pages/{index.page.test.tsx → page.test.tsx} +1 -1
- package/templates/apps/frontend-ssr-api/package.json +8 -8
- package/templates/apps/frontend-ssr-api/src/pages/{index.page.test.tsx → page.test.tsx} +1 -1
- package/templates/apps/frontend-static-blog/package.json +6 -6
- package/templates/apps/frontend-static-blog/src/pages/[locale]/blog/{[slug].page.tsx → [slug]/page.tsx} +4 -4
- package/templates/apps/frontend-static-blog/src/pages/[locale]/mirrors.test.tsx +2 -2
- package/templates/apps/frontend-static-blog/src/pages/[locale]/{index.page.tsx → page.tsx} +2 -2
- package/templates/apps/frontend-static-blog/src/pages/blog/{[slug].page.test.tsx → [slug]/page.test.tsx} +3 -3
- package/templates/apps/frontend-static-blog/src/pages/blog/{[slug].page.tsx → [slug]/page.tsx} +3 -3
- package/templates/apps/frontend-static-blog/src/pages/{index.page.test.tsx → page.test.tsx} +1 -1
- package/dist/apiBuild-BOczx9-m.js +0 -2
- package/dist/dbCommand-DNQ6l-su.js +0 -2
- package/dist/frameworkTableAssembly-D1zJSgOU.js +0 -2
- /package/templates/apps/changelog/src/pages/{index.page.tsx → page.tsx} +0 -0
- /package/templates/apps/frontend-admin/src/pages/(marketing)/{index.page.tsx → page.tsx} +0 -0
- /package/templates/apps/frontend-admin/src/pages/admin/{index.page.tsx → page.tsx} +0 -0
- /package/templates/apps/frontend-app/src/pages/{index.page.tsx → page.tsx} +0 -0
- /package/templates/apps/frontend-blank/src/pages/{index.page.tsx → page.tsx} +0 -0
- /package/templates/apps/frontend-contact/src/pages/{index.page.tsx → page.tsx} +0 -0
- /package/templates/apps/frontend-dashboard/src/pages/(marketing)/{index.page.tsx → page.tsx} +0 -0
- /package/templates/apps/frontend-dashboard/src/pages/dashboard/{index.page.tsx → page.tsx} +0 -0
- /package/templates/apps/frontend-docs/src/pages/{index.page.tsx → page.tsx} +0 -0
- /package/templates/apps/frontend-i18n/src/pages/{index.page.tsx → page.tsx} +0 -0
- /package/templates/apps/frontend-landing/src/pages/{index.page.tsx → page.tsx} +0 -0
- /package/templates/apps/frontend-spa/src/pages/{index.page.tsx → page.tsx} +0 -0
- /package/templates/apps/frontend-ssr/src/pages/{index.page.tsx → page.tsx} +0 -0
- /package/templates/apps/frontend-ssr-api/src/pages/{index.page.tsx → page.tsx} +0 -0
- /package/templates/apps/frontend-static-blog/src/pages/{index.page.tsx → page.tsx} +0 -0
|
@@ -39,7 +39,7 @@ apps/acme/web/ # dir named by the app, not the template
|
|
|
39
39
|
|
|
40
40
|
## The page
|
|
41
41
|
|
|
42
|
-
`src/pages/
|
|
42
|
+
`src/pages/page.tsx` is plain JSX with the two static-render exports:
|
|
43
43
|
|
|
44
44
|
```tsx
|
|
45
45
|
export const renderMode = 'static' as const // pre-render at build time
|
|
@@ -59,7 +59,7 @@ export default function Index() {
|
|
|
59
59
|
</section>
|
|
60
60
|
<section>
|
|
61
61
|
<h2>Ready to start?</h2>
|
|
62
|
-
<p>Edit <code>src/pages/
|
|
62
|
+
<p>Edit <code>src/pages/page.tsx</code> to make it yours.</p>
|
|
63
63
|
</section>
|
|
64
64
|
</main>
|
|
65
65
|
)
|
|
@@ -80,7 +80,7 @@ export default island(SignupForm, { name: 'SignupForm', hydrate: 'visible' })
|
|
|
80
80
|
```
|
|
81
81
|
|
|
82
82
|
```tsx
|
|
83
|
-
// src/pages/
|
|
83
|
+
// src/pages/page.tsx
|
|
84
84
|
export const interactive = 'islands' as const // was 'none'
|
|
85
85
|
import SignupForm from '../components/SignupForm.island'
|
|
86
86
|
// … render <SignupForm /> somewhere in the page …
|
|
@@ -185,7 +185,7 @@ If your final product is going to be a:
|
|
|
185
185
|
|
|
186
186
|
_A documentation site with a catch-all docs router and build-time static pre-rendering of every page via getStaticPaths._
|
|
187
187
|
|
|
188
|
-
A documentation-site starter. `src/pages/docs/[...slug]
|
|
188
|
+
A documentation-site starter. `src/pages/docs/[...slug]/page.tsx` is a catch-all router; `getStaticPaths` enumerates every doc URL at build time so `voltro build` pre-renders them all into a static site you can deploy anywhere. Template id: **`frontend-docs`**.
|
|
189
189
|
|
|
190
190
|
The starter ships a tiny in-file `DOCS` array as stub content. Replace it with a real loader (a markdown reader, a CMS query, a database fetch) — the page component's contract stays the same.
|
|
191
191
|
|
|
@@ -215,7 +215,7 @@ apps/acme/docs/ # dir named by the app, not the template
|
|
|
215
215
|
|
|
216
216
|
## The catch-all renderer
|
|
217
217
|
|
|
218
|
-
`src/pages/docs/[...slug]
|
|
218
|
+
`src/pages/docs/[...slug]/page.tsx` is the whole engine. It declares:
|
|
219
219
|
|
|
220
220
|
```tsx
|
|
221
221
|
export const renderMode = 'static' as const // pre-render at build
|
|
@@ -418,7 +418,7 @@ No rpc client, no loader, no `apis:` entry, no `@voltro/ui-shadcn` dependency. T
|
|
|
418
418
|
The page exports `renderMode = 'spa'`, which tells `voltro build` not to pre-render it: the client renders it on load. Use this when the whole page is interactive AND its state lives in the browser — an SSR'd first paint would just be discarded on hydration, so rendering it on the server buys nothing.
|
|
419
419
|
|
|
420
420
|
```tsx
|
|
421
|
-
// src/pages/
|
|
421
|
+
// src/pages/page.tsx — a pure client-side SPA (a bill splitter)
|
|
422
422
|
import type { ReactNode } from 'react'
|
|
423
423
|
import { useEffect, useState } from 'react'
|
|
424
424
|
|
|
@@ -603,7 +603,7 @@ The full reference for these modes lives in the [render modes guide](/docs/routi
|
|
|
603
603
|
`index.tsx` exports `renderMode = 'ssr'`. Its loader runs SERVER-SIDE on EVERY request under `voltro start` — the timestamp and nonce change on each refresh, and the loader can read the incoming request to personalise the HTML before it's sent. There is no caching: the response carries `x-voltro-rendered-by: ssr`.
|
|
604
604
|
|
|
605
605
|
```tsx
|
|
606
|
-
// src/pages/
|
|
606
|
+
// src/pages/page.tsx
|
|
607
607
|
import type { ReactNode } from 'react'
|
|
608
608
|
import { useLoaderData, useServerRequest, type LoaderFn, type PageMeta } from '@voltro/web'
|
|
609
609
|
|
|
@@ -646,7 +646,7 @@ Use SSR for anything that varies per request: a logged-in dashboard, a page that
|
|
|
646
646
|
`feed.tsx` takes the SAME render path as SSR, but `voltro start` CACHES the HTML and serves the cached copy to every request within the `revalidate` window. After the window expires the next request re-renders and replaces the cache — cache hits return in well under a millisecond. `tenantAware: true` folds the `x-tenant` request header into the cache key, so tenant A's render is never served to tenant B:
|
|
647
647
|
|
|
648
648
|
```tsx
|
|
649
|
-
// src/pages/feed
|
|
649
|
+
// src/pages/feed/page.tsx
|
|
650
650
|
import type { ReactNode } from 'react'
|
|
651
651
|
import { useLoaderData, type LoaderFn, type PageMeta } from '@voltro/web'
|
|
652
652
|
|
|
@@ -685,7 +685,7 @@ The loader's `headers` is populated server-side (lowercased keys) — that's how
|
|
|
685
685
|
`feed-swr.tsx` is the same ISR page plus a `staleWhileRevalidate` window. When `revalidate` expires, instead of blocking the next visitor on a fresh render, the cache serves the STALE HTML immediately AND kicks off a background re-render. The visitor never waits; the cache catches up out-of-band. Beyond `revalidate + staleWhileRevalidate` the next request blocks on a fresh render, like a cold MISS:
|
|
686
686
|
|
|
687
687
|
```tsx
|
|
688
|
-
// src/pages/feed-swr
|
|
688
|
+
// src/pages/feed-swr/page.tsx
|
|
689
689
|
import type { ReactNode } from 'react'
|
|
690
690
|
import { useLoaderData, type LoaderFn, type PageMeta } from '@voltro/web'
|
|
691
691
|
|
|
@@ -878,10 +878,10 @@ export const posts: ReadonlyArray<Post> = [
|
|
|
878
878
|
|
|
879
879
|
## SSG dynamic routes — `getStaticPaths` + `loader` + per-post `meta`
|
|
880
880
|
|
|
881
|
-
A dynamic route like `src/pages/blog/[slug]
|
|
881
|
+
A dynamic route like `src/pages/blog/[slug]/page.tsx` matches infinitely many URLs. To pre-render it statically the build needs to know WHICH slugs exist — that is what `getStaticPaths` answers, by mapping over the content source. For each enumerated `params` the build runs the `loader` (server-side, at build time) and renders one HTML file. The `meta` export is a FUNCTION of the loader data, so each post gets its own correct `<title>` / `<description>` baked into the HTML — exactly what you want for SEO and social cards.
|
|
882
882
|
|
|
883
883
|
```tsx
|
|
884
|
-
// src/pages/blog/[slug]
|
|
884
|
+
// src/pages/blog/[slug]/page.tsx
|
|
885
885
|
import type { ReactNode } from 'react'
|
|
886
886
|
import { useLoaderData, notFound, type LoaderFn, type PageMeta } from '@voltro/web'
|
|
887
887
|
import { posts, type Post } from '../../content/posts'
|
|
@@ -972,7 +972,7 @@ export default island(ReadingProgress, { name: 'ReadingProgress', hydrate: 'load
|
|
|
972
972
|
The index at `/` is pure content: a list of posts with no interactivity. It declares `interactive: 'none'`, which makes the framework strip EVERY `<script>` it would otherwise emit — the HTML ships with no JS bundle at all. It reads the same content source the `[slug]` page + `getStaticPaths` use:
|
|
973
973
|
|
|
974
974
|
```tsx
|
|
975
|
-
// src/pages/
|
|
975
|
+
// src/pages/page.tsx
|
|
976
976
|
import type { ReactNode } from 'react'
|
|
977
977
|
import type { PageMeta } from '@voltro/web'
|
|
978
978
|
import { posts } from '../content/posts'
|
|
@@ -1106,10 +1106,10 @@ The serverless function lives next to the web app but is a separate deploy artif
|
|
|
1106
1106
|
|
|
1107
1107
|
## The static page + the island form
|
|
1108
1108
|
|
|
1109
|
-
`src/pages/
|
|
1109
|
+
`src/pages/page.tsx` is `renderMode: 'static'` (pre-rendered, servable from a CDN) and `interactive: 'islands'` — so only the `<ContactForm>` island hydrates; the headline + copy ship as inert HTML with no React lifecycle:
|
|
1110
1110
|
|
|
1111
1111
|
```tsx
|
|
1112
|
-
// src/pages/
|
|
1112
|
+
// src/pages/page.tsx
|
|
1113
1113
|
import type { ReactNode } from 'react'
|
|
1114
1114
|
import type { PageMeta } from '@voltro/web'
|
|
1115
1115
|
import ContactForm from '../components/ContactForm.island'
|
|
@@ -1358,7 +1358,7 @@ apps/demo/web/
|
|
|
1358
1358
|
├── README.md
|
|
1359
1359
|
└── src/pages/
|
|
1360
1360
|
├── layout.tsx # root shell (header + <main>)
|
|
1361
|
-
└──
|
|
1361
|
+
└── page.tsx # the reactive page — useSubscription + useMutation
|
|
1362
1362
|
```
|
|
1363
1363
|
|
|
1364
1364
|
## The `apis` wiring
|
|
@@ -1390,7 +1390,7 @@ The `package` resolves the api workspace package; its port is auto-discovered fr
|
|
|
1390
1390
|
## The reactive loop
|
|
1391
1391
|
|
|
1392
1392
|
```tsx
|
|
1393
|
-
// src/pages/
|
|
1393
|
+
// src/pages/page.tsx
|
|
1394
1394
|
import { useMutation, useSubscription } from '@voltro/client'
|
|
1395
1395
|
|
|
1396
1396
|
const TENANT = 'acme'
|
|
@@ -1418,7 +1418,7 @@ The subscription builder also exposes `revision`, `emittedAt`, `error`, and `pen
|
|
|
1418
1418
|
|
|
1419
1419
|
## Pairing with a different api
|
|
1420
1420
|
|
|
1421
|
-
This page is wired to api-backend's `notes` domain. To point it at your own api, change `apis.app.package` in `app.config.ts`, then swap the rpc tags + the row type in `src/pages/
|
|
1421
|
+
This page is wired to api-backend's `notes` domain. To point it at your own api, change `apis.app.package` in `app.config.ts`, then swap the rpc tags + the row type in `src/pages/page.tsx` to match your descriptors. The hook shapes are identical for any api.
|
|
1422
1422
|
|
|
1423
1423
|
## When to use
|
|
1424
1424
|
|
|
@@ -1533,7 +1533,7 @@ export const withLocalePrefix = (path: string, locale: string): string =>
|
|
|
1533
1533
|
Each page has a one-line **mirror** under `src/pages/[locale]/`. The mirror re-exports the bare page verbatim (the component reads its locale from the URL via the layout's provider) and uses `getStaticPaths` to enumerate the *non-default* locales — which is what tells `voltro build` to emit the `/de/...` HTML. Without the mirror, only the default-locale URL is built.
|
|
1534
1534
|
|
|
1535
1535
|
```tsx
|
|
1536
|
-
// src/pages/[locale]/
|
|
1536
|
+
// src/pages/[locale]/page.tsx — the entire file
|
|
1537
1537
|
import { SUPPORTED_LOCALES, DEFAULT_LOCALE } from '../../lib/locale'
|
|
1538
1538
|
|
|
1539
1539
|
export { default } from '../index'
|
|
@@ -1550,7 +1550,7 @@ export const getStaticPaths = async (): Promise<Array<{ params: { locale: string
|
|
|
1550
1550
|
The body of each page is localised automatically (the SSG pipeline wraps every variant in the right catalog's provider). The document head is NOT — unless you export `meta` as a **function of `{ locale }`**. The framework drives `locale` from `params.locale` on the `[locale]` route (and `defaultLocale` on the bare path), so each variant bakes the right `<title>` / `<meta description>` into its HTML — exactly what you want for SEO and social cards. A plain `meta` object would leave the head English on `/de`.
|
|
1551
1551
|
|
|
1552
1552
|
```tsx
|
|
1553
|
-
// src/pages/
|
|
1553
|
+
// src/pages/page.tsx
|
|
1554
1554
|
import { T, useT, useLocale } from '@voltro/i18n'
|
|
1555
1555
|
import { getCatalog } from '../lib/locale'
|
|
1556
1556
|
|
|
@@ -1662,7 +1662,7 @@ The `[locale]` mirrors pick it up automatically — `getStaticPaths` reads `SUPP
|
|
|
1662
1662
|
|
|
1663
1663
|
## Anti-patterns
|
|
1664
1664
|
|
|
1665
|
-
- **Forgetting the `[locale]` mirror for a new page.** Create `src/pages/foo
|
|
1665
|
+
- **Forgetting the `[locale]` mirror for a new page.** Create `src/pages/foo/page.tsx` AND a one-line mirror `src/pages/[locale]/foo/page.tsx`. Without the mirror, only the default-locale URL is emitted — `/de/foo` 404s.
|
|
1666
1666
|
- **Exporting `meta` as a plain object.** The body localises, but the `<title>` / `<meta description>` stay English on `/de`. Export `meta` as a function of `{ locale }` and read the catalog via `getCatalog(locale)`.
|
|
1667
1667
|
- **Skipping `defineLocale<typeof en>()` for a non-base catalog.** Plain `defineCatalog(...)` compiles but loses the parity check — the one feature that stops translations drifting. Always run non-base locales through `defineLocale<typeof en>()`.
|
|
1668
1668
|
- **Angle brackets in catalog strings.** react-intl's ICU parser treats `<tag>` as a rich-text element and fails (`FORMAT_ERROR`) if there's no matching close tag. Write "page titles", not "`<title>` tags", in a message.
|
|
@@ -1759,7 +1759,7 @@ export default function DashboardLayout({ children }) {
|
|
|
1759
1759
|
The gate must run **per request** — it reads the request's cookie and may redirect. A `static` page is pre-rendered once at build time with no per-visitor render, so it can't redirect per-visitor; in fact `voltro build` **rejects** a `RedirectError` thrown during static pre-render. So the authed pages declare `renderMode: 'ssr'`:
|
|
1760
1760
|
|
|
1761
1761
|
```tsx
|
|
1762
|
-
// src/pages/dashboard/
|
|
1762
|
+
// src/pages/dashboard/page.tsx
|
|
1763
1763
|
export const renderMode = 'ssr' as const
|
|
1764
1764
|
export const meta = { title: 'Overview' }
|
|
1765
1765
|
export default function Overview() { /* … */ }
|
|
@@ -1884,7 +1884,7 @@ export default {
|
|
|
1884
1884
|
`query` is present ONLY server-side (`ssr` / `isr`). It invokes the api's rpc directly over `POST /rpc`, **forwarding the request's session cookie** so the same Subject + tenant resolve as the WebSocket path. A streaming query is drained to its FIRST snapshot — here, the current notes for this tenant. `meta` reads the result, so the `<title>` reflects real data in the server-rendered HTML:
|
|
1885
1885
|
|
|
1886
1886
|
```tsx
|
|
1887
|
-
// src/pages/
|
|
1887
|
+
// src/pages/page.tsx
|
|
1888
1888
|
import { useLoaderData, type LoaderFn, type PageMeta } from '@voltro/web'
|
|
1889
1889
|
import { useSubscription } from '@voltro/client'
|
|
1890
1890
|
|
|
@@ -1994,7 +1994,7 @@ const entities = manifest ? deriveEntityAdmins(manifest) : []
|
|
|
1994
1994
|
## Per-entity CRUD
|
|
1995
1995
|
|
|
1996
1996
|
```tsx
|
|
1997
|
-
// src/pages/admin/[entity]
|
|
1997
|
+
// src/pages/admin/[entity]/page.tsx (abridged)
|
|
1998
1998
|
const canCreate = useCan(spec.createScope) // <table>:create
|
|
1999
1999
|
{spec.createTag && canCreate ? (
|
|
2000
2000
|
<AutoForm api="app" mutation={spec.createTag} submitLabel={`Add ${spec.table}`} />
|
|
@@ -2041,7 +2041,7 @@ apps/acme/web/
|
|
|
2041
2041
|
|
|
2042
2042
|
## Make it yours
|
|
2043
2043
|
|
|
2044
|
-
Everything under `src/pages/admin/` is yours. Swap the cookie gate (`lib/auth.ts`) + demo scopes (`lib/admin.ts`) for real auth; replace the generic `[entity]
|
|
2044
|
+
Everything under `src/pages/admin/` is yours. Swap the cookie gate (`lib/auth.ts`) + demo scopes (`lib/admin.ts`) for real auth; replace the generic `[entity]/page.tsx` with a hand-built page for any entity that needs more than CRUD.
|
|
2045
2045
|
|
|
2046
2046
|
## When to use frontend-admin vs. the other web templates
|
|
2047
2047
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# What's new in 0.
|
|
1
|
+
# What's new in 0.16.0
|
|
2
2
|
|
|
3
3
|
Read this FIRST when a task touches an area you have not worked in recently.
|
|
4
4
|
It is the cheapest way to notice that the framework grew the thing you were
|
|
@@ -7,125 +7,61 @@ workaround for something that shipped two versions ago.
|
|
|
7
7
|
|
|
8
8
|
BREAKING entries name a codemod; run `voltro update` to apply it.
|
|
9
9
|
|
|
10
|
-
### ⚠ BREAKING
|
|
11
|
-
|
|
12
|
-
- **@voltro/cli** — **A rename now carries the ALIASED importers, not only the relative ones.**
|
|
13
|
-
|
|
14
|
-
An app's web build stopped compiling after `voltro update`: 84 relative imports were rewritten correctly, 163 aliased ones across 88 files were not, and `tsc` reported 249 errors on names that no longer existed. Nothing in the codemod's output hinted that a whole class of import had been skipped.
|
|
15
|
-
|
|
16
|
-
Two independent halves, and each alone leaves the imports stale:
|
|
17
|
-
|
|
18
|
-
- The codemod's ts-morph project was built with **no `baseUrl` and no `paths`**, so `@/components/link` resolved to nothing. It now gets the compiler's own shape — the raw `paths`, not the pre-resolved Vite table, because a `paths` target is relative to `baseUrl` by definition and an absolute one does not resolve. - `SourceFile.move()` rewrites relative specifiers and **nothing else**, which is correct on its own terms: ts-morph cannot know whether the alias mapping or the file is meant to change. So the codemods now rewrite the aliased ones themselves, narrowly — only specifiers that RESOLVED to the moved file, and only the trailing stem, which needs no alias table and so cannot disagree with one. The run reports how many it rewrote.
|
|
19
|
-
|
|
20
|
-
**A file an exact `paths` entry names is left alone and reported.** Renaming `link.tsx` while `"@/link": ["src/components/link.tsx"]` points at it leaves the mapping resolving to nothing — and the same path is usually repeated in a vite/vitest alias table no codemod owns. One app hit this and then saw a taxonomy violation reported on a file the codemod had itself created.
|
|
21
|
-
|
|
22
|
-
**`page/unsuffixed-in-pages` is now an error, not a warning.** The comment justifying the warning contradicted the scanner it described: that bucket is already narrowed to a default export nothing imports, which is what an unmigrated page looks like and what a co-located component never does. The failure it names is invisible everywhere else — an unmigrated route simply 404s, with a clean `tsc` and a green suite. One app finished a migration with 51 of them. `codemod: none`: the rename it asks for already ships as `0.14.0/03_pages-suffix`, and nothing about a user's SOURCE changes here — what changes is that `voltro check` now fails on a route that does not route.
|
|
23
|
-
- **@voltro/protocol, @voltro/client, @voltro/web** — **`errorTag` moves from `@voltro/client` to `@voltro/protocol`.**
|
|
24
|
-
|
|
25
|
-
It reads the `_tag` that `toRpc` writes, so it now lives beside `toRpc` — one file owning both ends of that contract. Where it used to live had a cost invisible from inside the framework: an app's shared error handler sat in a package that pulled only `@voltro/i18n`, and reading a tag would have meant depending on the entire client package for seven lines. They declined, and kept parsing message strings with a regex — the exact outcome the helper exists to prevent.
|
|
26
|
-
|
|
27
|
-
`@voltro/web` re-exports the client surface, so it loses the symbol too — the same codemod covers an app that imported it from there.
|
|
28
|
-
|
|
29
|
-
Not re-exported from `@voltro/client`: two import paths for one helper is how the next reader learns the wrong one. The transform codemod repoints the import, preserving an alias (`errorTag as tagOf`) and the type-only form, and merges into an existing `@voltro/protocol` import rather than adding a second.
|
|
30
|
-
|
|
31
|
-
While moving it, its doc comment gained the thing that matters at the call site and was only implied before: **`instanceof` does not hold on the client.** What arrives there was decoded from JSON and never constructed, so match on the tag, not on the class. One team read the old wording as a promise that `instanceof` works and was right to say so.
|
|
32
|
-
|
|
33
10
|
### Added
|
|
34
11
|
|
|
35
|
-
- **@voltro/protocol, @voltro/cli** — **
|
|
36
|
-
|
|
37
|
-
```ts
|
|
38
|
-
const sessionStrategy: AuthStrategy = {
|
|
39
|
-
id: 'db-session',
|
|
40
|
-
resolve: async ({ headers, store }) => {
|
|
41
|
-
if (store === undefined) return { kind: 'skip' } // still booting
|
|
42
|
-
const [row] = await store.query(sessions.byToken(headers.authorization))
|
|
43
|
-
return row ? { kind: 'matched', subject: toSubject(row) } : { kind: 'skip' }
|
|
44
|
-
},
|
|
45
|
-
}
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
Without it, a DB-backed strategy — a session row, an API-key record, a PAT table — had to open a SECOND connection path beside the framework's, to the same database the request store opens a moment later. One adopter's `auth/db.ts` is 105 lines of exactly that: a second `ManagedRuntime` plus a `MysqlClient`, load-bearing for their session lookup and their ApiKeyStore. Every DB-backed OIDC / SAML / PAT integration rebuilds it, which is what made this a framework gap rather than an app's problem.
|
|
49
|
-
|
|
50
|
-
It is the **same value** `auth.resolveScopes` already receives, through the **same lazy getter** — one ref, two consumers, rather than each caller reaching for the store its own way. That is deliberate: `voltro dev` builds the store AFTER the auth chain and `voltro serve` builds it BEFORE, so a value captured at config time would be `undefined` forever in dev and correct in production. The getter is read ONCE per request, not once per strategy.
|
|
12
|
+
- **@voltro/protocol, @voltro/runtime, @voltro/cli, @voltro/database** — **A plugin HTTP route reaches the app's DataStore, on `req.store`** — and `voltro db apply` finally honours `VOLTRO_DESTRUCTIVE_OK`.
|
|
51
13
|
|
|
52
|
-
|
|
14
|
+
### `PluginHttpRouteRequest.store`
|
|
53
15
|
|
|
54
|
-
|
|
16
|
+
The same seam as `AuthStrategyInput.store`, one layer over, and the same report produced it. An adopter's `auth/db.ts` has five consumers: two are auth strategies and collapsed onto `input.store` exactly as designed; three are plugin HTTP routes and could not, so the second `ManagedRuntime` + `MysqlClient` stayed for them.
|
|
55
17
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
- **@voltro/cli** — `voltro dev` terminates when its dev server does, instead of living forever.
|
|
59
|
-
|
|
60
|
-
The supervisor never watched its child die. `runChild` was an `Effect.acquireUseRelease` whose `use` was `Effect.never`, so `proc.on('exit')` existed ONLY in the release path — and that path runs when the fiber is interrupted (a restart, a signal), never when the child exits by itself. Two more places assumed the same thing: `runSupervisor` was typed `Effect<never>`, and `dev.ts` returned `new Promise(() => {})` after starting it.
|
|
61
|
-
|
|
62
|
-
So a boot that aborted — an unreachable database, a refused migration, a failed env gate — left the child dead and the supervisor waiting for a file change that nobody was there to make. Measured, not inferred: one developer machine carried 15 such `voltro dev` process pairs, `ppid=1`, the oldest 7 days old, every one a boot that had failed against a remote database. They hold a watcher and a terminal-less process each; in CI the same shape keeps a runner busy after the job "finished".
|
|
63
|
-
|
|
64
|
-
`use` now awaits the child (`awaitChildExit`) and the supervisor races that against the watch loop, so whichever happens first decides. A restart still does NOT end it — `stopChild` interrupts the fiber, so the deferred is never completed on that path. A child killed by a signal reports `code: null`, which is reported as a failure rather than a clean 0.
|
|
65
|
-
|
|
66
|
-
What a self-exit MEANS then depends on whether anyone is watching, because the two failure modes pull in opposite directions:
|
|
67
|
-
|
|
68
|
-
- **Interactive** (stdout is a TTY) — a crashed boot is something you are about to fix, so the supervisor says so and keeps watching. The next save restarts it, which is what every other dev server does; stopping would throw away the watcher mid-edit and make you retype the command. - **Non-interactive** — nobody is going to fix anything. `voltro dev` exits with the child's code, so a failed boot is a failed command. This is the case that produced the invisible processes, and the one CI actually waits on.
|
|
18
|
+
Login is the sharpest case and it is not exotic: it MUST write (the session row), it cannot be an rpc mutation because it is what mints the cookie, and it is a documented first-class pattern — `@voltro/plugin-auth` ships `handleSignIn` / `handleSignUp` and the reference consumer mounts them on this surface. Every app that does so needed a store the route contract did not give it.
|
|
69
19
|
|
|
70
|
-
|
|
20
|
+
It is the BOOT store, through the same lazy getter the auth chain reads — one ref, three consumers now — and `undefined` while it is still being built, so a route should answer rather than throw. It is **not tenant-scoped**: a route serves raw HTTP with no resolved Subject, so a route reading tenant-owned rows must derive and apply that scope itself. That is the price of the surface being raw, and the reason an rpc procedure stays the better home for anything that can be one.
|
|
71
21
|
|
|
72
|
-
|
|
73
|
-
- **@voltro/database** — **A user-facing message that names an API must have one — now checked in CI.**
|
|
22
|
+
### `db apply` honours `VOLTRO_DESTRUCTIVE_OK`
|
|
74
23
|
|
|
75
|
-
The
|
|
24
|
+
The table-list opt-in shipped on the auto-migrate path only. `voltro db apply` computes its own plan and checked `summary.blocked` directly; its module contained no occurrence of the variable at all. So an app's staging migration Job — running the sanctioned `db apply --plan` form — had **no route through an intentional, declared, `lossy`-classified table drop**, and the release note's own example was a command that ignored the variable it set.
|
|
76
25
|
|
|
77
|
-
|
|
26
|
+
Both forms now route through the same helper as the boot gate: every blocked op must be `lossy`, a named scope unblocks only the tables it names, anything still blocked refuses the whole plan. The applier receives the UNBLOCKED plan — passing the still-blocked one would have it refuse a second time, which is the exact bug `unblockLossy` was written for.
|
|
78
27
|
|
|
79
|
-
|
|
28
|
+
Worth naming, because it is a boundary of the message-API gate added last release: the variable exists, is spelled correctly, and IS read — by a different command than the one printing it. *"The named API exists"* and *"the named API is reachable from here"* are different claims, and only the first is checkable from a string.
|
|
80
29
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
The member rule is the one that took two attempts. The first version asked "does this name exist in the public surface" and the motivating bug **passed it**: `dropped` is exported, as a free `dropped()` you write as a column's value. A dotted claim is a claim about something chainable, so a free function and a `readonly dropped?: boolean` data property are both correctly rejected now.
|
|
84
|
-
|
|
85
|
-
**It immediately found a second instance nobody had reported** — the drop-COLUMN refusal also said "chain `.dropped()`", one level down from the reported one, and the real spelling is `<column>: dropped()` as the field's value. Close enough to guess from, which is why it survived.
|
|
86
|
-
|
|
87
|
-
Ships with a `--selftest` that runs first in CI, for the reason the changelog gate has one: a check that has quietly stopped detecting anything still prints green, and green is read as evidence.
|
|
88
|
-
- **@voltro/database** — **Two migration refusals sent people the wrong way** — the worst place for a bad hint, because whoever reads one is already blocked and looking for the sanctioned way out.
|
|
89
|
-
|
|
90
|
-
**The drop-table refusal recommended an API that does not exist.** Its first option was *"add it to your declared set + chain `.dropped()` on it"*. There is no table-level `dropped()` — only the column marker. The recommendation was also the conceptually RIGHT one, which is what made it expensive: the two options that do work are both worse, so a reader picks the one they cannot follow.
|
|
91
|
-
|
|
92
|
-
There is now a real per-table answer: **`VOLTRO_DESTRUCTIVE_OK` accepts a table list**, not just `1`. `VOLTRO_DESTRUCTIVE_OK=old_things` acknowledges the data loss for that table and leaves every other lossy op in the plan blocked. `1` still means all of them — which is rarely what somebody means, and was previously the only way to say anything. A user with one intended drop and three other lossy ops had to acknowledge all four or hand-write a `DROP TABLE` migration, the path 0.14.0's own upgrade note warns against.
|
|
93
|
-
|
|
94
|
-
The message also states why there is deliberately no table marker: a dropped COLUMN leaves a slot worth documenting in the declaration; a dropped TABLE leaves nothing, so the marker would be a dead entry you must remember to delete.
|
|
30
|
+
### Fixed
|
|
95
31
|
|
|
96
|
-
|
|
32
|
+
- **@voltro/cli, @voltro/database** — **`voltro update --codemods-only` crashed on a file deleted but not staged** — a regression introduced by 0.15.0's own git-based enumeration.
|
|
97
33
|
|
|
98
|
-
|
|
99
|
-
- **@voltro/cli** — **0.14.0's taxonomy codemod renamed two kinds of file it should not have, and a repo that already upgraded carries the damage with a green build.** Both were found by adopters running it on real projects; both are silent — the rename succeeds, the imports are rewritten, nothing throws.
|
|
34
|
+
`git ls-files --cached` reads the INDEX, and the index still holds a path that is already gone from disk until the deletion is staged. `addSourceFileAtPath` then threw ENOENT, surfacing as a raw ts-morph stack rather than as anything a reader could act on.
|
|
100
35
|
|
|
101
|
-
|
|
36
|
+
The state is not exotic — it is what an ordinary mid-work tree looks like, and `--dry-run` is documented as allowed on a dirty tree, which is exactly where an unstaged deletion lives. The command was unusable in the state it explicitly permits. Paths that are gone are now filtered out: there is nothing to rewrite and nothing to report.
|
|
102
37
|
|
|
103
|
-
|
|
38
|
+
**A failing DDL statement now travels with the error, not only to stderr.** A soft-drop aborted a boot with a bare `SqlError: Failed to execute statement` — no statement, no table, no operation. The applier wrote the detail with `process.stderr.write` inside a `tapError` and re-raised the ORIGINAL error, so the detail was lost whenever the process aborted before the stream flushed, or whenever the caller rendered the error rather than the console. `migrate.ts` already carried it in the error for file-based migrations; the planner-driven applier did not, so **which path failed decided whether you could see what failed.**
|
|
39
|
+
- **@voltro/cli** — **`voltro update --codemods-only` refused in the one state it exists for.**
|
|
104
40
|
|
|
105
|
-
|
|
41
|
+
The clean-tree guard ran before the repair path, so the command whose entire job is finishing an interrupted upgrade refused whenever the tree was dirty. An adopter reported it twice; the second time their tree carried uncommitted work **from the previous upgrade**, so they bumped four manifests by hand and ran the install themselves — the outcome `voltro update` exists to prevent, reached through its own guard.
|
|
106
42
|
|
|
107
|
-
|
|
43
|
+
`--force` was always available and is the wrong answer: it is documented as "not recommended", so it reads as an escape hatch rather than as the sanctioned route through a state we explicitly support.
|
|
108
44
|
|
|
109
|
-
|
|
110
|
-
- **@voltro/cli** — Three ways `voltro update` failed on a real adopter's host, none of which we could have found ourselves — each needs a machine we do not have.
|
|
45
|
+
`--codemods-only` now **warns** instead of refusing, saying the codemod diff will be mixed in with the existing changes and pointing at `--dry-run` / `--only`. The full update still refuses — bump, install and rewrite in one pass on top of unrelated changes is a diff nobody can read — and its refusal now names the repair path.
|
|
111
46
|
|
|
112
|
-
|
|
47
|
+
### Internal (no consumer-facing effect)
|
|
113
48
|
|
|
114
|
-
|
|
49
|
+
- **@voltro/cli** — The admin-import rejection cases share ONE booted server, and report their phase timings.
|
|
115
50
|
|
|
116
|
-
|
|
51
|
+
Two release gates have now failed on that single test line, with two different symptoms and neither an auth defect: first a **400** — this server rejects a request whose body framing broke BEFORE routing, so `gate()` never ran — and then, after the body was removed, a **120-second timeout**. A bare `Test timed out` cannot say whether the boot, the request or the close is what hung, which is why the second failure taught us nothing the first hadn't.
|
|
117
52
|
|
|
118
|
-
|
|
53
|
+
This file was standing up SIX real `serveApi` instances, each with its own socket and Effect layer, on a machine already running every other package's suite under `turbo --concurrency=2`. The two rejection cases assert nothing about the store, so they now share one boot, and the assertion carries `boot=…ms absent=…ms wrong=…ms` — a future failure names the slow phase instead of just the line number.
|
|
119
54
|
|
|
120
|
-
|
|
55
|
+
No product code changed.
|
|
56
|
+
- **@voltro/cli** — The admin-import rejection tests stop uploading an archive they never needed.
|
|
121
57
|
|
|
122
|
-
|
|
123
|
-
- **@voltro/cli** — **`voltro update --only <id>`**, and a summary that stops contradicting itself.
|
|
58
|
+
A release gate failed with `expected 400 to be 401` on `401 without a Bearer token` and did not reproduce in five later runs. It was not an auth defect and not load-flake in the usual sense: measured against a live server on that path, an honest no-auth request answers **401**, a body-less one answers **401**, and one whose `Content-Length` lies — or whose chunked framing ends early — answers **400**, because the HTTP layer rejects broken framing BEFORE routing. `gate()` is the handler's first statement, so when it never runs there is no 401 to give.
|
|
124
59
|
|
|
125
|
-
|
|
60
|
+
Those two tests were POSTing the full packed bundle to assert an authorization property that is decided without reading the body at all. The archive proved nothing and made a multi-KB upload a precondition of an auth assertion. They now send no body, which is both flake-free and the sharper claim; the assertions carry the response body so a future mismatch names the responder instead of printing a bare status.
|
|
126
61
|
|
|
127
|
-
|
|
62
|
+
No product code changed — the endpoint's behaviour is unaltered.
|
|
63
|
+
- **@voltro/cli** — The admin-import auth test reports WHO answered, not just that the number was wrong.
|
|
128
64
|
|
|
129
|
-
|
|
65
|
+
It failed once under full-gate load with `expected 400 to be 401`, and did not reproduce in four subsequent runs (the file alone, the integration group alone, two full suites, a second full gate). The bare status made the log useless: `gate()` is the first statement in `handleAdminImport` and always 401s an absent token, so a 400 proves the request never reached that handler — but nothing in the failure said which handler DID answer.
|
|
130
66
|
|
|
131
|
-
|
|
67
|
+
The assertion now carries the response body and the target URL, so the next occurrence names the responder instead of costing an afternoon. No product code changed; the endpoint's behaviour is unaltered.
|
|
@@ -11,16 +11,16 @@
|
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@effect/platform": "^0.96.1",
|
|
13
13
|
"@effect/rpc": "^0.75.1",
|
|
14
|
-
"@voltro/ai": "0.
|
|
15
|
-
"@voltro/cli": "0.
|
|
16
|
-
"@voltro/database": "0.
|
|
17
|
-
"@voltro/env": "0.
|
|
18
|
-
"@voltro/protocol": "0.
|
|
19
|
-
"@voltro/runtime": "0.
|
|
14
|
+
"@voltro/ai": "0.17.0",
|
|
15
|
+
"@voltro/cli": "0.17.0",
|
|
16
|
+
"@voltro/database": "0.17.0",
|
|
17
|
+
"@voltro/env": "0.17.0",
|
|
18
|
+
"@voltro/protocol": "0.17.0",
|
|
19
|
+
"@voltro/runtime": "0.17.0",
|
|
20
20
|
"effect": "^3.21.2"
|
|
21
21
|
},
|
|
22
22
|
"devDependencies": {
|
|
23
|
-
"@voltro/testing": "0.
|
|
23
|
+
"@voltro/testing": "0.17.0",
|
|
24
24
|
"typescript": "^5.7.0",
|
|
25
25
|
"vitest": "^3.0.0"
|
|
26
26
|
}
|
|
@@ -12,17 +12,17 @@
|
|
|
12
12
|
"dependencies": {
|
|
13
13
|
"@effect/platform": "^0.96.1",
|
|
14
14
|
"@effect/rpc": "^0.75.1",
|
|
15
|
-
"@voltro/cli": "0.
|
|
16
|
-
"@voltro/database": "0.
|
|
17
|
-
"@voltro/env": "0.
|
|
18
|
-
"@voltro/plugin-auth": "0.
|
|
19
|
-
"@voltro/protocol": "0.
|
|
20
|
-
"@voltro/runtime": "0.
|
|
21
|
-
"@voltro/sql-postgres": "0.
|
|
15
|
+
"@voltro/cli": "0.17.0",
|
|
16
|
+
"@voltro/database": "0.17.0",
|
|
17
|
+
"@voltro/env": "0.17.0",
|
|
18
|
+
"@voltro/plugin-auth": "0.17.0",
|
|
19
|
+
"@voltro/protocol": "0.17.0",
|
|
20
|
+
"@voltro/runtime": "0.17.0",
|
|
21
|
+
"@voltro/sql-postgres": "0.17.0",
|
|
22
22
|
"effect": "^3.21.2"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
|
-
"@voltro/testing": "0.
|
|
25
|
+
"@voltro/testing": "0.17.0",
|
|
26
26
|
"typescript": "^5.7.0",
|
|
27
27
|
"vitest": "^3.0.0"
|
|
28
28
|
}
|
|
@@ -12,16 +12,16 @@
|
|
|
12
12
|
"dependencies": {
|
|
13
13
|
"@effect/platform": "^0.96.1",
|
|
14
14
|
"@effect/rpc": "^0.75.1",
|
|
15
|
-
"@voltro/cli": "0.
|
|
16
|
-
"@voltro/database": "0.
|
|
17
|
-
"@voltro/env": "0.
|
|
18
|
-
"@voltro/plugin-multitenancy": "0.
|
|
19
|
-
"@voltro/protocol": "0.
|
|
20
|
-
"@voltro/runtime": "0.
|
|
15
|
+
"@voltro/cli": "0.17.0",
|
|
16
|
+
"@voltro/database": "0.17.0",
|
|
17
|
+
"@voltro/env": "0.17.0",
|
|
18
|
+
"@voltro/plugin-multitenancy": "0.17.0",
|
|
19
|
+
"@voltro/protocol": "0.17.0",
|
|
20
|
+
"@voltro/runtime": "0.17.0",
|
|
21
21
|
"effect": "^3.21.2"
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
24
|
-
"@voltro/testing": "0.
|
|
24
|
+
"@voltro/testing": "0.17.0",
|
|
25
25
|
"typescript": "^5.7.0",
|
|
26
26
|
"vitest": "^3.0.0"
|
|
27
27
|
}
|
|
@@ -12,16 +12,16 @@
|
|
|
12
12
|
"dependencies": {
|
|
13
13
|
"@effect/platform": "^0.96.1",
|
|
14
14
|
"@effect/rpc": "^0.75.1",
|
|
15
|
-
"@voltro/cli": "0.
|
|
16
|
-
"@voltro/database": "0.
|
|
17
|
-
"@voltro/env": "0.
|
|
18
|
-
"@voltro/plugin-deactivation": "0.
|
|
19
|
-
"@voltro/protocol": "0.
|
|
20
|
-
"@voltro/runtime": "0.
|
|
15
|
+
"@voltro/cli": "0.17.0",
|
|
16
|
+
"@voltro/database": "0.17.0",
|
|
17
|
+
"@voltro/env": "0.17.0",
|
|
18
|
+
"@voltro/plugin-deactivation": "0.17.0",
|
|
19
|
+
"@voltro/protocol": "0.17.0",
|
|
20
|
+
"@voltro/runtime": "0.17.0",
|
|
21
21
|
"effect": "^3.21.2"
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
24
|
-
"@voltro/testing": "0.
|
|
24
|
+
"@voltro/testing": "0.17.0",
|
|
25
25
|
"typescript": "^5.7.0",
|
|
26
26
|
"vitest": "^3.0.0"
|
|
27
27
|
}
|
|
@@ -12,18 +12,18 @@
|
|
|
12
12
|
"dependencies": {
|
|
13
13
|
"@react-email/components": "^1.0.12",
|
|
14
14
|
"@react-email/render": "^1.4.0",
|
|
15
|
-
"@voltro/cli": "0.
|
|
16
|
-
"@voltro/database": "0.
|
|
17
|
-
"@voltro/env": "0.
|
|
18
|
-
"@voltro/plugin-mail": "0.
|
|
19
|
-
"@voltro/plugin-multitenancy": "0.
|
|
20
|
-
"@voltro/protocol": "0.
|
|
21
|
-
"@voltro/runtime": "0.
|
|
15
|
+
"@voltro/cli": "0.17.0",
|
|
16
|
+
"@voltro/database": "0.17.0",
|
|
17
|
+
"@voltro/env": "0.17.0",
|
|
18
|
+
"@voltro/plugin-mail": "0.17.0",
|
|
19
|
+
"@voltro/plugin-multitenancy": "0.17.0",
|
|
20
|
+
"@voltro/protocol": "0.17.0",
|
|
21
|
+
"@voltro/runtime": "0.17.0",
|
|
22
22
|
"effect": "^3.21.2",
|
|
23
23
|
"react": "^19.0.0"
|
|
24
24
|
},
|
|
25
25
|
"devDependencies": {
|
|
26
|
-
"@voltro/testing": "0.
|
|
26
|
+
"@voltro/testing": "0.17.0",
|
|
27
27
|
"typescript": "^5.7.0",
|
|
28
28
|
"vitest": "^3.0.0"
|
|
29
29
|
}
|
|
@@ -12,18 +12,18 @@
|
|
|
12
12
|
"dependencies": {
|
|
13
13
|
"@effect/platform": "^0.96.1",
|
|
14
14
|
"@effect/rpc": "^0.75.1",
|
|
15
|
-
"@voltro/cli": "0.
|
|
16
|
-
"@voltro/database": "0.
|
|
17
|
-
"@voltro/env": "0.
|
|
18
|
-
"@voltro/plugin-multitenancy": "0.
|
|
19
|
-
"@voltro/plugin-storage": "0.
|
|
20
|
-
"@voltro/protocol": "0.
|
|
21
|
-
"@voltro/runtime": "0.
|
|
22
|
-
"@voltro/sql-mysql": "0.
|
|
15
|
+
"@voltro/cli": "0.17.0",
|
|
16
|
+
"@voltro/database": "0.17.0",
|
|
17
|
+
"@voltro/env": "0.17.0",
|
|
18
|
+
"@voltro/plugin-multitenancy": "0.17.0",
|
|
19
|
+
"@voltro/plugin-storage": "0.17.0",
|
|
20
|
+
"@voltro/protocol": "0.17.0",
|
|
21
|
+
"@voltro/runtime": "0.17.0",
|
|
22
|
+
"@voltro/sql-mysql": "0.17.0",
|
|
23
23
|
"effect": "^3.21.2"
|
|
24
24
|
},
|
|
25
25
|
"devDependencies": {
|
|
26
|
-
"@voltro/testing": "0.
|
|
26
|
+
"@voltro/testing": "0.17.0",
|
|
27
27
|
"typescript": "^5.7.0",
|
|
28
28
|
"vitest": "^3.0.0"
|
|
29
29
|
}
|