@voltro/cli 0.6.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/bin/voltro.mjs +15 -6
  3. package/dist/apiBuild-CmtI6Ygh.js +2 -0
  4. package/dist/{apiBuild-BxY44VGj.js → apiBuild-OrDkXGFX.js} +21 -20
  5. package/dist/bin.js +2 -2
  6. package/dist/{commands-My_YlUJV.js → commands-DT4PipBz.js} +2489 -2972
  7. package/dist/dev-Ca1jdwnt.js +2 -0
  8. package/dist/dev-Ga65K4jc.js +6492 -0
  9. package/dist/index.js +1 -1
  10. package/dist/inspectMetrics-EK0CcHyu.js +3929 -0
  11. package/dist/{serveCommand-C-XQ89NJ.js → serveCommand-CR5xnYZt.js} +209 -208
  12. package/dist/serveEntry.js +3 -2
  13. package/dist/start-DuyBQxW3.js +977 -0
  14. package/dist/startEntry.d.ts +5 -0
  15. package/dist/startEntry.js +3 -0
  16. package/package.json +17 -17
  17. package/templates/AGENTS.core.md +15 -4
  18. package/templates/AGENTS.md +15 -4
  19. package/templates/agent-docs/authentication.md +111 -13
  20. package/templates/agent-docs/cli.md +94 -6
  21. package/templates/agent-docs/data.md +99 -31
  22. package/templates/agent-docs/database/schema.md +1 -1
  23. package/templates/agent-docs/reference.md +20 -0
  24. package/templates/agent-docs/routing.md +158 -24
  25. package/templates/agent-docs/testing.md +82 -0
  26. package/templates/apps/api-ai/package.json +7 -7
  27. package/templates/apps/api-auth/package.json +8 -8
  28. package/templates/apps/api-backend/package.json +7 -7
  29. package/templates/apps/api-backend-deactivation/package.json +7 -7
  30. package/templates/apps/api-backend-mail/package.json +8 -8
  31. package/templates/apps/api-backend-mariadb/package.json +9 -9
  32. package/templates/apps/api-backend-storage/package.json +8 -8
  33. package/templates/apps/api-data-advanced/package.json +8 -8
  34. package/templates/apps/api-durable/package.json +8 -8
  35. package/templates/apps/api-feature-flags/package.json +9 -9
  36. package/templates/apps/api-governance/package.json +8 -8
  37. package/templates/apps/api-kv/package.json +8 -8
  38. package/templates/apps/api-moderation/package.json +8 -8
  39. package/templates/apps/api-observability/package.json +8 -8
  40. package/templates/apps/api-ratelimit/package.json +8 -8
  41. package/templates/apps/api-rbac/package.json +8 -8
  42. package/templates/apps/api-rest/package.json +7 -7
  43. package/templates/apps/api-saas/package.json +11 -11
  44. package/templates/apps/api-search/package.json +8 -8
  45. package/templates/apps/api-versioning/package.json +8 -8
  46. package/templates/apps/api-webhooks/package.json +8 -8
  47. package/templates/apps/changelog/package.json +6 -6
  48. package/templates/apps/edge-functions/package.json +2 -2
  49. package/templates/apps/frontend-admin/package.json +8 -8
  50. package/templates/apps/frontend-app/package.json +8 -8
  51. package/templates/apps/frontend-blank/package.json +7 -7
  52. package/templates/apps/frontend-contact/package.json +7 -7
  53. package/templates/apps/frontend-dashboard/package.json +7 -7
  54. package/templates/apps/frontend-docs/package.json +7 -7
  55. package/templates/apps/frontend-i18n/package.json +6 -6
  56. package/templates/apps/frontend-landing/package.json +7 -7
  57. package/templates/apps/frontend-spa/package.json +7 -7
  58. package/templates/apps/frontend-ssr/package.json +7 -7
  59. package/templates/apps/frontend-ssr-api/package.json +8 -8
  60. package/templates/apps/frontend-static-blog/package.json +6 -6
  61. package/dist/apiBuild-Bk4JBt-i.js +0 -2
  62. package/dist/dev-Bjq-nIvH.js +0 -10186
  63. package/dist/dev-D6b74iXI.js +0 -2
@@ -630,6 +630,26 @@ Available in:
630
630
 
631
631
  Returns `null` on pages without a `loader`. The generic narrows the type.
632
632
 
