@voltro/cli 0.11.2 → 0.11.3
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 +55 -0
- package/dist/apiBuild-BmPdmhgi.js +2 -0
- package/dist/{apiBuild-BVIiATXr.js → apiBuild-zFDv0u8y.js} +2 -2
- package/dist/bin.js +2 -2
- package/dist/{commands-C0ytPFfl.js → commands-BDGYrBhK.js} +7 -7
- package/dist/{dev-zJGsTTNb.js → dev-CGt0PP1f.js} +1 -1
- package/dist/dev-D_PGP9Kx.js +2 -0
- package/dist/index.js +1 -1
- package/dist/{inspectMetrics-BuBwg1yW.js → inspectMetrics-SRtv8KDy.js} +8 -4
- package/dist/{serveCommand-Dd3oLzbo.js → serveCommand-p6e5Ahgx.js} +3 -3
- package/dist/serveEntry.js +3 -3
- package/dist/{start-ChN6PO-c.js → start-Crl39M38.js} +1 -1
- package/dist/startEntry.js +2 -2
- package/package.json +17 -17
- package/templates/agent-docs/_manifest.json +1 -1
- package/templates/agent-docs/authentication.md +22 -0
- package/templates/agent-docs/data.md +68 -0
- package/templates/agent-docs/database/querying.md +16 -0
- package/templates/agent-docs/internationalization.md +13 -1
- package/templates/agent-docs/routing.md +17 -0
- 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 +8 -8
- package/templates/apps/changelog/package.json +6 -6
- 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 +8 -8
- package/templates/apps/frontend-blank/package.json +7 -7
- package/templates/apps/frontend-contact/package.json +7 -7
- package/templates/apps/frontend-dashboard/package.json +7 -7
- package/templates/apps/frontend-docs/package.json +7 -7
- package/templates/apps/frontend-i18n/package.json +6 -6
- package/templates/apps/frontend-landing/package.json +7 -7
- 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/package.json +6 -6
- package/dist/apiBuild-C_KY5oY_.js +0 -2
- package/dist/dev-DH13Ysgs.js +0 -2
package/CHANGELOG.md
CHANGED
|
@@ -39,6 +39,61 @@ _Changes staged for the next release accumulate here (rolled up from
|
|
|
39
39
|
|
|
40
40
|
---
|
|
41
41
|
|
|
42
|
+
## [0.11.3] — 2026-07-24
|
|
43
|
+
|
|
44
|
+
### Added
|
|
45
|
+
|
|
46
|
+
- **@voltro/runtime** — `crud.*` secure-default CRUD handler helpers + `redactColumns` (A1 core). Each returns an executor you export as a `*.query.server.ts` / `*.mutation.server.ts` default — the descriptor (schemas + `guards`) stays hand-written and browser-safe:
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
// accounts.list.query.server.ts
|
|
50
|
+
import { crud } from '@voltro/runtime'
|
|
51
|
+
export default crud.list('accounts', { redact: ['apiSecret'] })
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
They bake in the invariants a hand-rolled CRUD generator kept getting wrong (the leak class was in the HANDLERS, not the schemas):
|
|
55
|
+
|
|
56
|
+
- **Tenant scope** — `list` / `getById` read through `ctx.store`, which auto-scopes a `tenant()` table; they never `.unscoped()`, so a cross-tenant read is impossible. - **Redaction** — `redact` columns are stripped from every returned row (a credential / secret / salary a read must never ship), on reads AND on the row a `create` / `update` echoes. `redactColumns(rows, cols)` is exported standalone for a hand-written handler that isn't plain CRUD. - **`getById` returns `null`, never throws** — a reactive getter that throws stalls its shared-WS siblings (pairs with the per-subscription error isolation).
|
|
57
|
+
|
|
58
|
+
What they deliberately DON'T do is authorize: a guard runs before the executor, so gating stays on the DESCRIPTOR (`guards: [...]`) — an executor can't gate itself. Keep write descriptors guarded.
|
|
59
|
+
|
|
60
|
+
Scope note: this is the browser-safe, codegen-free core. Deriving the descriptor SCHEMAS from a table (to drop the hand-written `Schema.Struct`) is structurally a codegen concern — a table VALUE can't be imported into a browser-loaded descriptor (it drags the store into the bundle; `rowSchema` is server-only for exactly this reason) — so full schema-derivation + a `.crud()` boot audit for the scope/gating discipline are a separate, planned pass. See `plans/framework-a1-defineCrud.md`.
|
|
61
|
+
- **@voltro/runtime** — `ctx.store.links(junctionTable, anchor)` — a diff-based writer for a many-to-many JUNCTION table (A2). It reconciles the links from one anchor row against a target-id list by writing only the DIFFERENCE:
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
await ctx.store.links('post_tags', { postId: post.id }).set(tagIds) // add missing, remove surplus
|
|
65
|
+
await ctx.store.links('post_tags', { postId: post.id }).add([tagId]) // idempotent
|
|
66
|
+
await ctx.store.links('post_tags', { postId: post.id }).remove([tagId])
|
|
67
|
+
await ctx.store.links('post_tags', { postId: post.id }).list() // current target ids
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Why it belongs in the framework rather than every app: a drop-all-then-reinsert `setLinks` loses data when two writers overlap and makes a reactive subscription on the junction churn every row (flicker) even when nothing changed. `links().set()` touches only the rows that actually differ — the added are inserted, the removed deleted, the unchanged left in place — so a reactive consumer sees a change only for what changed, and `set()` returns `{ added, removed }`. `add`/`remove` are likewise idempotent (they read first and act only on the genuine delta).
|
|
71
|
+
|
|
72
|
+
`anchor` names the source column and its id (`{ postId: 'p1' }`); the target column is the junction's OTHER `reference()` column, auto-detected. A junction with anything but exactly two reference columns is refused with a message naming what it found — use plain `insertMany`/`deleteMany` for a non-standard junction. The writes go through the normal stamped/tenant-scoped store path, so tenant and audit columns are filled as usual. Additive: a new `links` method on `FluentStore` + the `JunctionLinks` interface.
|
|
73
|
+
- **@voltro/client, @voltro/web** — `useSubscription(..., { initialSnapshot })` — the last mile of "SSR-correct first paint, then live" (A5). Pass the value an SSR loader already fetched with `ctx.query` (read it in the component with `useLoaderData()`) and the subscription shows it at the first paint with `loading: false` — it IS real server data — then swaps to the live stream the instant its first snapshot arrives:
|
|
74
|
+
|
|
75
|
+
```tsx
|
|
76
|
+
const seed = useLoaderData<Employee>()
|
|
77
|
+
const { data } = useSubscription('app', 'employees.me', {}, { initialSnapshot: seed })
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
The SSR markup and the hydration render read the same loader value, so they match (no hydration flicker), and the app no longer hand-builds a seed store to bridge loader data into the first render. This is the difference from `fallback`, whose value never came from the server and so keeps `loading: true`; use exactly one of the two. Like `fallback`, `initialSnapshot` guarantees `data` is present, so the call gets the non-union result and needs no `loading` branch. Additive: a new `initialSnapshot` field on `SubscriptionOptions` + an overload; `@voltro/web` re-exports the client surface.
|
|
81
|
+
- **@voltro/cli** — `apis.<name>.authHeaders` in a web `app.config.ts` — a declarative per-reconnect auth-header resolver, so an authenticated split-origin web app no longer hand-mounts `VoltroRuntimeProvider` just to inject a rotating-token thunk (A4). The framework owns the client mount, the reconnect re-resolve, and the SSR-null case (the resolver runs browser-only — it never fires on the server):
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
// app.config.ts
|
|
85
|
+
apis: {
|
|
86
|
+
api: {
|
|
87
|
+
package: '@app/api',
|
|
88
|
+
authHeaders: async () => ({ authorization: `Bearer ${await getToken()}` }),
|
|
89
|
+
},
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Because it's a FUNCTION, the codegen imports it from `app.config.ts` into the client bundle rather than serializing it — so a config that declares `authHeaders` must stay browser-safe (no `node:*` / server-only value imports; a pure env schema is fine, and tree-shakes out). It supersedes a static `headers` on the same api. The provider already resolved a `ResolvableHeaders` thunk fresh per connection generation; this just lets you declare it in config instead of hand-writing a `mount()` call.
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
42
97
|
## [0.11.2] — 2026-07-24
|
|
43
98
|
|
|
44
99
|
### Added
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { Mt as e } from "./inspectMetrics-
|
|
2
|
-
import { d as t, lt as n } from "./dev-
|
|
1
|
+
import { Mt as e } from "./inspectMetrics-SRtv8KDy.js";
|
|
2
|
+
import { d as t, lt as n } from "./dev-CGt0PP1f.js";
|
|
3
3
|
import { isAbsolute as r, join as i, relative as a, resolve as o } from "node:path";
|
|
4
4
|
import { createLogger as s } from "@voltro/logger";
|
|
5
5
|
import { existsSync as c, promises as l } from "node:fs";
|
package/dist/bin.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { bt as e } from "./inspectMetrics-
|
|
3
|
-
import { n as t } from "./commands-
|
|
2
|
+
import { bt as e } from "./inspectMetrics-SRtv8KDy.js";
|
|
3
|
+
import { n as t } from "./commands-BDGYrBhK.js";
|
|
4
4
|
import { createLogger as n } from "@voltro/logger";
|
|
5
5
|
//#region src/bin.ts
|
|
6
6
|
var r = n({ scope: "voltro:cli" }), i = process.argv.slice(2);
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { At as e, D as t, Dt as n, F as r, Ft as i, It as a, K as o, M as s, Mt as c, N as l, Nt as u, Ot as d, P as f, Pt as p, T as m, a as h, b as g, c as _, d as v, f as y, jt as ee, k as b, kt as te, n as ne, o as re, p as ie, s as ae, u as oe, v as se, w as ce, x as le, y as ue } from "./inspectMetrics-
|
|
2
|
-
import { B as de, K as fe, L as pe, R as me, V as he, ct as ge, i as _e, l as ve, n as ye, o as be, p as xe, rt as Se, tt as Ce, ut as we, z as Te } from "./dev-
|
|
1
|
+
import { At as e, D as t, Dt as n, F as r, Ft as i, It as a, K as o, M as s, Mt as c, N as l, Nt as u, Ot as d, P as f, Pt as p, T as m, a as h, b as g, c as _, d as v, f as y, jt as ee, k as b, kt as te, n as ne, o as re, p as ie, s as ae, u as oe, v as se, w as ce, x as le, y as ue } from "./inspectMetrics-SRtv8KDy.js";
|
|
2
|
+
import { B as de, K as fe, L as pe, R as me, V as he, ct as ge, i as _e, l as ve, n as ye, o as be, p as xe, rt as Se, tt as Ce, ut as we, z as Te } from "./dev-CGt0PP1f.js";
|
|
3
3
|
import { a as x, c as Ee, i as De, o as Oe, s as ke } from "./devActivity-1WtIVyHc.js";
|
|
4
|
-
import { i as Ae, n as je, s as Me, t as Ne } from "./apiBuild-
|
|
5
|
-
import { t as Pe } from "./start-
|
|
6
|
-
import { n as Fe, r as Ie } from "./serveCommand-
|
|
4
|
+
import { i as Ae, n as je, s as Me, t as Ne } from "./apiBuild-zFDv0u8y.js";
|
|
5
|
+
import { t as Pe } from "./start-Crl39M38.js";
|
|
6
|
+
import { n as Fe, r as Ie } from "./serveCommand-p6e5Ahgx.js";
|
|
7
7
|
import { basename as S, dirname as C, isAbsolute as Le, join as w, relative as T, resolve as E, sep as Re } from "node:path";
|
|
8
8
|
import { fileURLToPath as ze, pathToFileURL as Be } from "node:url";
|
|
9
9
|
import { FileSystem as Ve } from "@effect/platform";
|
|
@@ -728,10 +728,10 @@ external scheduler owns the once-only guarantee.
|
|
|
728
728
|
}, tr = async (e) => {
|
|
729
729
|
let t = await ae(e);
|
|
730
730
|
if (!t) {
|
|
731
|
-
let { loadApiConfig: t } = await import("./dev-
|
|
731
|
+
let { loadApiConfig: t } = await import("./dev-D_PGP9Kx.js"), n = await t(e);
|
|
732
732
|
if (n) {
|
|
733
733
|
B.info("building api app", { app: n.name ?? "(unnamed)" });
|
|
734
|
-
let { runApiBuild: t, runServeBundleBuild: r } = await import("./apiBuild-
|
|
734
|
+
let { runApiBuild: t, runServeBundleBuild: r } = await import("./apiBuild-BmPdmhgi.js");
|
|
735
735
|
await t(e);
|
|
736
736
|
try {
|
|
737
737
|
await r(e);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $ as e, A as t, B as n, C as r, Dt as i, E as a, Et as o, G as s, H as c, Lt as l, O as u, Pt as d, Q as f, R as p, U as m, V as h, W as g, X as _, Y as ee, Z as te, _t as ne, at as re, ct as ie, dt as ae, et as v, ft as oe, gt as y, ht as se, it as ce, j as le, kt as ue, l as b, lt as de, mt as fe, nt as pe, ot as me, pt as he, rt as ge, st as _e, t as ve, tt as ye, ut as be, vt as xe, w as Se, xt as Ce, yt as we } from "./inspectMetrics-
|
|
1
|
+
import { $ as e, A as t, B as n, C as r, Dt as i, E as a, Et as o, G as s, H as c, Lt as l, O as u, Pt as d, Q as f, R as p, U as m, V as h, W as g, X as _, Y as ee, Z as te, _t as ne, at as re, ct as ie, dt as ae, et as v, ft as oe, gt as y, ht as se, it as ce, j as le, kt as ue, l as b, lt as de, mt as fe, nt as pe, ot as me, pt as he, rt as ge, st as _e, t as ve, tt as ye, ut as be, vt as xe, w as Se, xt as Ce, yt as we } from "./inspectMetrics-SRtv8KDy.js";
|
|
2
2
|
import { i as x, n as Te, t as Ee } from "./startupRunner-DhlX9nqd.js";
|
|
3
3
|
import { r as De } from "./devActivity-1WtIVyHc.js";
|
|
4
4
|
import { t as Oe } from "./frameworkInspectState-CX2250XB.js";
|
package/dist/index.js
CHANGED
|
@@ -2754,7 +2754,8 @@ import {
|
|
|
2754
2754
|
browserWsUrl: t.url,
|
|
2755
2755
|
proxyTarget: void 0,
|
|
2756
2756
|
...t.serverUrl ? { serverOrigin: t.serverUrl } : {},
|
|
2757
|
-
...t.headers ? { headers: t.headers } : {}
|
|
2757
|
+
...t.headers ? { headers: t.headers } : {},
|
|
2758
|
+
...t.authHeaders ? { hasAuthHeaders: !0 } : {}
|
|
2758
2759
|
}), Di = (e, t, n) => {
|
|
2759
2760
|
let r = t.transport?.wsPath ?? n.wsPath;
|
|
2760
2761
|
return {
|
|
@@ -2764,7 +2765,8 @@ import {
|
|
|
2764
2765
|
proxyTarget: `ws://localhost:${n.port}`,
|
|
2765
2766
|
...r ? { wsPath: r } : {},
|
|
2766
2767
|
...t.serverUrl ? { serverOrigin: t.serverUrl } : {},
|
|
2767
|
-
...t.headers ? { headers: t.headers } : {}
|
|
2768
|
+
...t.headers ? { headers: t.headers } : {},
|
|
2769
|
+
...t.authHeaders ? { hasAuthHeaders: !0 } : {}
|
|
2768
2770
|
};
|
|
2769
2771
|
}, Oi = (e) => {
|
|
2770
2772
|
let t = /* @__PURE__ */ new Map();
|
|
@@ -2891,9 +2893,11 @@ import {
|
|
|
2891
2893
|
""
|
|
2892
2894
|
].join("\n"), u = r(e, "src", "pages"), { pages: d, dirs: f } = await $(u);
|
|
2893
2895
|
await rr(u, d);
|
|
2894
|
-
let p = (e) => `${e.replace(/[^a-zA-Z0-9]/g, "_")}AppGroup`, m = (e) => `${e.replace(/[^a-zA-Z0-9]/g, "_")}AppDescriptors`, h = n.map((e) => `import { appGroup as ${p(e.name)}, appDescriptors as ${m(e.name)} } from '${e.pkg}/rpcGroup'`)
|
|
2896
|
+
let p = (e) => `${e.replace(/[^a-zA-Z0-9]/g, "_")}AppGroup`, m = (e) => `${e.replace(/[^a-zA-Z0-9]/g, "_")}AppDescriptors`, h = n.map((e) => `import { appGroup as ${p(e.name)}, appDescriptors as ${m(e.name)} } from '${e.pkg}/rpcGroup'`);
|
|
2897
|
+
n.some((e) => e.hasAuthHeaders) && (h.push("import __voltroAppConfig from '../app.config'"), h.push("const __voltroAuthHeaders = (__voltroAppConfig as { readonly apis?: Record<string, { readonly authHeaders?: () => Record<string, string> | Promise<Record<string, string>> }> }).apis"));
|
|
2898
|
+
let g = n.map((e) => {
|
|
2895
2899
|
let t = [`group: ${p(e.name)}`];
|
|
2896
|
-
return t.push(`descriptors: ${m(e.name)}`), t.push(`wsUrl: ${JSON.stringify(e.browserWsUrl)}`), e.headers && t.push(`headers: ${JSON.stringify(e.headers)}`), ` ${JSON.stringify(e.name)}: { ${t.join(", ")} },`;
|
|
2900
|
+
return t.push(`descriptors: ${m(e.name)}`), t.push(`wsUrl: ${JSON.stringify(e.browserWsUrl)}`), e.hasAuthHeaders ? t.push(`headers: __voltroAuthHeaders?.[${JSON.stringify(e.name)}]?.authHeaders`) : e.headers && t.push(`headers: ${JSON.stringify(e.headers)}`), ` ${JSON.stringify(e.name)}: { ${t.join(", ")} },`;
|
|
2897
2901
|
}), _, v, b;
|
|
2898
2902
|
if (d.length > 0) {
|
|
2899
2903
|
let e = d.map((e) => ` ${JSON.stringify(e.pattern)}: () => import('../src/pages/${e.file}'),`), n = [];
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { kt as e, t } from "./inspectMetrics-
|
|
2
|
-
import { $ as n, A as r, C as i, D as a, E as o, F as s, G as c, H as l, I as u, J as d, M as f, N as p, O as ee, P as m, Q as h, S as g, T as te, U as ne, W as re, X as _, Y as ie, Z as ae, _ as oe, at as v, b as se, d as ce, et as y, f as b, g as le, h as ue, i as x, it as de, j as S, k as C, m as fe, n as pe, nt as me, o as w, ot as T, q as he, s as ge, st as _e, t as ve, ut as E, v as ye, w as be, x as xe, y as D } from "./dev-
|
|
1
|
+
import { kt as e, t } from "./inspectMetrics-SRtv8KDy.js";
|
|
2
|
+
import { $ as n, A as r, C as i, D as a, E as o, F as s, G as c, H as l, I as u, J as d, M as f, N as p, O as ee, P as m, Q as h, S as g, T as te, U as ne, W as re, X as _, Y as ie, Z as ae, _ as oe, at as v, b as se, d as ce, et as y, f as b, g as le, h as ue, i as x, it as de, j as S, k as C, m as fe, n as pe, nt as me, o as w, ot as T, q as he, s as ge, st as _e, t as ve, ut as E, v as ye, w as be, x as xe, y as D } from "./dev-CGt0PP1f.js";
|
|
3
3
|
import { a as Se, r as Ce } from "./startupRunner-DhlX9nqd.js";
|
|
4
|
-
import { n as we, s as O } from "./apiBuild-
|
|
4
|
+
import { n as we, s as O } from "./apiBuild-zFDv0u8y.js";
|
|
5
5
|
import { t as Te } from "./bootTiming-BdyP9nYw.js";
|
|
6
6
|
import { join as Ee } from "node:path";
|
|
7
7
|
import { pathToFileURL as De } from "node:url";
|
package/dist/serveEntry.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { bt as e } from "./inspectMetrics-
|
|
2
|
-
import { dt as t } from "./dev-
|
|
1
|
+
import { bt as e } from "./inspectMetrics-SRtv8KDy.js";
|
|
2
|
+
import { dt as t } from "./dev-CGt0PP1f.js";
|
|
3
3
|
import { a as n } from "./startupRunner-DhlX9nqd.js";
|
|
4
|
-
import { t as r } from "./serveCommand-
|
|
4
|
+
import { t as r } from "./serveCommand-p6e5Ahgx.js";
|
|
5
5
|
export { e as loadDotEnv, n as registerAppModules, t as registerDriver, r as runServe };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as e, C as t, Ct as n, E as r, Et as i, G as a, H as o, I as s, J as c, Mt as l, N as u, O as d, R as ee, St as f, Tt as p, U as m, V as h, W as g, Z as _, _ as v, _t as te, a as y, at as b, b as x, c as ne, f as S, g as re, gt as C, h as w, i as T, j as E, k as D, kt as O, m as k, o as ie, p as ae, q as A, r as oe, s as se, t as ce, v as j, w as le, wt as M, y as N } from "./inspectMetrics-
|
|
1
|
+
import { A as e, C as t, Ct as n, E as r, Et as i, G as a, H as o, I as s, J as c, Mt as l, N as u, O as d, R as ee, St as f, Tt as p, U as m, V as h, W as g, Z as _, _ as v, _t as te, a as y, at as b, b as x, c as ne, f as S, g as re, gt as C, h as w, i as T, j as E, k as D, kt as O, m as k, o as ie, p as ae, q as A, r as oe, s as se, t as ce, v as j, w as le, wt as M, y as N } from "./inspectMetrics-SRtv8KDy.js";
|
|
2
2
|
import { t as ue } from "./bootTiming-BdyP9nYw.js";
|
|
3
3
|
import { dirname as P, extname as F, join as I, resolve as L } from "node:path";
|
|
4
4
|
import { fileURLToPath as R, pathToFileURL as de } from "node:url";
|
package/dist/startEntry.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { bt as e } from "./inspectMetrics-
|
|
2
|
-
import { t } from "./start-
|
|
1
|
+
import { bt as e } from "./inspectMetrics-SRtv8KDy.js";
|
|
2
|
+
import { t } from "./start-Crl39M38.js";
|
|
3
3
|
export { e as loadDotEnv, t as runStartCommand };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@voltro/cli",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.3",
|
|
4
4
|
"description": "The `voltro` CLI — dev server, codegen, migrations, project scaffolding, agent-docs seeding, and production serve.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"voltro",
|
|
@@ -62,22 +62,22 @@
|
|
|
62
62
|
"@effect/platform-node": "^0.107.0",
|
|
63
63
|
"@effect/sql": "^0.51.1",
|
|
64
64
|
"@effect/workflow": "^0.18.2",
|
|
65
|
-
"@voltro/ai": "0.11.
|
|
66
|
-
"@voltro/cache": "0.11.
|
|
67
|
-
"@voltro/data-transfer": "0.11.
|
|
68
|
-
"@voltro/database": "0.11.
|
|
69
|
-
"@voltro/env": "0.11.
|
|
70
|
-
"@voltro/kv": "0.11.
|
|
71
|
-
"@voltro/logger": "0.11.
|
|
72
|
-
"@voltro/plugin-auth": "0.11.
|
|
73
|
-
"@voltro/plugin-broadcast": "0.11.
|
|
74
|
-
"@voltro/plugin-mail": "0.11.
|
|
75
|
-
"@voltro/plugin-storage": "0.11.
|
|
76
|
-
"@voltro/plugin-webhooks": "0.11.
|
|
77
|
-
"@voltro/protocol": "0.11.
|
|
78
|
-
"@voltro/runtime": "0.11.
|
|
79
|
-
"@voltro/serverless": "0.11.
|
|
80
|
-
"@voltro/workflow": "0.11.
|
|
65
|
+
"@voltro/ai": "0.11.3",
|
|
66
|
+
"@voltro/cache": "0.11.3",
|
|
67
|
+
"@voltro/data-transfer": "0.11.3",
|
|
68
|
+
"@voltro/database": "0.11.3",
|
|
69
|
+
"@voltro/env": "0.11.3",
|
|
70
|
+
"@voltro/kv": "0.11.3",
|
|
71
|
+
"@voltro/logger": "0.11.3",
|
|
72
|
+
"@voltro/plugin-auth": "0.11.3",
|
|
73
|
+
"@voltro/plugin-broadcast": "0.11.3",
|
|
74
|
+
"@voltro/plugin-mail": "0.11.3",
|
|
75
|
+
"@voltro/plugin-storage": "0.11.3",
|
|
76
|
+
"@voltro/plugin-webhooks": "0.11.3",
|
|
77
|
+
"@voltro/protocol": "0.11.3",
|
|
78
|
+
"@voltro/runtime": "0.11.3",
|
|
79
|
+
"@voltro/serverless": "0.11.3",
|
|
80
|
+
"@voltro/workflow": "0.11.3",
|
|
81
81
|
"chokidar": "^5.0.0",
|
|
82
82
|
"ioredis": "^5.11.1",
|
|
83
83
|
"tinyglobby": "^0.2.17",
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
"group": null,
|
|
54
54
|
"description": "How Voltro's reactive data layer works — queries, mutations, actions, streams, all over one WebSocket with typed errors and tracked dependencies.",
|
|
55
55
|
"path": "agent-docs/data.md",
|
|
56
|
-
"files":
|
|
56
|
+
"files": 16
|
|
57
57
|
},
|
|
58
58
|
{
|
|
59
59
|
"id": "database/advancedqueries",
|
|
@@ -723,6 +723,28 @@ The strategy above only *verifies* — your browser still has to *send* the toke
|
|
|
723
723
|
|
|
724
724
|
This is **required when the api is a separate origin** (the common case — `api.example.com` vs your web origin): there is no shared cookie and no upgrade header, so without `headers` every subscription connects **anonymous** (the shell renders, but user-/tenant-scoped data stays empty). A static object works for non-rotating tokens; never hardcode a secret literal — it ships to the browser.
|
|
725
725
|
|
|
726
|
+
### Declaratively — `apis.<name>.authHeaders` in `app.config.ts`
|
|
727
|
+
|
|
728
|
+
Rather than hand-writing a `mount()` entry just to inject the thunk, declare it on the api in `app.config.ts`. The framework owns the client mount, the SSR-null case (the resolver runs browser-only — it never fires on the server), and the re-resolve on every reconnect; you supply only the token function:
|
|
729
|
+
|
|
730
|
+
```ts
|
|
731
|
+
// app.config.ts
|
|
732
|
+
export default {
|
|
733
|
+
type: 'web' as const,
|
|
734
|
+
apis: {
|
|
735
|
+
api: {
|
|
736
|
+
package: '@app/api',
|
|
737
|
+
// Resolved fresh on every (re)connect — a rotating token is pulled anew:
|
|
738
|
+
authHeaders: async () => ({
|
|
739
|
+
authorization: `Bearer ${(await supabase.auth.getSession()).data.session?.access_token ?? ''}`,
|
|
740
|
+
}),
|
|
741
|
+
},
|
|
742
|
+
},
|
|
743
|
+
}
|
|
744
|
+
```
|
|
745
|
+
|
|
746
|
+
`authHeaders` is a **function**, so it is imported from `app.config.ts` into the client bundle rather than serialized — the file must stay browser-safe (no `node:*` / server-only value imports; the env schema and other pure config are fine). It supersedes a static `headers` on the same api. This is the preferred form; reach for a hand-written `mount()` only when you need to wrap the tree in your own provider as well.
|
|
747
|
+
|
|
726
748
|
## WorkOS
|
|
727
749
|
|
|
728
750
|
```ts
|
|
@@ -315,6 +315,8 @@ export default defineExecutor(notesSummary, async (_input, ctx) => {
|
|
|
315
315
|
|
|
316
316
|
It's a runtime identity (returns the handler unchanged) — the whole value is the compile check. The Effect error and requirement channels stay inferred; only the success value is constrained. A **descriptor-return** (reactive) executor is allowed through unchecked: the store produces the rows, so a value-level return type can't express the row-vs-`output` check. `defineExecutor` works the same for `defineMutation` / `defineAction` handlers.
|
|
317
317
|
|
|
318
|
+
> **Composing one executor inside another (e.g. a workflow `step`).** The value `defineExecutor` returns is typed as the `ExecutorReturn` UNION (`Output | Promise | Effect | descriptor-return`), so it has no `.pipe` — you can't feed the wrapped default export straight into another Effect. Export the handler's raw `Effect` separately (a named `export const execute = …`, or import the un-wrapped function) and compose THAT; keep the `defineExecutor`-wrapped default only as the procedure's entry point. The union return is deliberate — it's what lets one helper type every executor shape — so this is a "import the raw effect for composition" convention, not a gap to route around.
|
|
319
|
+
|
|
318
320
|
## Auto-optimistic source
|
|
319
321
|
|
|
320
322
|
`source` also connects query caches to mutation `target` metadata:
|
|
@@ -1947,6 +1949,72 @@ When no analytics sink is configured the framework provides the no-op sink: the
|
|
|
1947
1949
|
|
|
1948
1950
|
|
|
1949
1951
|
|
|
1952
|
+
---
|
|
1953
|
+
|
|
1954
|
+
<!-- source: en/data/crud.md -->
|
|
1955
|
+
## CRUD helpers
|
|
1956
|
+
|
|
1957
|
+
_crud.* secure-default handler helpers — tenant-scoped reads, column redaction, and null-not-throw getById, so the CRUD tail of a handler is one honest line._
|
|
1958
|
+
|
|
1959
|
+
Most of a plain list / get / create / update / delete handler is the same five lines every time — and getting those lines subtly wrong is how data leaks. The `crud.*` helpers from `@voltro/runtime` give you the **executor** with the secure defaults baked in; you still write the descriptor (schemas + `guards`), which is where the browser-safe wire contract and the authorization live.
|
|
1960
|
+
|
|
1961
|
+
```ts
|
|
1962
|
+
// accounts.list.query.server.ts
|
|
1963
|
+
import { crud } from '@voltro/runtime'
|
|
1964
|
+
|
|
1965
|
+
export default crud.list('accounts', { redact: ['apiSecret'] })
|
|
1966
|
+
```
|
|
1967
|
+
|
|
1968
|
+
```ts
|
|
1969
|
+
// accounts.list.query.ts — the descriptor stays hand-written + browser-safe
|
|
1970
|
+
import { defineQuery } from '@voltro/protocol'
|
|
1971
|
+
import { Schema } from 'effect'
|
|
1972
|
+
|
|
1973
|
+
export default defineQuery({
|
|
1974
|
+
name: 'accounts.list',
|
|
1975
|
+
input: Schema.Struct({}),
|
|
1976
|
+
// note: the wire output OMITS apiSecret, so it never reaches the client
|
|
1977
|
+
output: Schema.Array(Schema.Struct({ id: Schema.String, name: Schema.String })),
|
|
1978
|
+
})
|
|
1979
|
+
```
|
|
1980
|
+
|
|
1981
|
+
## What the defaults bake in
|
|
1982
|
+
|
|
1983
|
+
- **Tenant scope.** `crud.list` and `crud.getById` read through `ctx.store`, which auto-scopes a `tenant()` table. They never call `.unscoped()`, so a cross-tenant read is impossible through them — `payslips.list` cannot return another tenant's rows.
|
|
1984
|
+
- **Redaction.** `redact` names columns stripped from every returned row — a credential, a token hash, a salary that a generated read must never ship. It applies to reads and to the row a `create` / `update` echoes back. Declare the same omission in the descriptor's `output` schema so the column never reaches the client at all; the helper is the runtime guarantee that it doesn't, whatever the schema says.
|
|
1985
|
+
- **`getById` returns `null`, never throws.** A reactive getter that throws takes its shared-WebSocket siblings down with it. `crud.getById` resolves `null` for an absent row.
|
|
1986
|
+
|
|
1987
|
+
## The helpers
|
|
1988
|
+
|
|
1989
|
+
| Helper | Executor it returns |
|
|
1990
|
+
|---|---|
|
|
1991
|
+
| `crud.list(table, { redact? })` | tenant-scoped list of every row, redacted |
|
|
1992
|
+
| `crud.getById(table, { redact? })` | one row by `input.id`, or `null` — redacted |
|
|
1993
|
+
| `crud.create(table, { redact? })` | insert `input`; id/tenant/audit auto-stamped; echoes the redacted row |
|
|
1994
|
+
| `crud.update(table, { redact? })` | patch `{ id, ...patch }`; returns the updated row or `null` |
|
|
1995
|
+
| `crud.remove(table)` | delete `input.id`; returns `{ deleted }` |
|
|
1996
|
+
|
|
1997
|
+
`redactColumns(rows, cols)` is exported standalone for a hand-written handler that isn't plain CRUD but still needs to redact declaratively.
|
|
1998
|
+
|
|
1999
|
+
## What they deliberately don't do — authorization
|
|
2000
|
+
|
|
2001
|
+
A guard runs *before* the executor, so gating lives on the **descriptor**, not the handler — an executor cannot gate itself. Keep every write descriptor guarded:
|
|
2002
|
+
|
|
2003
|
+
```ts
|
|
2004
|
+
export default defineMutation({
|
|
2005
|
+
name: 'accounts.create',
|
|
2006
|
+
input: AccountInput,
|
|
2007
|
+
output: Account,
|
|
2008
|
+
guards: [requireScope('accounts:write')], // ← the gate; crud.create does not add one
|
|
2009
|
+
})
|
|
2010
|
+
```
|
|
2011
|
+
|
|
2012
|
+
## Scope — why the schema is still hand-written
|
|
2013
|
+
|
|
2014
|
+
These helpers give you the secure **handler**, not schema derivation. Deriving the descriptor's `input`/`output` from the table automatically would need the table VALUE inside the descriptor file — and a descriptor is loaded value-level by the browser client, so importing a table there drags the store and driver into the browser bundle (the boundary guard aborts the boot; `rowSchema` is server-only for exactly this reason). Full schema-derivation, and a boot audit that fails when a `tenant()` table's list reads unscoped or a write goes ungated, are a separate planned pass — the handler defaults above are the part that ships browser-safe today and closes the leak class.
|
|
2015
|
+
|
|
2016
|
+
|
|
2017
|
+
|
|
1950
2018
|
---
|
|
1951
2019
|
|
|
1952
2020
|
<!-- source: en/data/subscribers.md -->
|
|
@@ -1044,6 +1044,22 @@ The framework deliberately avoids the Prisma / TypeORM "auto-generated junction"
|
|
|
1044
1044
|
|
|
1045
1045
|
3. **Schema is explicit**. Looking at your `database/` directory tells you exactly which tables exist. No hidden auto-generated tables to chase down at migration time.
|
|
1046
1046
|
|
|
1047
|
+
## Writing links — `store.links`
|
|
1048
|
+
|
|
1049
|
+
Reconciling the set of links from one row (a post's tags, a user's orgs) by hand — read the current rows, work out which to insert and which to delete — is fiddly and easy to get wrong. A drop-all-then-reinsert shortcut loses data when two requests overlap and makes a reactive subscription on the junction churn *every* row even when nothing changed. `ctx.store.links(junctionTable, anchor)` does the diff for you:
|
|
1050
|
+
|
|
1051
|
+
```ts
|
|
1052
|
+
// anchor names the source column + id; the target column is auto-detected
|
|
1053
|
+
await ctx.store.links('org_memberships', { userId: user.id }).set(orgIds) // reconcile to exactly orgIds
|
|
1054
|
+
await ctx.store.links('org_memberships', { userId: user.id }).add([orgId]) // idempotent — no-op if already linked
|
|
1055
|
+
await ctx.store.links('org_memberships', { userId: user.id }).remove([orgId])
|
|
1056
|
+
const orgIds = await ctx.store.links('org_memberships', { userId: user.id }).list()
|
|
1057
|
+
```
|
|
1058
|
+
|
|
1059
|
+
`set(targetIds)` writes only the **difference**: the missing links are inserted, the surplus deleted, and links that are already correct are left untouched — so a reactive consumer sees a change only for what actually changed, and it returns `{ added, removed }`. `add` and `remove` read first and act only on the genuine delta, so both are idempotent.
|
|
1060
|
+
|
|
1061
|
+
The **target column** is the junction's *other* `reference()` column — the one the anchor doesn't name. A junction with anything but exactly two reference columns is refused (write it by hand with `insertMany` / `deleteMany`). The writes go through the normal store path, so a junction that carries `tenant()` / `audit()` gets those columns stamped as usual. A junction with extra business columns (a membership `role`, a tag `order`) needs those set per row — `links` only manages the two FK columns, so insert those rows directly.
|
|
1062
|
+
|
|
1047
1063
|
## SQL shape
|
|
1048
1064
|
|
|
1049
1065
|
The framework emits an INNER JOIN through the junction:
|
|
@@ -178,13 +178,25 @@ useT('does.not.exist') // ✗ compile error — not a catalog key
|
|
|
178
178
|
|
|
179
179
|
It's almost pure type refinement — the runtime is the same `useT` / `useTFn` / `<T>` (plus a `.dynamic` escape, below), only the signatures narrow to your catalog. Requires the base catalog to be `as const` (so its message strings survive as literal types).
|
|
180
180
|
|
|
181
|
-
**Scope.** Simple `{name}` and single-argument `{count, number}` placeholders are extracted and required. For a *nested* inline ICU message (`{count, plural, one {…} other {…}}` / `select`), the **top-level arg** (`count`) is required and a branch's inner text is NOT mistaken for a var — so `t('duration', { count })` typechecks and renders. A REAL var nested inside a branch (`other {#
|
|
181
|
+
**Scope.** Simple `{name}` and single-argument `{count, number}` placeholders are extracted and required. For a *nested* inline ICU message (`{count, plural, one {…} other {…}}` / `select`), the **top-level arg** (`count`) is required and a branch's inner text is NOT mistaken for a var — so `t('duration', { count })` typechecks and renders. A REAL var nested inside a branch (`other {# blockers in {discipline}}`) is not collected, so it reads as *not required* rather than wrongly required; pass it via the loose values bag.
|
|
182
|
+
|
|
183
|
+
To get that nested var **compiler-required**, split the plural into simple sibling keys and pick with a JS branch — each key is then a plain message where every var is top-level:
|
|
184
|
+
|
|
185
|
+
```tsx
|
|
186
|
+
// catalog: 'blocker.one': '{count} blocker in {discipline}', 'blocker.other': '{count} blockers in {discipline}'
|
|
187
|
+
count === 1
|
|
188
|
+
? t('blocker.one', { count, discipline }) // ✓ both vars required
|
|
189
|
+
: t('blocker.other', { count, discipline })
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
(The [`plural()` helper](/docs/i18n/formatting) only substitutes `{count}`, so it doesn't type or fill a second var — use it for count-only messages.) `<T>` gets a typed key with loose values, because its rich-text `<tag>` renderers can't be modelled by placeholder extraction.
|
|
182
193
|
|
|
183
194
|
### Runtime-computed keys — `t.dynamic`
|
|
184
195
|
|
|
185
196
|
For a key you build at runtime, do NOT cast it to the catalog key union — that union spans placeholder-bearing keys, so the call then demands a spurious 2nd ICU arg. Use `t.dynamic`, a plain-string escape with no forced args, on both `useT` and the `useTFn()` result:
|
|
186
197
|
|
|
187
198
|
```tsx
|
|
199
|
+
import { useTFn } from '@app/messages' // your createTypedMessages() barrel — `.dynamic` lives on these
|
|
188
200
|
const t = useTFn()
|
|
189
201
|
t.dynamic(`status.${row.state}`) // ✓ plain string, no forced arg
|
|
190
202
|
t.dynamic(`greeting.${kind}`, { name }) // values still allowed, but loose
|
|
@@ -793,6 +793,23 @@ Two consequences worth designing around:
|
|
|
793
793
|
a side effect), put it in an effect or use `useSubscription` — do not rely on
|
|
794
794
|
the loader firing a second time.
|
|
795
795
|
|
|
796
|
+
#### SSR first paint, then live — `initialSnapshot`
|
|
797
|
+
|
|
798
|
+
When a page wants the SSR-rendered data AND a live subscription, hand the loader's value to `useSubscription` as its `initialSnapshot`. The loader fetches the data on the server with `ctx.query`; the subscription shows that value at the first paint with `loading: false` — it is real server data, not a placeholder — then swaps to the live stream the instant its first snapshot arrives:
|
|
799
|
+
|
|
800
|
+
```tsx
|
|
801
|
+
export const loader = async ({ query }) => query('employees.me', {})
|
|
802
|
+
|
|
803
|
+
export default function Profile() {
|
|
804
|
+
const seed = useLoaderData<Employee>()
|
|
805
|
+
// First paint shows the SSR value; the WS stream takes over seamlessly.
|
|
806
|
+
const { data } = useSubscription<Employee>('app', 'employees.me', {}, { initialSnapshot: seed })
|
|
807
|
+
return <ProfileCard employee={data} />
|
|
808
|
+
}
|
|
809
|
+
```
|
|
810
|
+
|
|
811
|
+
Because the SSR markup and the hydration render read the same loader value, they match — no hydration flicker — and you don't hand-build a seed store to bridge the two. This is distinct from `fallback`, whose value never came from the server and so keeps `loading: true`; use exactly one of the two.
|
|
812
|
+
|
|
796
813
|
Client-side navigation is unchanged: moving to another route runs that route's
|
|
797
814
|
loaders in the browser as usual. A `spa` page has no server render, so its
|
|
798
815
|
loader runs on the client on first mount.
|
|
@@ -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.11.
|
|
15
|
-
"@voltro/cli": "0.11.
|
|
16
|
-
"@voltro/database": "0.11.
|
|
17
|
-
"@voltro/env": "0.11.
|
|
18
|
-
"@voltro/protocol": "0.11.
|
|
19
|
-
"@voltro/runtime": "0.11.
|
|
14
|
+
"@voltro/ai": "0.11.3",
|
|
15
|
+
"@voltro/cli": "0.11.3",
|
|
16
|
+
"@voltro/database": "0.11.3",
|
|
17
|
+
"@voltro/env": "0.11.3",
|
|
18
|
+
"@voltro/protocol": "0.11.3",
|
|
19
|
+
"@voltro/runtime": "0.11.3",
|
|
20
20
|
"effect": "^3.21.2"
|
|
21
21
|
},
|
|
22
22
|
"devDependencies": {
|
|
23
|
-
"@voltro/testing": "0.11.
|
|
23
|
+
"@voltro/testing": "0.11.3",
|
|
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.11.
|
|
16
|
-
"@voltro/database": "0.11.
|
|
17
|
-
"@voltro/env": "0.11.
|
|
18
|
-
"@voltro/plugin-auth": "0.11.
|
|
19
|
-
"@voltro/protocol": "0.11.
|
|
20
|
-
"@voltro/runtime": "0.11.
|
|
21
|
-
"@voltro/sql-postgres": "0.11.
|
|
15
|
+
"@voltro/cli": "0.11.3",
|
|
16
|
+
"@voltro/database": "0.11.3",
|
|
17
|
+
"@voltro/env": "0.11.3",
|
|
18
|
+
"@voltro/plugin-auth": "0.11.3",
|
|
19
|
+
"@voltro/protocol": "0.11.3",
|
|
20
|
+
"@voltro/runtime": "0.11.3",
|
|
21
|
+
"@voltro/sql-postgres": "0.11.3",
|
|
22
22
|
"effect": "^3.21.2"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
|
-
"@voltro/testing": "0.11.
|
|
25
|
+
"@voltro/testing": "0.11.3",
|
|
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.11.
|
|
16
|
-
"@voltro/database": "0.11.
|
|
17
|
-
"@voltro/env": "0.11.
|
|
18
|
-
"@voltro/plugin-multitenancy": "0.11.
|
|
19
|
-
"@voltro/protocol": "0.11.
|
|
20
|
-
"@voltro/runtime": "0.11.
|
|
15
|
+
"@voltro/cli": "0.11.3",
|
|
16
|
+
"@voltro/database": "0.11.3",
|
|
17
|
+
"@voltro/env": "0.11.3",
|
|
18
|
+
"@voltro/plugin-multitenancy": "0.11.3",
|
|
19
|
+
"@voltro/protocol": "0.11.3",
|
|
20
|
+
"@voltro/runtime": "0.11.3",
|
|
21
21
|
"effect": "^3.21.2"
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
24
|
-
"@voltro/testing": "0.11.
|
|
24
|
+
"@voltro/testing": "0.11.3",
|
|
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.11.
|
|
16
|
-
"@voltro/database": "0.11.
|
|
17
|
-
"@voltro/env": "0.11.
|
|
18
|
-
"@voltro/plugin-deactivation": "0.11.
|
|
19
|
-
"@voltro/protocol": "0.11.
|
|
20
|
-
"@voltro/runtime": "0.11.
|
|
15
|
+
"@voltro/cli": "0.11.3",
|
|
16
|
+
"@voltro/database": "0.11.3",
|
|
17
|
+
"@voltro/env": "0.11.3",
|
|
18
|
+
"@voltro/plugin-deactivation": "0.11.3",
|
|
19
|
+
"@voltro/protocol": "0.11.3",
|
|
20
|
+
"@voltro/runtime": "0.11.3",
|
|
21
21
|
"effect": "^3.21.2"
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
24
|
-
"@voltro/testing": "0.11.
|
|
24
|
+
"@voltro/testing": "0.11.3",
|
|
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.11.
|
|
16
|
-
"@voltro/database": "0.11.
|
|
17
|
-
"@voltro/env": "0.11.
|
|
18
|
-
"@voltro/plugin-mail": "0.11.
|
|
19
|
-
"@voltro/plugin-multitenancy": "0.11.
|
|
20
|
-
"@voltro/protocol": "0.11.
|
|
21
|
-
"@voltro/runtime": "0.11.
|
|
15
|
+
"@voltro/cli": "0.11.3",
|
|
16
|
+
"@voltro/database": "0.11.3",
|
|
17
|
+
"@voltro/env": "0.11.3",
|
|
18
|
+
"@voltro/plugin-mail": "0.11.3",
|
|
19
|
+
"@voltro/plugin-multitenancy": "0.11.3",
|
|
20
|
+
"@voltro/protocol": "0.11.3",
|
|
21
|
+
"@voltro/runtime": "0.11.3",
|
|
22
22
|
"effect": "^3.21.2",
|
|
23
23
|
"react": "^19.0.0"
|
|
24
24
|
},
|
|
25
25
|
"devDependencies": {
|
|
26
|
-
"@voltro/testing": "0.11.
|
|
26
|
+
"@voltro/testing": "0.11.3",
|
|
27
27
|
"typescript": "^5.7.0",
|
|
28
28
|
"vitest": "^3.0.0"
|
|
29
29
|
}
|