633
+ Precisely, it returns `LoaderData<T>`. For every ordinary loader that IS `T`. For
634
+ a loader that returned `defer()`, `LoaderData<T>` flattens the two buckets into
635
+ one object — eager fields as values, deferred fields as `Promise<T>` — so the
636
+ compiler tells you which fields have to be rendered through
637
+ [`<Await>`](/docs/routing/loaders-and-meta#deferring-slow-data-defer--await):
638
+
639
+ ```tsx
640
+ export const loader = async ({ query }) => defer(
641
+ { user: await query('users.me') },
642
+ { report: query('reports.quarterly') },
643
+ )
644
+
645
+ // user: User report: Promise<Report>
646
+ const { user, report } = useLoaderData<Awaited<ReturnType<typeof loader>>>()
647
+ ```
648
+
649
+ If you wrap this hook in your own generic helper, propagate the mapped type
650
+ (`<D,>(): LoaderData<D> => useLoaderData<D>()`) — `LoaderData<D>` is not
651
+ assignable to a bare type parameter `D`.
652
+
633
653
  See [Loaders & meta](/docs/routing/loaders-and-meta) for the server-side counterpart.
634
654
 
635
655
  ## Compositions
@@ -537,29 +537,39 @@ Cost: every request triggers a fresh render. For very high-traffic pages, prefer
537
537
 
538
538
  ### Streaming SSR
539
539
 
540
- By default an `ssr` page renders with `renderToString` — the whole document is built in memory, then sent. The server-side SSR helpers in `@voltro/web/ssr` also expose a **streaming** path, `renderPageToStream`, built on React's `renderToPipeableStream`. It flushes the shell (and each `<Suspense>` boundary as it resolves) instead of buffering the whole page, so the browser gets the first bytes sooner:
541
-
542
- ```ts
543
- import { renderPageToStream } from '@voltro/web/ssr'
544
-
545
- const { stream, meta } = renderPageToStream({
546
- descriptor,
547
- params,
548
- pathname,
549
- loaderData,
550
- bootstrapModules: ['/assets/client.js'],
551
- onShellReady: () => {
552
- // meta is resolved synchronously write <head> before piping the body.
553
- res.setHeader('content-type', 'text/html')
554
- res.write(`<!doctype html><html><head>${renderMetaToHtml(meta)}</head><body><div id="root">`)
555
- stream.pipe(res)
556
- },
557
- onAllReady: () => res.end('</div></body></html>'),
558
- onShellError: (err) => { res.statusCode = 500; res.end('Internal error') },
559
- })
560
- ```
561
-
562
- `renderPageToStream` takes the same options as `renderPageToHtml` plus the stream callbacks (`onShellReady` / `onAllReady` / `onShellError`) and `bootstrapModules`. Both paths compose the same providers and resolve `meta` synchronously, so the sync string render and the streaming render produce the same tree — the difference is time-to-first-byte and native Suspense streaming. Most apps never call these directly; the `voltro start` pipeline uses them under the hood.
540
+ An `ssr` page is **streamed**, on both boot paths (`voltro dev` and `voltro
541
+ start`). The server sends the `<head>` and the page shell as soon as they are
542
+ rendered, then flushes each `<Suspense>` boundary as its data settles, inside
543
+ the same response.
544
+
545
+ For a page with no deferred data this is a time-to-first-byte win and nothing
546
+ else — React's render loop still runs to completion in one pass, so a slow
547
+ render is still a slow render. The lever that matters is `defer()`: it puts a
548
+ real `<Suspense>` boundary in the tree, which is what lets the server return to
549
+ the event loop while a slow value is still pending. See
550
+ [Deferring slow data](/docs/routing/loaders-and-meta#deferring-slow-data-defer--await).
551
+
552
+ Measured against a real server rendering a page with one 400ms deferred field:
553
+ first body byte at 7ms, the deferred chunk at 408ms — and a probe firing every
554
+ 10ms for the duration of that request was served 30 times with a 3ms median and
555
+ a 7ms maximum. The request does not occupy the event loop while it waits.
556
+
557
+ Two consequences worth knowing:
558
+
559
+ - **The entry script is emitted as a bootstrap module.** A plain
560
+ `<script type="module">` is deferred until the document finishes parsing,
561
+ which on a streamed response is *after the last deferred boundary* — the
562
+ framework hands the URL to React instead, so it goes out `async` at the end
563
+ of the shell and hydration starts immediately.
564
+ - **`isr` and `static` are still buffered**, because both produce a stored
565
+ artefact rather than a response. That is also why `defer()` is an error on
566
+ those modes.
567
+
568
+ Apps do not call the renderer directly. If you are building your own server on
569
+ top of `@voltro/web/ssr`, `renderPageToStream` is the entry point — it takes the
570
+ same options as `renderPageToHtml` plus `bootstrapModules` and the stream
571
+ callbacks, resolves `meta` synchronously so the `<head>` can go out first, and
572
+ returns a Node pipeable stream.
563
573
 
564
574
  ## isr (incremental static regeneration)
565
575
 
@@ -660,7 +670,7 @@ That last case is how dynamic `static` routes work in dev / when `getStaticPaths
660
670
  ## What doesn't work
661
671
 
662
672
  - **Switching `renderMode` per request.** It's a static module export — one value per build.
663
- - **`renderMode: 'ssr'` without `voltro start`.** SSR requires the production server. Dev mode runs everything client-side regardless.
673
+ - **Assuming `ssr` is client-only in dev.** It is not: `voltro dev` runs the same SSR path `voltro start` does, streaming included, so cookie-driven gates and `useServerRequest()` behave the same in both. What dev does NOT do is pre-render `static` pages — those fall through to the SPA shell.
664
674
  - **`isr` with `cacheInvalidatesOn` against memory cache.** Memory cache is per-process; CDC events fire across processes. Use `SSR_CACHE=postgres`.
665
675
 
666
676
  ## Where to read next
@@ -730,6 +740,130 @@ export default function NotePage(): ReactNode {
730
740
 
731
741
  For `static` pages with `getStaticPaths`, the loader runs once per enumerated path.
732
742
 
743
+ ### Server-rendered data reaches the first client render
744
+
745
+ For `static`, `ssr` and `isr` pages the framework inlines the loader's result
746
+ into the HTML document (a `<script type="application/json"
747
+ id="__voltro_state__">` tag) and the browser adopts it before React hydrates.
748
+ Two consequences worth designing around:
749
+
750
+ - **`useLoaderData()` returns real data on the very first client render.** It is
751
+ not `undefined` until an effect has run, so a page can dereference its loader
752
+ data directly (`data.title`) without a guard, and a layout renders its
753
+ loader's value identically on the server and on the client — no hydration
754
+ mismatch, no flash of fallback content.
755
+ - **The loader does NOT re-run on that initial hydration.** It already ran on
756
+ the server; running it again in the browser would just re-fetch what the page
757
+ is already showing. If you need work to happen after mount (refreshing data,
758
+ a side effect), put it in an effect or use `useSubscription` — do not rely on
759
+ the loader firing a second time.
760
+
761
+ Client-side navigation is unchanged: moving to another route runs that route's
762
+ loaders in the browser as usual. A `spa` page has no server render, so its
763
+ loader runs on the client on first mount.
764
+
765
+ Layout loaders are inlined the same way, keyed per layout, so each layout reads
766
+ its OWN data on the first render. The one exception is `voltro build`'s static
767
+ prerender, which runs page loaders but not layout loaders — a `static` page's
768
+ layouts resolve their data on the client after mount. The PAGE keeps its own
769
+ inlined data for the whole of that window, however slow those layout loaders
770
+ are, so the no-guard promise above holds on a prerendered page too: only the
771
+ layout shows its no-data fallback until its loader settles.
772
+
773
+
774
+ ## Deferring slow data: `defer()` + `<Await>`
775
+
776
+ A loader blocks the whole response. One slow field therefore costs every byte
777
+ of the page — the user stares at nothing while a report query runs. `defer()`
778
+ splits the loader's result into data that blocks the shell and data that
779
+ **streams in after it**, behind a `<Suspense>` boundary the server flushes as
780
+ soon as the promise settles.
781
+
782
+ ```tsx
783
+ // src/pages/dashboard.tsx
784
+ import { Await, defer, useLoaderData } from '@voltro/web'
785
+
786
+ export const renderMode = 'ssr' as const // required — see below
787
+
788
+ export const loader = async ({ query }: { query?: <T>(tag: string, input?: Record<string, unknown>) => Promise<T> }) => defer(
789
+ // EAGER — awaited before the shell renders. Keep this fast.
790
+ { user: await query?.<User>('users.me') },
791
+ // DEFERRED — NOT awaited. Each becomes a promise on useLoaderData().
792
+ { report: query!<Report>('reports.quarterly') },
793
+ )
794
+
795
+ export default function Dashboard() {
796
+ const { user, report } = useLoaderData<Awaited<ReturnType<typeof loader>>>()
797
+ return (
798
+ <main>
799
+ <h1>Hello {user?.name}</h1>
800
+
801
+ <Await value={report} fallback={<ReportSkeleton />}>
802
+ {(report) => <ReportTable rows={report.rows} />}
803
+ </Await>
804
+ </main>
805
+ )
806
+ }
807
+ ```
808
+
809
+ What the browser sees: the full page with `<ReportSkeleton />` in place,
810
+ immediately — then the real table, injected in a later chunk of the same
811
+ response. No second request, no client-side fetch, no loading spinner driven by
812
+ `useEffect`.
813
+
814
+ **Two explicit buckets, not one object.** `defer(eager, deferred)` takes them
815
+ separately rather than treating any promise-valued field as deferred. Deferral
816
+ is then something you wrote down, not something inferred from a value's runtime
817
+ shape — and `useLoaderData()` can type it: eager fields come back as values,
818
+ deferred fields as `Promise<T>`, so the compiler tells you which ones need an
819
+ `<Await>`.
820
+
821
+ ### `<Await>`
822
+
823
+ | Prop | Meaning |
824
+ |---|---|
825
+ | `value` | A deferred field off `useLoaderData()`. |
826
+ | `fallback` | Rendered until the value arrives. **This is what ships in the streamed shell** — keep it cheap and layout-stable. |
827
+ | `children` | `(value) => ReactNode` — rendered with the resolved value. |
828
+ | `errorFallback` | Rendered if the deferred promise rejects. Without it, a rejection renders nothing in that subtree. |
829
+
830
+ `<Await>` owns the `<Suspense>` boundary and the hydration handoff for the
831
+ streamed value. Do not hand-roll it with `<Suspense>` + `use()` — the server
832
+ and the client would then have to agree on a wire format that the framework
833
+ otherwise guarantees by construction.
834
+
835
+ A rejected deferred value never takes the page down: it renders
836
+ `errorFallback` in place, on the server and on the client alike.
837
+
838
+ ### `defer()` requires `renderMode: 'ssr'` and full interactivity
839
+
840
+ Every other combination is a **hard error at boot or build**, naming the page —
841
+ because each one fails silently otherwise:
842
+
843
+ | Combination | Why it is rejected |
844
+ |---|---|
845
+ | `renderMode: 'static'` | The static prerender uses `renderToString`, which does not support Suspense. It emits an errored boundary and a "switched to client rendering" template with no warning — the artefact would ship a permanent fallback. |
846
+ | `renderMode: 'isr'` | ISR caches a completed HTML string. Filling it in would make `defer()` a silent no-op that still reads like it streams. |
847
+ | `interactive: 'none'` | Revealing a streamed boundary needs React's inline reveal scripts, and this mode ships no JS. The fallback would be permanent. |
848
+ | `interactive: 'islands'` | The page's React root never hydrates, so nothing consumes the streamed value. |
849
+
850
+ In all four cases the fix is the same: put the value in the eager bucket (or
851
+ return it directly) and let the page render as it did before.
852
+
853
+ ### Layout loaders can defer too
854
+
855
+ A `layout.tsx` loader may return `defer()` under the same rules. Its deferred
856
+ fields are keyed per layout, so a layout reads its own promises via
857
+ `useLoaderData()` exactly as a page does.
858
+
859
+ ### Client-side navigation
860
+
861
+ On a client-side navigation there is no server render, so the loader runs in the
862
+ browser and its deferred fields are ordinary promises. `<Await>` renders the
863
+ fallback and swaps in the content when they settle — the same code, driven by
864
+ React alone.
865
+
866
+
733
867
  ## Loader arguments
734
868
 
735
869
  ```ts
@@ -116,6 +116,8 @@ test('searchDocs returns matching rows', async () => {
116
116
  | `ai` | — | Injected AI mock (a `mockAi({...})` value). |
117
117
  | `llmResponses` | `[]` | Queued responses for the bundled `ctx.llm` (`MockLLM`). |
118
118
  | `env` | ambient `process.env` | Env values sealed into the boot snapshot so handler code reading `getSecret('X')` / `serverEnv.X` resolves under test. Merged over `process.env` (these win). |
119
+ | `relations` | — | `relations()` specs to register for this context — the boot sweep's stand-in. See [Eager loads under test](#eager-loads-under-test). |
120
+ | `rowFilter` | the registered filter | A row filter for this context only, instead of the process-global `setRowFilter(...)`. See [Row-level security under test](#row-level-security-under-test). |
119
121
 
120
122
  The returned `TestContext` carries `{ clock, email, llm, ai?, request, cache, store, withSubject, withTenant }` — read the acting subject at `ctx.request.subject` and the in-memory cache at `ctx.cache`.
121
123
 
@@ -137,6 +139,31 @@ const note = await invoke(createNote, createNoteHandler, { title: 'hi' }, ctx)
137
139
  await expect(invoke(createNote, createNoteHandler, { title: 42 }, ctx)).rejects.toThrow()
138
140
  ```
139
141
 
142
+ ### Effect-mode handlers run too
143
+
144
+ A handler may be written `async` **or** as an `Effect` — the framework's contract is "your choice, per handler", and the dispatcher runs both. `invoke` makes the same test, so an `Effect`-returning executor is *executed*, and `invoke` resolves to its success value (typed as that value, not as the `Effect`):
145
+
146
+ ```ts
147
+ export const publishNote = (input: { id: string }, ctx: AppContext) =>
148
+ Effect.gen(function* () {
149
+ const store = yield* EffectStore
150
+ yield* store.update('notes', input.id, { published: true })
151
+ return 'published'
152
+ })
153
+
154
+ const out = await invoke(publish, publishNote, { id: 'n1' }, ctx)
155
+ expect(out).toBe('published') // the value — not an un-run Effect
156
+ ```
157
+
158
+ A failure on the typed error channel rejects with **that error**, exactly as an async handler's `throw` does — so the same assertion works for either mode:
159
+
160
+ ```ts
161
+ await expect(invoke(publish, publishNote, { id: 'gone' }, ctx))
162
+ .rejects.toMatchObject({ _tag: 'NoteNotFound' })
163
+ ```
164
+
165
+ `EffectStore` and `SubjectService` are provided over the context the handler is actually given — inside a mutation that is the *transactional* one, so an Effect handler's writes roll back with everything else. Guards, the input decode, the transaction, the deadlock replay, `afterCommit` and the plugin interceptors all wrap the Effect form identically. An app's own `layers:` and the aggregate registry are **not** provided: those are boot injections the harness has no access to.
166
+
140
167
  ### Guards are enforced
141
168
 
142
169
  An unauthorized caller is refused with the typed `ScopeError` — the same error a client would receive — before the handler runs:
@@ -325,6 +352,61 @@ const raw = await ctx.store.select('notes').unscoped().withDeleted().all()
325
352
  expect(raw[0]?.deletedAt).not.toBeNull() // …but still there, tombstoned
326
353
  ```
327
354
 
355
+ ## Eager loads under test
356
+
357
+ `relations()` is **pure** — it returns a spec, it does not register one. In production `voltro dev` discovers every `*.relations.ts` and registers what it exports; a unit test runs no boot, so importing the module registers nothing and the first `.with({ … })` fails with *"no relations registered"*. Hand the specs to the context instead:
358
+
359
+ ```ts
360
+ import { teamRelations } from '../db/teams.relations'
361
+
362
+ const ctx = makeTestContext({
363
+ relations: [teamRelations],
364
+ store: mockStore({ teams: [{ id: 't1' }], members: [{ id: 'm1', teamId: 't1' }] }),
365
+ })
366
+
367
+ const rows = await ctx.store.select('teams').with({ members: true }).all()
368
+ expect(rows[0].members).toHaveLength(1)
369
+ ```
370
+
371
+ The relations registry is **process-global**, so the option *replaces* it with exactly the specs you pass rather than adding to it. That is what keeps two `makeTestContext({ relations: [...] })` calls in one file independent — additive registration would throw `duplicate relation` on a re-registered spec and would carry the first test's relations into the second. Omitting the option leaves the registry untouched.
372
+
373
+ ## Row-level security under test
374
+
375
+ `ctx.store` applies the app's [row filter](/docs/authentication/row-level-security) for the context's subject: registered with `setRowFilter(...)`, resolved once per context, AND-merged into every read. Both read paths are covered (the fluent builders and `store.query(descriptor)`), `.unscoped()` does **not** bypass it — that opts out of tenant isolation, not of authorization — and a `system` subject bypasses it, exactly as at runtime.
376
+
377
+ ```ts
378
+ const ctx = makeTestContext({ subject: alice, store: mockStore({ tickets: seed }), rowFilter: ownTickets })
379
+
380
+ const rows = await ctx.store.select('tickets').all()
381
+ expect(rows.map((r) => r.id)).not.toContain('bobs-ticket') // the rule, asserted
382
+ ```
383
+
384
+ Pass `rowFilter:` — as above — to scope a filter to **this context only**. `setRowFilter` is process-global: registered in one test it silently constrains every later test in the same worker, and a forgotten `afterEach` surfaces as a failure in an unrelated file. Either way the resolution is the runtime's own, so the retry schedule, the system bypass and the `onLoadError` policy behave identically: a filter whose `load` fails refuses the read (with `RowFilterUnavailable`, or zero rows under `onLoadError: 'deny'`) rather than quietly returning everything.
385
+
386
+ ### Filters over SHARED resources
387
+
388
+ A filter over rows the user *owns* needs no database — the subject carries the id. A filter over rows *shared with* the user must read a membership table, and `load` is `Effect<Ctx, unknown>` with `R = never`, so it cannot `yield*` a store service. Its only route is [`runAsSystem`](/docs/multi-tenancy/edge-cases), and `makeTestContext` wires its seeded store into that, so this resolves under test exactly as it does at runtime:
389
+
390
+ ```ts
391
+ const sharedLists: RowFilter<{ listIds: ReadonlyArray<string> }> = {
392
+ load: (subject) =>
393
+ Effect.promise(() =>
394
+ runAsSystem(async (sys) => {
395
+ const rows = await sys.store.select('listMembers').where('userId', String(subject.id)).all()
396
+ return { listIds: rows.map((r) => String(r.listId)) }
397
+ }),
398
+ ),
399
+ predicate: (ctx, table) => (table === 'lists' ? inSet('id', ctx.listIds) : undefined),
400
+ }
401
+
402
+ const bob = makeTestContext({ subject: bobSubject, store: mockStore(seed), rowFilter: sharedLists })
403
+ expect((await bob.store.select('lists').all()).map((r) => r.id)).not.toContain('alices-list')
404
+ ```
405
+
406
+ `makeTestContext` registers the **raw** data store + schema registry, so nothing is double-wrapped (`runAsSystem` applies the system-subject mixin wrap itself), and it is the outer store rather than a transactional view — matching production, where a `runAsSystem` block inside a mutation reads outside that mutation's transaction.
407
+
408
+ The handle is a process global, and the harness handles that in two layers. Registration is last-wins, so a bare `await runAsSystem(...)` written directly in a test also resolves. On top of that, each context re-points the handle at **its own** store for the span of its `load` and restores the previous value — necessary because two `makeTestContext` calls allocate two separate in-memory stores even from one seed object, so filter resolution must not depend on build order. Nothing leaks across test files (vitest gives each file a fresh module registry). Only the harness registers: an app that registers no handle still gets `runAsSystem: no data store available`, unchanged. If a test in the same file needs that refusal back, call `clearSystemStoreHandle()` from `@voltro/runtime`.
409
+
328
410
  ## The deterministic mocks
329
411
 
330
412
  ### `ctx.clock` — `MockClock`
@@ -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.6.0",
15
- "@voltro/cli": "0.6.0",
16
- "@voltro/database": "0.6.0",
17
- "@voltro/env": "0.6.0",
18
- "@voltro/protocol": "0.6.0",
19
- "@voltro/runtime": "0.6.0",
14
+ "@voltro/ai": "0.8.0",
15
+ "@voltro/cli": "0.8.0",
16
+ "@voltro/database": "0.8.0",
17
+ "@voltro/env": "0.8.0",
18
+ "@voltro/protocol": "0.8.0",
19
+ "@voltro/runtime": "0.8.0",
20
20
  "effect": "^3.21.2"
21
21
  },
22
22
  "devDependencies": {
23
- "@voltro/testing": "0.6.0",
23
+ "@voltro/testing": "0.8.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.6.0",
16
- "@voltro/database": "0.6.0",
17
- "@voltro/env": "0.6.0",
18
- "@voltro/plugin-auth": "0.6.0",
19
- "@voltro/protocol": "0.6.0",
20
- "@voltro/runtime": "0.6.0",
21
- "@voltro/sql-postgres": "0.6.0",
15
+ "@voltro/cli": "0.8.0",
16
+ "@voltro/database": "0.8.0",
17
+ "@voltro/env": "0.8.0",
18
+ "@voltro/plugin-auth": "0.8.0",
19
+ "@voltro/protocol": "0.8.0",
20
+ "@voltro/runtime": "0.8.0",
21
+ "@voltro/sql-postgres": "0.8.0",
22
22
  "effect": "^3.21.2"
23
23
  },
24
24
  "devDependencies": {
25
- "@voltro/testing": "0.6.0",
25
+ "@voltro/testing": "0.8.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.6.0",
16
- "@voltro/database": "0.6.0",
17
- "@voltro/env": "0.6.0",
18
- "@voltro/plugin-multitenancy": "0.6.0",
19
- "@voltro/protocol": "0.6.0",
20
- "@voltro/runtime": "0.6.0",
15
+ "@voltro/cli": "0.8.0",
16
+ "@voltro/database": "0.8.0",
17
+ "@voltro/env": "0.8.0",
18
+ "@voltro/plugin-multitenancy": "0.8.0",
19
+ "@voltro/protocol": "0.8.0",
20
+ "@voltro/runtime": "0.8.0",
21
21
  "effect": "^3.21.2"
22
22
  },
23
23
  "devDependencies": {
24
- "@voltro/testing": "0.6.0",
24
+ "@voltro/testing": "0.8.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.6.0",
16
- "@voltro/database": "0.6.0",
17
- "@voltro/env": "0.6.0",
18
- "@voltro/plugin-deactivation": "0.6.0",
19
- "@voltro/protocol": "0.6.0",
20
- "@voltro/runtime": "0.6.0",
15
+ "@voltro/cli": "0.8.0",
16
+ "@voltro/database": "0.8.0",
17
+ "@voltro/env": "0.8.0",
18
+ "@voltro/plugin-deactivation": "0.8.0",
19
+ "@voltro/protocol": "0.8.0",
20
+ "@voltro/runtime": "0.8.0",
21
21
  "effect": "^3.21.2"
22
22
  },
23
23
  "devDependencies": {
24
- "@voltro/testing": "0.6.0",
24
+ "@voltro/testing": "0.8.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.6.0",
16
- "@voltro/database": "0.6.0",
17
- "@voltro/env": "0.6.0",
18
- "@voltro/plugin-mail": "0.6.0",
19
- "@voltro/plugin-multitenancy": "0.6.0",
20
- "@voltro/protocol": "0.6.0",
21
- "@voltro/runtime": "0.6.0",
15
+ "@voltro/cli": "0.8.0",
16
+ "@voltro/database": "0.8.0",
17
+ "@voltro/env": "0.8.0",
18
+ "@voltro/plugin-mail": "0.8.0",
19
+ "@voltro/plugin-multitenancy": "0.8.0",
20
+ "@voltro/protocol": "0.8.0",
21
+ "@voltro/runtime": "0.8.0",
22
22
  "effect": "^3.21.2",
23
23
  "react": "^19.0.0"
24
24
  },
25
25
  "devDependencies": {
26
- "@voltro/testing": "0.6.0",
26
+ "@voltro/testing": "0.8.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.6.0",
16
- "@voltro/database": "0.6.0",
17
- "@voltro/env": "0.6.0",
18
- "@voltro/plugin-multitenancy": "0.6.0",
19
- "@voltro/plugin-storage": "0.6.0",
20
- "@voltro/protocol": "0.6.0",
21
- "@voltro/runtime": "0.6.0",
22
- "@voltro/sql-mysql": "0.6.0",
15
+ "@voltro/cli": "0.8.0",
16
+ "@voltro/database": "0.8.0",
17
+ "@voltro/env": "0.8.0",
18
+ "@voltro/plugin-multitenancy": "0.8.0",
19
+ "@voltro/plugin-storage": "0.8.0",
20
+ "@voltro/protocol": "0.8.0",
21
+ "@voltro/runtime": "0.8.0",
22
+ "@voltro/sql-mysql": "0.8.0",
23
23
  "effect": "^3.21.2"
24
24
  },
25
25
  "devDependencies": {
26
- "@voltro/testing": "0.6.0",
26
+ "@voltro/testing": "0.8.0",
27
27
  "typescript": "^5.7.0",
28
28
  "vitest": "^3.0.0"
29
29
  }
@@ -10,17 +10,17 @@
10
10
  "test": "voltro test"
11
11
  },
12
12
  "dependencies": {
13
- "@voltro/cli": "0.6.0",
14
- "@voltro/database": "0.6.0",
15
- "@voltro/env": "0.6.0",
16
- "@voltro/plugin-multitenancy": "0.6.0",
17
- "@voltro/plugin-storage": "0.6.0",
18
- "@voltro/protocol": "0.6.0",
19
- "@voltro/runtime": "0.6.0",
13
+ "@voltro/cli": "0.8.0",
14
+ "@voltro/database": "0.8.0",
15
+ "@voltro/env": "0.8.0",
16
+ "@voltro/plugin-multitenancy": "0.8.0",
17
+ "@voltro/plugin-storage": "0.8.0",
18
+ "@voltro/protocol": "0.8.0",
19
+ "@voltro/runtime": "0.8.0",
20
20
  "effect": "^3.21.2"
21
21
  },
22
22
  "devDependencies": {
23
- "@voltro/testing": "0.6.0",
23
+ "@voltro/testing": "0.8.0",
24
24
  "typescript": "^5.7.0",
25
25
  "vitest": "^3.0.0"
26
26
  }
@@ -11,17 +11,17 @@
11
11
  "dependencies": {
12
12
  "@effect/platform": "^0.96.1",
13
13
  "@effect/rpc": "^0.75.1",
14
- "@voltro/cli": "0.6.0",
15
- "@voltro/database": "0.6.0",
16
- "@voltro/env": "0.6.0",
17
- "@voltro/plugin-governance": "0.6.0",
18
- "@voltro/plugin-multitenancy": "0.6.0",
19
- "@voltro/protocol": "0.6.0",
20
- "@voltro/runtime": "0.6.0",
14
+ "@voltro/cli": "0.8.0",
15
+ "@voltro/database": "0.8.0",
16
+ "@voltro/env": "0.8.0",
17
+ "@voltro/plugin-governance": "0.8.0",
18
+ "@voltro/plugin-multitenancy": "0.8.0",
19
+ "@voltro/protocol": "0.8.0",
20
+ "@voltro/runtime": "0.8.0",
21
21
  "effect": "^3.21.2"
22
22
  },
23
23
  "devDependencies": {
24
- "@voltro/testing": "0.6.0",
24
+ "@voltro/testing": "0.8.0",
25
25
  "typescript": "^5.7.0",
26
26
  "vitest": "^3.0.0"
27
27
  }
@@ -11,17 +11,17 @@
11
11
  "dependencies": {
12
12
  "@effect/platform": "^0.96.1",
13
13
  "@effect/rpc": "^0.75.1",
14
- "@voltro/cli": "0.6.0",
15
- "@voltro/database": "0.6.0",
16
- "@voltro/env": "0.6.0",
17
- "@voltro/plugin-multitenancy": "0.6.0",
18
- "@voltro/protocol": "0.6.0",
19
- "@voltro/runtime": "0.6.0",
20
- "@voltro/workflow": "0.6.0",
14
+ "@voltro/cli": "0.8.0",
15
+ "@voltro/database": "0.8.0",
16
+ "@voltro/env": "0.8.0",
17
+ "@voltro/plugin-multitenancy": "0.8.0",
18
+ "@voltro/protocol": "0.8.0",
19
+ "@voltro/runtime": "0.8.0",
20
+ "@voltro/workflow": "0.8.0",
21
21
  "effect": "^3.21.2"
22
22
  },
23
23
  "devDependencies": {
24
- "@voltro/testing": "0.6.0",
24
+ "@voltro/testing": "0.8.0",
25
25
  "typescript": "^5.7.0",
26
26
  "vitest": "^3.0.0"
27
27
  }
@@ -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.6.0",
16
- "@voltro/database": "0.6.0",
17
- "@voltro/env": "0.6.0",
18
- "@voltro/plugin-flags": "0.6.0",
19
- "@voltro/plugin-multitenancy": "0.6.0",
20
- "@voltro/protocol": "0.6.0",
21
- "@voltro/runtime": "0.6.0",
22
- "@voltro/sql-postgres": "0.6.0",
15
+ "@voltro/cli": "0.8.0",
16
+ "@voltro/database": "0.8.0",
17
+ "@voltro/env": "0.8.0",
18
+ "@voltro/plugin-flags": "0.8.0",
19
+ "@voltro/plugin-multitenancy": "0.8.0",
20
+ "@voltro/protocol": "0.8.0",
21
+ "@voltro/runtime": "0.8.0",
22
+ "@voltro/sql-postgres": "0.8.0",
23
23
  "effect": "^3.21.2"
24
24
  },
25
25
  "devDependencies": {
26
- "@voltro/testing": "0.6.0",
26
+ "@voltro/testing": "0.8.0",
27
27
  "typescript": "^5.7.0",
28
28
  "vitest": "^3.0.0"
29
29
  }
@@ -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.6.0",
16
- "@voltro/database": "0.6.0",
17
- "@voltro/env": "0.6.0",
18
- "@voltro/plugin-audit": "0.6.0",
19
- "@voltro/plugin-governance": "0.6.0",
20
- "@voltro/protocol": "0.6.0",
21
- "@voltro/runtime": "0.6.0",
15
+ "@voltro/cli": "0.8.0",
16
+ "@voltro/database": "0.8.0",
17
+ "@voltro/env": "0.8.0",
18
+ "@voltro/plugin-audit": "0.8.0",
19
+ "@voltro/plugin-governance": "0.8.0",
20
+ "@voltro/protocol": "0.8.0",
21
+ "@voltro/runtime": "0.8.0",
22
22
  "effect": "^3.21.2"
23
23
  },
24
24
  "devDependencies": {
25
- "@voltro/testing": "0.6.0",
25
+ "@voltro/testing": "0.8.0",
26
26
  "typescript": "^5.7.0",
27
27
  "vitest": "^3.0.0"
28
28
  }