@voltro/cli 0.44.1 → 0.45.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 (101) hide show
  1. package/CHANGELOG.md +104 -1
  2. package/dist/{apiBuild-DvWreIQP.js → apiBuild-CDQBNRDx.js} +1 -1
  3. package/dist/apiBuild-yJCqJYQ8.js +2 -0
  4. package/dist/bin.d.ts +16 -0
  5. package/dist/bin.js +46 -11
  6. package/dist/{build-Ckc-FA7d.js → build-yvsLG77U.js} +7 -7
  7. package/dist/{checkCommand-BNfEs4KG.js → checkCommand-Boxov8lr.js} +11 -11
  8. package/dist/{checkCommand-AlbawIal.js → checkCommand-lrzwOzV1.js} +1 -1
  9. package/dist/codegen-DcE2li1i.js +2 -0
  10. package/dist/{codegen-CbpGCWLG.js → codegen-sbN1Y8PB.js} +37 -37
  11. package/dist/codegenCommand-CQOLytL-.js +144 -0
  12. package/dist/{codemodRunner-BKTnaGOz.js → codemodRunner-5ZhDH3rB.js} +41 -0
  13. package/dist/{commands-CYkHTox2.js → commands-CEkVoFM5.js} +19 -19
  14. package/dist/{dashboardCommand-DHDmlo9A.js → dashboardCommand-0ahyp-zA.js} +1 -1
  15. package/dist/{dataCommand-WFn8CM11.js → dataCommand-D4IlZcih.js} +275 -211
  16. package/dist/{dev-D-JZ6CGQ.js → dev-DAM5CnAa.js} +1856 -1801
  17. package/dist/{dev-BZO5aLdr.js → dev-P4HyeY9W.js} +1 -1
  18. package/dist/devActivity.d.ts +16 -0
  19. package/dist/doctorCommand-2K1dIzYk.js +2 -0
  20. package/dist/{doctorCommand-BlUyESpD.js → doctorCommand-DW2cwAvC.js} +244 -223
  21. package/dist/doctorSourceRelations-CPjSwYuZ.js +83 -0
  22. package/dist/{dormancyCommand-lQxdrU8C.js → dormancyCommand-BqQz7lbM.js} +1 -1
  23. package/dist/{embeddingsCommand-ByBTju6X.js → embeddingsCommand-DJyArpU4.js} +1 -1
  24. package/dist/{envCommand-CpCbYAgR.js → envCommand-CviRZx1L.js} +1 -1
  25. package/dist/{evolveCommand-CBV3-TtK.js → evolveCommand-qdlP-R9G.js} +2 -2
  26. package/dist/index.d.ts +16 -0
  27. package/dist/index.js +1 -1
  28. package/dist/{infoCommand-DJoA9cKD.js → infoCommand-CNWPSUcW.js} +1 -1
  29. package/dist/inspectState.d.ts +16 -0
  30. package/dist/{runtimeTrace-DJ9tPhrc.js → runtimeTrace-BDZSt9W_.js} +1 -1
  31. package/dist/{sdkgen-7dr8-Onw.js → sdkgen-sNaD3oZB.js} +1 -1
  32. package/dist/{serveCommand-BlnAlNQg.js → serveCommand-BBhT2U94.js} +731 -737
  33. package/dist/serveCommand-DY2jLy0o.js +2 -0
  34. package/dist/serveEntry.d.ts +16 -0
  35. package/dist/serveEntry.js +1 -1
  36. package/dist/startEntry.d.ts +16 -0
  37. package/dist/startup.d.ts +16 -0
  38. package/dist/{test-CLYXrZ2F.js → test-CHb7UwvE.js} +1 -1
  39. package/dist/updateCommand-BH6EGuCc.js +2 -0
  40. package/dist/{updateCommand-BvRidHp6.js → updateCommand-rCnhaj84.js} +1 -1
  41. package/dist/{webhooksCommand-BfO_JF2b.js → webhooksCommand-C0iZZfaI.js} +1 -1
  42. package/package.json +23 -17
  43. package/templates/AGENTS.md +1 -1
  44. package/templates/agent-docs/_index.md +1 -1
  45. package/templates/agent-docs/cli.md +41 -1
  46. package/templates/agent-docs/data.md +33 -3
  47. package/templates/agent-docs/plugins/versioning.md +2 -0
  48. package/templates/agent-docs/whats-new.md +93 -6
  49. package/templates/apps/api-ai/package.json +7 -7
  50. package/templates/apps/api-auth/package.json +8 -8
  51. package/templates/apps/api-backend/package.json +7 -7
  52. package/templates/apps/api-backend-deactivation/package.json +7 -7
  53. package/templates/apps/api-backend-mail/package.json +8 -8
  54. package/templates/apps/api-backend-mariadb/package.json +9 -9
  55. package/templates/apps/api-backend-sqlite/package.json +8 -8
  56. package/templates/apps/api-backend-storage/package.json +8 -8
  57. package/templates/apps/api-cms/package.json +10 -10
  58. package/templates/apps/api-collab/package.json +8 -8
  59. package/templates/apps/api-data-advanced/package.json +8 -8
  60. package/templates/apps/api-durable/package.json +8 -8
  61. package/templates/apps/api-feature-flags/package.json +9 -9
  62. package/templates/apps/api-governance/package.json +8 -8
  63. package/templates/apps/api-kv/package.json +8 -8
  64. package/templates/apps/api-moderation/package.json +8 -8
  65. package/templates/apps/api-observability/package.json +8 -8
  66. package/templates/apps/api-ratelimit/package.json +8 -8
  67. package/templates/apps/api-rbac/package.json +8 -8
  68. package/templates/apps/api-rest/package.json +7 -7
  69. package/templates/apps/api-saas/package.json +11 -11
  70. package/templates/apps/api-saas-starter/package.json +10 -10
  71. package/templates/apps/api-search/package.json +8 -8
  72. package/templates/apps/api-status/package.json +8 -8
  73. package/templates/apps/api-versioning/package.json +8 -8
  74. package/templates/apps/api-webhooks/package.json +9 -9
  75. package/templates/apps/changelog/package.json +6 -6
  76. package/templates/apps/edge-functions/package.json +2 -2
  77. package/templates/apps/frontend-admin/package.json +8 -8
  78. package/templates/apps/frontend-app/package.json +9 -9
  79. package/templates/apps/frontend-auth/package.json +8 -8
  80. package/templates/apps/frontend-blank/package.json +7 -7
  81. package/templates/apps/frontend-cms/package.json +9 -9
  82. package/templates/apps/frontend-collab/package.json +10 -10
  83. package/templates/apps/frontend-contact/package.json +7 -7
  84. package/templates/apps/frontend-dashboard/package.json +7 -7
  85. package/templates/apps/frontend-docs/package.json +7 -7
  86. package/templates/apps/frontend-i18n/package.json +6 -6
  87. package/templates/apps/frontend-landing/package.json +7 -7
  88. package/templates/apps/frontend-portal/package.json +8 -8
  89. package/templates/apps/frontend-saas/package.json +8 -8
  90. package/templates/apps/frontend-spa/package.json +7 -7
  91. package/templates/apps/frontend-ssr/package.json +7 -7
  92. package/templates/apps/frontend-ssr-api/package.json +8 -8
  93. package/templates/apps/frontend-static-blog/package.json +6 -6
  94. package/templates/apps/frontend-status/package.json +8 -8
  95. package/templates/apps/mobile-app/package.json +4 -4
  96. package/dist/apiBuild-XaW40W8x.js +0 -2
  97. package/dist/codegen-i8QGcHsi.js +0 -2
  98. package/dist/codegenCommand-iClXoRCW.js +0 -129
  99. package/dist/doctorCommand-BeLOwq7S.js +0 -2
  100. package/dist/serveCommand-Dg_E1ev4.js +0 -2
  101. package/dist/updateCommand-DcKVmLVN.js +0 -2
@@ -0,0 +1,2 @@
1
+ import { n as e } from "./serveCommand-BBhT2U94.js";
2
+ export { e as runServeDispatch };
@@ -20,3 +20,19 @@ export declare const registerDriver: (spec: string, mod: Record<string, unknown>
20
20
  export declare const runServe: (args: ReadonlyArray<string>) => Promise<number>;
21
21
 
22
22
  export { }
23
+
24
+
25
+ declare module '@voltro/protocol' {
26
+ interface VoltroTableNames {
27
+ 'tasks': true;
28
+ 'task_sub_tasks': true;
29
+ '_voltro_workflow_runs': true;
30
+ '_voltro_workflow_run_steps': true;
31
+ '_voltro_workflow_run_events': true;
32
+ '_voltro_workflow_events': true;
33
+ '_voltro_workflow_event_deliveries': true;
34
+ '_voltro_approvals': true;
35
+ '_voltro_connections': true;
36
+ '_voltro_undo_log': true;
37
+ }
38
+ }
@@ -1,5 +1,5 @@
1
1
  import { t as e } from "./loadEnv-D9nEOClM.js";
2
2
  import { r as t } from "./appModuleLoader-C9r9mxZt.js";
3
3
  import { i as n } from "./dialectDriver-czCHYpeH.js";
4
- import { t as r } from "./serveCommand-BlnAlNQg.js";
4
+ import { t as r } from "./serveCommand-BBhT2U94.js";
5
5
  export { e as loadDotEnv, t as registerAppModules, n as registerDriver, r as runServe };
@@ -3,3 +3,19 @@ export declare const loadDotEnv: (argv: ReadonlyArray<string>) => void;
3
3
  export declare const runStartCommand: (args: ReadonlyArray<string>) => Promise<number>;
4
4
 
5
5
  export { }
6
+
7
+
8
+ declare module '@voltro/protocol' {
9
+ interface VoltroTableNames {
10
+ 'tasks': true;
11
+ 'task_sub_tasks': true;
12
+ '_voltro_workflow_runs': true;
13
+ '_voltro_workflow_run_steps': true;
14
+ '_voltro_workflow_run_events': true;
15
+ '_voltro_workflow_events': true;
16
+ '_voltro_workflow_event_deliveries': true;
17
+ '_voltro_approvals': true;
18
+ '_voltro_connections': true;
19
+ '_voltro_undo_log': true;
20
+ }
21
+ }
package/dist/startup.d.ts CHANGED
@@ -63,3 +63,19 @@ export declare interface StartupHandle {
63
63
  export declare const startupTimeoutMsFromEnv: (envValue: string | undefined) => number;
64
64
 
65
65
  export { }
66
+
67
+
68
+ declare module '@voltro/protocol' {
69
+ interface VoltroTableNames {
70
+ 'tasks': true;
71
+ 'task_sub_tasks': true;
72
+ '_voltro_workflow_runs': true;
73
+ '_voltro_workflow_run_steps': true;
74
+ '_voltro_workflow_run_events': true;
75
+ '_voltro_workflow_events': true;
76
+ '_voltro_workflow_event_deliveries': true;
77
+ '_voltro_approvals': true;
78
+ '_voltro_connections': true;
79
+ '_voltro_undo_log': true;
80
+ }
81
+ }
@@ -79,7 +79,7 @@ var d = l({ scope: "voltro:test" }), f = (e) => {
79
79
  }, C = async (e) => {
80
80
  let t = m(e);
81
81
  if (!o(t.root)) return console.error(`voltro test: no such directory: ${t.root}`), 1;
82
- let { checkRpcGroupFreshness: n } = await import("./codegen-i8QGcHsi.js"), r = await n(t.root);
82
+ let { checkRpcGroupFreshness: n } = await import("./codegen-DcE2li1i.js"), r = await n(t.root);
83
83
  if (r.kind === "stale") return console.error(`voltro test: ${r.file} is stale — ${r.reason}.`), console.error(" The tests would run against the previously generated procedure group, pass, and prove nothing about the descriptors you just edited.\n Run `voltro codegen` (or boot `voltro dev` once) and try again."), 1;
84
84
  d.info("running vitest", {
85
85
  root: t.root,
@@ -0,0 +1,2 @@
1
+ import { f as e, p as t } from "./updateCommand-rCnhaj84.js";
2
+ export { e as runApplyCodemods, t as runUpdateCommand };
@@ -1,4 +1,4 @@
1
- import { n as e, r as t, t as n } from "./codemodRunner-BKTnaGOz.js";
1
+ import { n as e, r as t, t as n } from "./codemodRunner-5ZhDH3rB.js";
2
2
  import { basename as r, dirname as i, join as a, relative as o, resolve as s } from "node:path";
3
3
  import { existsSync as c, readFileSync as l, readdirSync as u, statSync as d, writeFileSync as f } from "node:fs";
4
4
  import { totalmem as p } from "node:os";
@@ -222,7 +222,7 @@ createVerifier({ secret: [process.env.WEBHOOK_SECRET, process.env.WEBHOOK_SECRET
222
222
  ...t === void 0 ? {} : { payload: t }
223
223
  };
224
224
  }, S = u({ scope: "voltro:webhooks" }), C = ["--out", "--name"], w = async (e) => {
225
- let { walk: t, loadDiscovered: n } = await import("./dev-BZO5aLdr.js"), { outgoingFromEvents: r } = await import("./webhookDiscovery-il9ti-HE.js");
225
+ let { walk: t, loadDiscovered: n } = await import("./dev-P4HyeY9W.js"), { outgoingFromEvents: r } = await import("./webhookDiscovery-il9ti-HE.js");
226
226
  return r((await n(await t(e))).events.map((e) => ({
227
227
  file: e.file,
228
228
  descriptor: e.descriptor
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voltro/cli",
3
- "version": "0.44.1",
3
+ "version": "0.45.0",
4
4
  "description": "The `voltro` CLI — dev server, codegen, migrations, project scaffolding, agent-docs seeding, and production serve.",
5
5
  "keywords": [
6
6
  "voltro",
@@ -640,6 +640,12 @@
640
640
  "title": "`runImport` returns an ImportOutcome — read `.manifest` for the bundle",
641
641
  "kind": "manual"
642
642
  },
643
+ {
644
+ "version": "0.45.0",
645
+ "id": "0.45.0/01_typed-source-table-name",
646
+ "title": "`source:` is typechecked against the app’s tables, from the next `voltro dev`",
647
+ "kind": "manual"
648
+ },
643
649
  {
644
650
  "version": "0.5.0",
645
651
  "id": "0.5.0/01_one-terminal-exactly-one",
@@ -727,22 +733,22 @@
727
733
  "@effect/platform-node": "^0.108.0",
728
734
  "@effect/sql": "^0.52.0",
729
735
  "@effect/workflow": "^0.19.0",
730
- "@voltro/ai": "0.44.1",
731
- "@voltro/cache": "0.44.1",
732
- "@voltro/data-transfer": "0.44.1",
733
- "@voltro/database": "0.44.1",
734
- "@voltro/env": "0.44.1",
735
- "@voltro/kv": "0.44.1",
736
- "@voltro/logger": "0.44.1",
737
- "@voltro/plugin-auth": "0.44.1",
738
- "@voltro/plugin-broadcast": "0.44.1",
739
- "@voltro/plugin-mail": "0.44.1",
740
- "@voltro/plugin-storage": "0.44.1",
741
- "@voltro/plugin-webhooks": "0.44.1",
742
- "@voltro/protocol": "0.44.1",
743
- "@voltro/runtime": "0.44.1",
744
- "@voltro/serverless": "0.44.1",
745
- "@voltro/workflow": "0.44.1",
736
+ "@voltro/ai": "0.45.0",
737
+ "@voltro/cache": "0.45.0",
738
+ "@voltro/data-transfer": "0.45.0",
739
+ "@voltro/database": "0.45.0",
740
+ "@voltro/env": "0.45.0",
741
+ "@voltro/kv": "0.45.0",
742
+ "@voltro/logger": "0.45.0",
743
+ "@voltro/plugin-auth": "0.45.0",
744
+ "@voltro/plugin-broadcast": "0.45.0",
745
+ "@voltro/plugin-mail": "0.45.0",
746
+ "@voltro/plugin-storage": "0.45.0",
747
+ "@voltro/plugin-webhooks": "0.45.0",
748
+ "@voltro/protocol": "0.45.0",
749
+ "@voltro/runtime": "0.45.0",
750
+ "@voltro/serverless": "0.45.0",
751
+ "@voltro/workflow": "0.45.0",
746
752
  "chokidar": "^5.0.0",
747
753
  "ioredis": "^5.11.1",
748
754
  "tinyglobby": "^0.2.17",
@@ -707,7 +707,7 @@ each plugin's own README.
707
707
 
708
708
  | Topic | Open | Summary |
709
709
  |---|---|---|
710
- | **What's new in 0.44.1** | `node_modules/@voltro/cli/templates/agent-docs/whats-new.md` | Everything that changed in this version. Read it before hand-rolling something the framework may now ship. |
710
+ | **What's new in 0.45.0** | `node_modules/@voltro/cli/templates/agent-docs/whats-new.md` | Everything that changed in this version. Read it before hand-rolling something the framework may now ship. |
711
711
  | AI | `node_modules/@voltro/cli/templates/agent-docs/ai.md` | How Voltro treats AI — agents, tools, streaming, RAG — all primitives over the same WebSocket as the rest of the framework. |
712
712
  | Authentication | `node_modules/@voltro/cli/templates/agent-docs/authentication.md` | How @voltro/plugin-auth wires password + session-cookie auth across api + web, plus the pluggable identity-strategy protocol. |
713
713
  | Caching | `node_modules/@voltro/cli/templates/agent-docs/caching.md` | Voltro's caching layer (@voltro/cache) — an always-on memory default, swappable Redis-compatible backends, a low-level wrap primitive, and automatic query-result invalidation. |
@@ -9,7 +9,7 @@ each plugin's own README.
9
9
 
10
10
  | Topic | Open | Summary |
11
11
  |---|---|---|
12
- | **What's new in 0.44.1** | `node_modules/@voltro/cli/templates/agent-docs/whats-new.md` | Everything that changed in this version. Read it before hand-rolling something the framework may now ship. |
12
+ | **What's new in 0.45.0** | `node_modules/@voltro/cli/templates/agent-docs/whats-new.md` | Everything that changed in this version. Read it before hand-rolling something the framework may now ship. |
13
13
  | AI | `node_modules/@voltro/cli/templates/agent-docs/ai.md` | How Voltro treats AI — agents, tools, streaming, RAG — all primitives over the same WebSocket as the rest of the framework. |
14
14
  | Authentication | `node_modules/@voltro/cli/templates/agent-docs/authentication.md` | How @voltro/plugin-auth wires password + session-cookie auth across api + web, plus the pluggable identity-strategy protocol. |
15
15
  | Caching | `node_modules/@voltro/cli/templates/agent-docs/caching.md` | Voltro's caching layer (@voltro/cache) — an always-on memory default, swappable Redis-compatible backends, a low-level wrap primitive, and automatic query-result invalidation. |
@@ -1377,6 +1377,22 @@ Docker step) and prints the exact remedy: add a `voltro build .` step before
1377
1377
  `voltro serve .`. Drop it into your image build right after `voltro build` to
1378
1378
  guarantee the artefact is present before the image ships.
1379
1379
 
1380
+ ### Relations a query loads but does not declare
1381
+
1382
+ An eager-loaded relation is part of the RESULT, so its table has to be in `source:` or the view stops updating when it changes:
1383
+
1384
+ ```
1385
+ ✗ 1 query loads a relation it does not declare:
1386
+ tasks.getById: eager-loads `subTasks` from 'tasks' but does not declare
1387
+ 'task_sub_tasks' in `source:` — the view will not update when 'task_sub_tasks' changes.
1388
+ ```
1389
+
1390
+ This is the failure that looks like a broken feature and is not: the write lands, a reload shows it, and every test of the write path is green. The name in `source:` is spelled right and the table exists, so neither the typed `source:` nor the boot audit has anything to say.
1391
+
1392
+ The rule has no exception list, deliberately. It resolves `.with({ … })` keys through the relation registry, so the missing table is a fact rather than an inference — and a **many-to-many** is reported twice when needed, because the junction table is where a link add/remove actually writes.
1393
+
1394
+ It reads what it can read literally: a computed `.with()` key yields nothing rather than a guess.
1395
+
1380
1396
  ### The access-decision gate
1381
1397
 
1382
1398
  Before the authz scan below — which is a heuristic over executor SOURCE — doctor
@@ -2757,7 +2773,21 @@ The error carries three things, and they answer different questions:
2757
2773
  | `byTable` | **complete** per-table counts (`refused`, `primary`), worst first. |
2758
2774
  | `rows` | up to 20 refused rows — primary ones first — each with `table`, `id`, `reason`. |
2759
2775
 
2760
- `rows` is **capped and `byTable` is not**, and that distinction is worth one sentence: tallying the tables in the printed list answers "how big is the cap", not "which tables failed". The CLI prints the `byTable` line above the list and says so when the list is truncated.
2776
+ `rows` is **capped and `byTable` is not**, and that distinction is worth one sentence: tallying the tables in the printed list answers "how big is the cap", not "which tables failed". The CLI prints the `byTable` line above the list and says so when the list is truncated:
2777
+
2778
+ ```
2779
+ import refused 35 row(s), of which 35 are the actual failures — the rest could not land because a row they reference did not.
2780
+ by table: vacations 26, weeklyUpdates 9
2781
+ vacations v1 not-null constraint userId: the column requires a value [ER_BAD_NULL_ERROR/1048]
2782
+
2783
+ (the list above is capped at 20 of 35 — the counts by table are complete)
2784
+ ```
2785
+
2786
+ A refused import ends with a **non-zero exit status and that report** — not with a stack trace. A refusal is a condition with a named cause, not a framework defect, so it is not dressed as one. The report is the same on both transports: over `--target api` the refusal crosses as a 500 whose message embeds it, and the CLI renders that rather than printing the body raw.
2787
+
2788
+ **`RowsRefusedError` keeps its tag in every mode, `--atomic` included.** That is worth stating because it is the mode `replace` uses by default: the whole table phase runs in one transaction, and rolling that transaction back needs a rejection — but the rejection carries the typed error, not a rendering of it. So `Effect.catchTag('RowsRefusedError', …)` works on the default path, which is the one most likely to raise one.
2789
+
2790
+ One trap if you consume `runImport` yourself: what `Effect.runPromise` **rejects** with is a `FiberFailure`, and `err._tag` on one of those reads `undefined` however good the error inside is. Use `Effect.catchTag` on the effect, or `asImportError(err)` (exported from `@voltro/data-transfer`) on the rejection — reading the tag off the caught value takes the "not my error" branch every time.
2761
2791
 
2762
2792
  A row's `reason` is stated as precisely as the driver allows, in three tiers:
2763
2793
 
@@ -2898,6 +2928,16 @@ voltro data import ./out --target api --api-url https://api.example --token $SEC
2898
2928
 
2899
2929
  Note the asymmetry when a deadline *is* hit: re-running an `upsert` after the first run has finished is safe (it is idempotent); re-running a **`replace`** while the first is still mid-flight would empty the target under it. Check the instance's log before deciding — the message says so too.
2900
2930
 
2931
+ An instance that is **not answering at all** is a different message, and it names the instance rather than a database:
2932
+
2933
+ ```
2934
+ voltro: api import: the instance at api.example.com is not answering (ETIMEDOUT).
2935
+ That address came from --api-url (or VOLTRO_API_URL) — it is the running instance, not the database.
2936
+ Nothing was sent, so nothing was imported. …
2937
+ ```
2938
+
2939
+ Worth one line because the alternative was measured: a connect failure carries an address, a port and an errno, which is the same shape a database driver's carries — so without this the api host was reported as an unreachable *database*, attributed to `DB_URL`, a variable not in play on that run. Every word after the address was wrong, and the address being right is what made it convincing.
2940
+
2901
2941
  **Import** (`POST /_voltro/admin/import`) applies a bundle **in the instance's own process** through its store — full app pipeline (validation, mixins, **field encryption**, hooks) AND automatic reactivity. Two transports, neither needs a server-readable path:
2902
2942
  - **upload** (default) — the CLI **packs the bundle and uploads the bytes**; the server unpacks to its own temp dir, imports in-process, cleans up. Buffered → small/moderate bundles.
2903
2943
  - **storage-pull** (`--bundle-key <key>`) — the CLI sends only `{ "bundleKey": "<key>" }`; the instance **streams that archive from its configured object storage** (S3 / Azure / MinIO / filesystem, resolved from the storage env — `STORAGE_PROVIDER`, `S3_*`, `AZURE_*`, …), no body buffering → arbitrarily large bundles.
@@ -1638,14 +1638,44 @@ warning](#fan-out--how-many-subscribers-may-one-change-wake) is the only signal
1638
1638
  for a subscription that has gone permanently quiet, and an exemption for a name
1639
1639
  you invented disables it for the one case it was built for.
1640
1640
 
1641
+ ### A table `source:` is typechecked
1642
+
1643
+ `voltro dev` writes `voltro-tables.generated.d.ts` beside your generated rpc group, listing every table the app has — your entities, your plugins' `extendSchema.tables`, and the framework's own. `source:` is narrowed to those names, so a typo or a table you renamed away is a **compile error**:
1644
+
1645
+ ```ts
1646
+ source: 'task_subtasks', // ✗ Type '"task_subtasks"' is not assignable to type 'TableName'
1647
+ source: 'task_sub_tasks', // ✓
1648
+ ```
1649
+
1650
+ That matters because the failure it replaces is silent. A `source:` is matched by NAME against change events, so one that matches nothing does not break the query — it makes it never update. The write still lands, a reload still shows it, and the panel keeps showing the old value.
1651
+
1652
+ The file is generated, so **commit it** like the rpc group and let `voltro dev` rewrite it. Before the first run — and in a project that never generates it — `source:` is plain `string` again, which is exactly the previous behaviour; there is no configuration and nothing to opt into.
1653
+
1654
+ Two things it deliberately does not narrow. A **plugin's** route `source:` stays `string`: a plugin ships against many apps and cannot know any of their tables. And nothing that READS a descriptor's source at runtime narrows either — a reader that refused an unknown name would reject the stale name it exists to report.
1655
+
1656
+ **It does not check the other direction — except for relations.** A `source:` that omits a table the query genuinely reads is silent: the name is right, the table exists, and nothing has an opinion. That is the failure that costs a user report — they type, the row lands, and the panel does not move.
1657
+
1658
+ `voltro doctor` closes the part of it that can be closed without guessing:
1659
+
1660
+ ```
1661
+ ✗ 1 query loads a relation it does not declare:
1662
+ tasks.getById: eager-loads `subTasks` from 'tasks' but does not declare
1663
+ 'task_sub_tasks' in `source:` — the view will not update when 'task_sub_tasks' changes.
1664
+ ```
1665
+
1666
+ An eager-loaded relation is composition by definition — its rows are IN the result — and its table comes from the relation registry, so the rule has no heuristic and no exception list. A **many-to-many** wants the junction table too, and says so separately: adding or removing a link writes only the junction row, so declaring the target alone leaves the list stale on exactly the operation a user performs to change it.
1667
+
1668
+ What doctor deliberately does NOT do is derive every table an executor reads. That needs a judgement — does this read **compose** the result or merely **restrict** it? — and only composing reads belong in `source:`; a restricting one re-running on every unrelated write puts a hundred lists back on the wire. A scan has to infer that from syntax, and a rule that guesses on a correct codebase teaches you to ignore it.
1669
+
1670
+ So: when a live view does not update, check the writing table is in the reading query's `source:` before anything else.
1671
+
1641
1672
  ### Pass the channel, not its key
1642
1673
 
1643
1674
  A channel's routing key is `channel:<name>`, and you can read it off
1644
1675
  `jobQueue.key`. **Do not write that string into `source:`.** Passing the object
1645
1676
  creates an import edge from the query to the declaration, which removes the
1646
- entire class of stale-source bugs for channels: a table `source:` is a string,
1647
- so a rename leaves the old one behind and `tsc` cannot see it, while a channel
1648
- that is not imported does not exist to be named. The boot audit reports a
1677
+ entire class of stale-source bugs for channels: a channel that is not imported
1678
+ does not exist to be named. The boot audit reports a
1649
1679
  `channel:` key nothing declared, for the two ways round it.
1650
1680
 
1651
1681
  Names are lowercase kebab segments separated by dots — `presence`, `job-queue`,
@@ -27,6 +27,8 @@ export default {
27
27
 
28
28
  Every committed change to a listed table appends a row to `_voltro_row_history` (`tableName`, `rowId`, monotonic `version`, `op`, the full `data` snapshot, `changedBy`, `changedAt`, `traceId`, `subjectId`, `procedure`).
29
29
 
30
+ The history row's own `id` is **derived** from `(tableName, rowId, version)` and has a fixed width — it is a surrogate, and every part of it is already a column beside it, so do not parse or construct it. That width is the point: an `id()` column is `VARCHAR(64)` on mysql/mariadb and `NVARCHAR(64)` on mssql, so a key built by concatenating those parts grew with your **table name** and stopped fitting past 22 characters — which failed every write to that table, not merely an import.
31
+
30
32
 
31
33
  ### What gets versioned — opt OUT, not in
32
34
 
@@ -1,4 +1,4 @@
1
- # What's new in 0.44.1
1
+ # What's new in 0.45.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,14 +7,101 @@ 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/protocol, @voltro/cli, @voltro/voltro** — `source:` on a query is now typed against the app's own tables, so a typo or a missed rename is a compile error instead of a subscription that goes quiet.
13
+
14
+ A `source:` is matched by NAME against change events, so a name matching nothing does not break the query — it makes it permanently silent: it compiles, boots, serves its first snapshot and never updates. From the outside that reads as a feature that does nothing, with a correct write path and green tests behind it. The boot has warned about this since 0.26.0, on both paths; a warning is read once, and a rename lands in a diff where nobody is checking strings.
15
+
16
+ `voltro dev` writes `voltro-tables.generated.d.ts` beside the generated rpc group, augmenting `VoltroTableNames` with the FULL live set — app entities, plugin `extendSchema.tables` and the framework's own — from the same binding the boot audit resolves against, so the type and the warning cannot disagree about which tables exist. `source:` narrows to those names.
17
+
18
+ Nothing changes at runtime: these are still string literals, so a descriptor carrying them is as browser-loadable as before. That is what ruled out accepting the table VALUE — a descriptor is loaded value-level by the web client, and a table value drags `@voltro/database` across that boundary.
19
+
20
+ **Breaking, and filed that way after being written up as additive.** The test is not whether a symbol disappeared, it is whether code that compiled can stop: `['tasks', 'agent_messages']` was assignable and is not, which is the whole point where the name is stale and an obstacle where the source is genuinely computed. `normalizeSource`'s parameter narrowed with it. The wide shape stays public as `ReactivitySourceValue` for the computed case.
21
+
22
+ The break does NOT land at upgrade time, which is why the codemod is a written note rather than a transform: right after `voltro update` the generated file does not exist, `keyof VoltroTableNames` is `never`, `TableName` falls back to `string`, and everything compiles as before. The narrowing switches on at the next `voltro dev` — a different command, by which point the change that caused it is no longer what the reader is looking at. A transform could not have found the sites either, since the type that rejects them has not been generated yet. And the two things `tsc` flags — a stale name versus a runtime-computed one — want opposite fixes, so the mechanical one (widen the annotation) would convert every defect this surfaces back into the quiet subscription it exists to expose.
23
+
24
+ Delete the generated file and `source:` widens back to `string`.
25
+
26
+ One deliberate asymmetry, stated because it is one: runtime READERS of a descriptor's source stay wide (`ReactivitySourceValue`). Narrow where an author writes, stay wide where the framework reads — a reader that refused an unknown name would be asserting a fact it cannot check, and the first thing it would reject is the stale name it exists to report.
27
+
28
+ ### Added
29
+
30
+ - **@voltro/cli** — `voltro doctor` reports a query that eager-loads a relation and does not declare its table in `source:` — the failure that looks like a broken feature and is not.
31
+
32
+ The write lands, a reload shows it, every test of the write path is green, the name in `source:` is spelled right and the table exists. So neither the typed `source:` nor the boot audit has anything to say, and the only observer is a user watching a panel that does not move.
33
+
34
+ ```
35
+ ✗ 1 query loads a relation it does not declare:
36
+ tasks.getById: eager-loads `subTasks` from 'tasks' but does not declare
37
+ 'task_sub_tasks' in `source:` — the view will not update when 'task_sub_tasks' changes.
38
+ ```
39
+
40
+ No exception list, deliberately. An eager-loaded relation is composition by definition — its rows are IN the result — and its table comes from the relation registry, so the missing name is a fact rather than an inference. A many-to-many is reported twice when needed: adding or removing a link writes only the JUNCTION row, so declaring the target alone leaves the list stale on exactly the operation a user performs to change it. A computed `.with()` key yields nothing rather than a guess.
41
+
42
+ The general question — every table an executor reads — is NOT answered, on purpose: it needs a compose-versus-restrict judgement a scan can only infer from syntax, and a rule that guesses on a correct codebase teaches its reader to ignore it. Design in `plans/open/framework/source-completeness.md`.
43
+
44
+ Two things around it:
45
+
46
+ - **The stale-`source:` audit covered queries only, on both boot paths.** A stream carries a `source:` too, and a stale one there is the same permanently quiet subscription with a longer-lived connection behind it. Both paths now take the set from one `auditableSources`. - **`voltro codegen` writes the typed-`source:` declaration too**, from the same shared `declaredTableNames` merge the boots use. Letting it lag was the bad direction: a table added since the last `voltro dev` would make a CORRECT `source:` a type error. Both commands now say what they wrote — the narrowing has a silent no-op if the app's tsconfig does not pick the file up, so the write has to be loud enough that a reader can check.
47
+
10
48
  ### Fixed
11
49
 
12
- - **@voltro/database, @voltro/data-transfer, @voltro/cli** — A refused import row reported `(FiberFailure) SqlError: Failed to execute statement` — our runtime's rendering of a rejection, marker and stack frames and all — instead of the constraint that fired. It names neither a rule, nor a code, nor even which layer refused, and every row refused for the same cause carries it identically.
50
+ - **@voltro/database, @voltro/sql-mysql** — A binding failure names the TYPE of every value, so the culprit is read rather than guessed.
51
+
52
+ `ER_WRONG_ARGUMENTS` / 1210 reads like a count problem and often is not: a statement with twelve columns and twelve placeholders is internally consistent, and the driver is refusing one VALUE it cannot bind. Measured against a live mariadb 11.8 and mysql 8.4 (mysql2 3.22), binding to a PREPARED statement:
53
+
54
+ | value | mariadb | mysql | |---|---|---| | plain object | **1210** | accepted | | array | **1210** | accepted | | bigint | accepted | accepted | | Invalid Date | accepted | 1292 |
55
+
56
+ So the same row binds on one engine of the family and not the other — which is how a suite comes to fail on mariadb and pass on mysql in the SAME run, and why the type of each binding is the diagnosis rather than a detail.
57
+
58
+ `describeDriverError` now reports `bindings: id:string data:Object changedAt:Date …` beside the placeholder count and the statement. Types only; a value there would be row data in a log line, the same reason the statement is carried only in its placeholder form.
59
+
60
+ The types travel as a FIELD, not in a message. That is load-bearing: a failing write recorder rethrows with its own sentence, so anything said only in text is dropped exactly where it is needed. `extractDbCause` collects it like any driver field, so it survives every wrapper between the failing statement and the log.
61
+ - **@voltro/database, @voltro/testing** — A driver error now carries the two numbers a binding failure is made of.
62
+
63
+ `ER_WRONG_ARGUMENTS` / errno 1210 means the parameter count did not match the placeholder count — reproduced against mariadb 11.8 by sending one parameter for two `?` — and the message says only `Incorrect arguments to mysqld_stmt_execute`. Neither number was reachable from the error, so an investigation into one of these starts by eliminating hypotheses instead of subtracting.
64
+
65
+ `describeDriverError` reports `placeholders=N` and the statement, and the statement is carried ONLY in its placeholder form. That restriction is measured, not cautious: against mysql2 3.22 the prepared path (`execute`) leaves `?` in `err.sql` because the server did the binding, while the text path (`query`) interpolates and the same field then holds row DATA. The placeholder is the discriminator, and the form that keeps it is exactly the form 1210 arises in.
66
+
67
+ Alongside it, `reportEngineVersion` (`@voltro/testing`): a dialect suite prints the engine BUILD it ran against. A suite that is green on a developer machine and red in CI is only comparable if both name their software, and the test compose file uses moving tags — so "the same tag" is not the same build, and checking the tag locally observes what it points at today rather than what the runner resolved.
68
+ - **@voltro/database, @voltro/plugin-versioning, @voltro/plugin-flags** — A versioned table whose NAME was long enough could not be written to at all.
69
+
70
+ `id()` is `VARCHAR(64)` on mysql / mariadb and `NVARCHAR(64)` on mssql, and unbounded `TEXT` on postgres and sqlite. The versioning recorder built its history key by concatenation — `rowver_<tableName>_<rowId>_<version>`, which is `42 + len(tableName)` characters for a 32-character row id — so a 22-character table name fit and a 23-character one produced `ERROR 1406 (22001): Data too long for column 'id' at row 1`. A recorder runs on EVERY write, so this was not a refused import: it was a table nobody could write to, on three of five dialects, at a boundary no one can see when naming a table.
71
+
72
+ `derivedRowId(prefix, …parts)` (`@voltro/database`) derives a deterministic key of CONSTANT width — `rowver_<32 hex>`, 39 characters whatever goes in — joined over a `\u0000` separator so the parts stay injective (a `_`-joined key cannot tell `('a_b','c')` from `('a','b_c')`). Widening the column was the alternative and moves the wall rather than removing it; `id()` is also every user table's PK type. Nothing legible is lost: every table deriving a key this way already stores the parts in their own columns.
73
+
74
+ The same construction was in `plugin-flags` (`flag_<key>`, over an unbounded user-chosen flag key) and is fixed with it. A guard scans framework sources for an `id:` composed by interpolation and requires the helper, with an allowlist whose entries each name why their parts cannot grow — and which fails if an entry stops matching.
75
+
76
+ Also fixed: the versioning suite's live coverage was postgres-only, and postgres is one of the two dialects where that column is unbounded, so it was structurally incapable of seeing this. `@voltro/sql-mysql` is a test devDep of `@voltro/plugin-versioning` now, with a mysql+mariadb case driving an ordinary insert and update against a 33-character table name.
77
+ - **@voltro/database, @voltro/cli** — Two gaps on the `--target api` path, both about a failure that is present and unreadable.
78
+
79
+ **The driver was unreachable behind a WRAPPED rejection.** `extractDbCause` unwrapped a `FiberFailure` at the root only, so one reached through a `.cause` link stopped the walk — it carries `stack`, `message` and `name` and nothing else, which is indistinguishable from "no driver under this". That is exactly the shape a failing write recorder produces: it rethrows `new Error(<what it was doing>, { cause: err })` where `err` is the rejection its own insert made. So the same database refusal classified where no recorder runs and degraded to the bare runtime rendering where one does — which is the difference between the direct importer and an import through a running app with versioning or audit on. The walk now unwraps at every link.
80
+
81
+ **And the refusal report was never printed on that transport.** A refusal that crossed HTTP arrives as a 500 whose message embeds the `RowsRefusedError` as JSON; the CLI printed that body raw. So the operator on the transport that exists for "the database is somewhere you cannot open a shell" got the one output that has to be triaged by hand — and tallying the capped row list is how a per-table distribution gets reported that is not the real one. `--target api` now prints the same report as the direct path, `byTable` line and cap notice included.
82
+ - **@voltro/data-transfer, @voltro/cli** — Two reporting defects that made a refused import unreadable, both of the shape "the payload is present and property access is not the way to it".
83
+
84
+ **A refusal lost its tag on the mode that raises it most.** `--mode replace` runs in one transaction by default, and rolling that back needs a rejection — which the atomic wrapper obtained by throwing `new Error(Cause.pretty(cause))`, a rendering rather than the failure. From there the typed error could not come back: it was re-wrapped as a `BundleError` carrying itself as text. So `Effect.catchTag('RowsRefusedError', …)` matched nothing on the default path, `ImportError`'s union was a claim that path could not honour, and the CLI's refusal report — which branches on the tag — printed nothing at all. The typed error is thrown and passed through now; `asImportError` is exported for callers who catch the rejection rather than the effect.
85
+
86
+ **And the report read the tag off a `FiberFailure`.** What `Effect.runPromise` rejects with does not expose `_tag` by property access, so the renderer took its "not my error" branch on every direct-path run while being wired, tested and correct — the test drove the renderer with the error object, which is not the shape the call site produces. A reported refusal now also ENDS the command instead of being rethrown into `fatal unhandled cli error`: a refusal is a condition with a named cause, not a framework defect.
87
+
88
+ **An api host is no longer reported as an unreachable database.** A connect failure carries an address, a port and an errno — the same shape a database driver's carries — and one global handler renders that shape, so `--target api --api-url https://…` against a stopped instance printed `the database is not reachable at <api-host>:443 … Configured by: DB_URL` with `DB_URL` not in play. The transport names its own failure now (`InstanceUnreachable`), and the database explainer declines an endpoint whose PORT cannot be a database — judged by port because a driver reports the resolved address, so a host comparison would silence the real message for anyone naming their database by hostname.
89
+
90
+ ### Internal (no consumer-facing effect)
91
+
92
+ - **@voltro/sql-postgres** — A test teardown terminated connections its own pool was still closing, and the resulting error failed the RUN rather than any test.
93
+
94
+ `clusterColdStart` drops a per-run database, and the runners it spawned are killed with SIGKILL, so their backends never close — hence the deliberate `pg_terminate_backend` before the `DROP`. But `pool.end()` resolves once it has ASKED the pool to close, not once every socket is down, so the terminate could also land on a connection belonging to the test itself. `pg` reports that as an `error` event on the idle client, and an unhandled one takes down the process.
95
+
96
+ The shape it took on a release gate is the reason this is written down: **36 of 36 test files green, and the suite exiting 1.** Nothing points at the teardown — the failure is attributed to whichever suite happened to run last, which is a different one each time. A connection error while we are tearing the database down carries no signal, so it is handled where it arises.
97
+
98
+ Test-only; no product code changed.
99
+ - **@voltro/plugin-auth** — The TOTP skew-window test uses a fixed secret. Test-only; no product code changed, and the assertion is unchanged.
13
100
 
14
- Two defects, and fixing either alone still leaves a reader stuck.
101
+ It failed once on a release gate `expected true to be false`, meaning a code two steps outside the ±1 window verified. That is the shape of a security defect, so it was treated as one until measured:
15
102
 
16
- **`Cause.squash` elects a branch, and first is a position, not a ranking.** A `Cause` is a tree, and a transaction routinely produces one with more than one leaf: the statement that failed, and whatever the rollback or a finalizer did on the way out. When the first leaf was the bare wrapper, the driver error sitting in the sibling branch was never looked at. Measured: `sequential(bareSqlError, sqlErrorWithDriver)` classified as nothing while the same two branches in the opposite order classified as `unique constraint PRIMARY [ER_DUP_ENTRY/1062]`. `extractDbCause` flattens failures AND defects now, in Cause order, expanding a nested `FiberFailure` leaf, and elects the branch that names a driver — the others' chains are appended rather than dropped.
103
+ - `TOTP_SKEW` is 1 and the verify loop checks exactly three counters, compared with `timingSafeEqual`; - `T0` is a constant and the clock is injected, so the only varying input was `generateTotpSecret()`; - over **50 000 fresh secrets**: zero collisions between the ±2 codes and the ±1 window (pure chance predicts ~0.3), zero degenerate secrets, uniform length; - **60 consecutive runs** of the file: green.
17
104
 
18
- **A row's reason may never read like a stack trace.** The runtime rendering is stripped before any tier looks at the text, so the failure mode cannot return invisibly. And when no driver detail is reachable at all, the reason now names the CHAIN of wrappers the failure passed through the difference between "the database refused this row" and "the connection died mid-import" — while the run logs the full rendering of the first few such failures. Never returned over the wire: it carries frames, and on some engines a driver's sentence carries row data.
105
+ So the implementation is sound and that red was two 6-digit codes coincidingabout six in a million per run. Worth stating plainly: that makes the observed failure a one-in-167 000 event, which fits every measurement and is still remarkable. It was not reproduced.
19
106
 
20
- `RowsRefusedError` also gained `byTable`: **complete** per-table counts. `rows` is capped at 20, so per-table counts tallied off the printed list sum to the cap rather than to the failure and nothing else in the payload offered any.
107
+ The fix is to remove the coin flip rather than to re-run until green. A random secret buys this test nothing — the property under test is the WIDTH of the window, which does not depend on which secret is used. It only buys a rare red that costs a diagnosis cycle and teaches the reader to re-run. Pinned, so the next failure there means the window moved.
@@ -12,16 +12,16 @@
12
12
  "dependencies": {
13
13
  "@effect/platform": "^0.97.0",
14
14
  "@effect/rpc": "^0.76.0",
15
- "@voltro/ai": "0.44.1",
16
- "@voltro/cli": "0.44.1",
17
- "@voltro/database": "0.44.1",
18
- "@voltro/env": "0.44.1",
19
- "@voltro/protocol": "0.44.1",
20
- "@voltro/runtime": "0.44.1",
15
+ "@voltro/ai": "0.45.0",
16
+ "@voltro/cli": "0.45.0",
17
+ "@voltro/database": "0.45.0",
18
+ "@voltro/env": "0.45.0",
19
+ "@voltro/protocol": "0.45.0",
20
+ "@voltro/runtime": "0.45.0",
21
21
  "effect": "^3.22.0"
22
22
  },
23
23
  "devDependencies": {
24
- "@voltro/testing": "0.44.1",
24
+ "@voltro/testing": "0.45.0",
25
25
  "typescript": "^6.0.3",
26
26
  "@vitest/coverage-v8": "^4.1.10",
27
27
  "vitest": "^4.1.10"
@@ -13,17 +13,17 @@
13
13
  "dependencies": {
14
14
  "@effect/platform": "^0.97.0",
15
15
  "@effect/rpc": "^0.76.0",
16
- "@voltro/cli": "0.44.1",
17
- "@voltro/database": "0.44.1",
18
- "@voltro/env": "0.44.1",
19
- "@voltro/plugin-auth": "0.44.1",
20
- "@voltro/protocol": "0.44.1",
21
- "@voltro/runtime": "0.44.1",
22
- "@voltro/sql-postgres": "0.44.1",
16
+ "@voltro/cli": "0.45.0",
17
+ "@voltro/database": "0.45.0",
18
+ "@voltro/env": "0.45.0",
19
+ "@voltro/plugin-auth": "0.45.0",
20
+ "@voltro/protocol": "0.45.0",
21
+ "@voltro/runtime": "0.45.0",
22
+ "@voltro/sql-postgres": "0.45.0",
23
23
  "effect": "^3.22.0"
24
24
  },
25
25
  "devDependencies": {
26
- "@voltro/testing": "0.44.1",
26
+ "@voltro/testing": "0.45.0",
27
27
  "typescript": "^6.0.3",
28
28
  "@vitest/coverage-v8": "^4.1.10",
29
29
  "vitest": "^4.1.10"
@@ -16,16 +16,16 @@
16
16
  "dependencies": {
17
17
  "@effect/platform": "^0.97.0",
18
18
  "@effect/rpc": "^0.76.0",
19
- "@voltro/cli": "0.44.1",
20
- "@voltro/database": "0.44.1",
21
- "@voltro/env": "0.44.1",
22
- "@voltro/plugin-multitenancy": "0.44.1",
23
- "@voltro/protocol": "0.44.1",
24
- "@voltro/runtime": "0.44.1",
19
+ "@voltro/cli": "0.45.0",
20
+ "@voltro/database": "0.45.0",
21
+ "@voltro/env": "0.45.0",
22
+ "@voltro/plugin-multitenancy": "0.45.0",
23
+ "@voltro/protocol": "0.45.0",
24
+ "@voltro/runtime": "0.45.0",
25
25
  "effect": "^3.22.0"
26
26
  },
27
27
  "devDependencies": {
28
- "@voltro/testing": "0.44.1",
28
+ "@voltro/testing": "0.45.0",
29
29
  "typescript": "^6.0.3",
30
30
  "@vitest/coverage-v8": "^4.1.10",
31
31
  "vitest": "^4.1.10"
@@ -13,16 +13,16 @@
13
13
  "dependencies": {
14
14
  "@effect/platform": "^0.97.0",
15
15
  "@effect/rpc": "^0.76.0",
16
- "@voltro/cli": "0.44.1",
17
- "@voltro/database": "0.44.1",
18
- "@voltro/env": "0.44.1",
19
- "@voltro/plugin-deactivation": "0.44.1",
20
- "@voltro/protocol": "0.44.1",
21
- "@voltro/runtime": "0.44.1",
16
+ "@voltro/cli": "0.45.0",
17
+ "@voltro/database": "0.45.0",
18
+ "@voltro/env": "0.45.0",
19
+ "@voltro/plugin-deactivation": "0.45.0",
20
+ "@voltro/protocol": "0.45.0",
21
+ "@voltro/runtime": "0.45.0",
22
22
  "effect": "^3.22.0"
23
23
  },
24
24
  "devDependencies": {
25
- "@voltro/testing": "0.44.1",
25
+ "@voltro/testing": "0.45.0",
26
26
  "typescript": "^6.0.3",
27
27
  "@vitest/coverage-v8": "^4.1.10",
28
28
  "vitest": "^4.1.10"
@@ -13,18 +13,18 @@
13
13
  "dependencies": {
14
14
  "@react-email/components": "^1.0.12",
15
15
  "@react-email/render": "^1.4.0",
16
- "@voltro/cli": "0.44.1",
17
- "@voltro/database": "0.44.1",
18
- "@voltro/env": "0.44.1",
19
- "@voltro/plugin-mail": "0.44.1",
20
- "@voltro/plugin-multitenancy": "0.44.1",
21
- "@voltro/protocol": "0.44.1",
22
- "@voltro/runtime": "0.44.1",
16
+ "@voltro/cli": "0.45.0",
17
+ "@voltro/database": "0.45.0",
18
+ "@voltro/env": "0.45.0",
19
+ "@voltro/plugin-mail": "0.45.0",
20
+ "@voltro/plugin-multitenancy": "0.45.0",
21
+ "@voltro/protocol": "0.45.0",
22
+ "@voltro/runtime": "0.45.0",
23
23
  "effect": "^3.22.0",
24
24
  "react": "^19.0.0"
25
25
  },
26
26
  "devDependencies": {
27
- "@voltro/testing": "0.44.1",
27
+ "@voltro/testing": "0.45.0",
28
28
  "typescript": "^6.0.3",
29
29
  "@vitest/coverage-v8": "^4.1.10",
30
30
  "vitest": "^4.1.10"
@@ -13,18 +13,18 @@
13
13
  "dependencies": {
14
14
  "@effect/platform": "^0.97.0",
15
15
  "@effect/rpc": "^0.76.0",
16
- "@voltro/cli": "0.44.1",
17
- "@voltro/database": "0.44.1",
18
- "@voltro/env": "0.44.1",
19
- "@voltro/plugin-multitenancy": "0.44.1",
20
- "@voltro/plugin-storage": "0.44.1",
21
- "@voltro/protocol": "0.44.1",
22
- "@voltro/runtime": "0.44.1",
23
- "@voltro/sql-mysql": "0.44.1",
16
+ "@voltro/cli": "0.45.0",
17
+ "@voltro/database": "0.45.0",
18
+ "@voltro/env": "0.45.0",
19
+ "@voltro/plugin-multitenancy": "0.45.0",
20
+ "@voltro/plugin-storage": "0.45.0",
21
+ "@voltro/protocol": "0.45.0",
22
+ "@voltro/runtime": "0.45.0",
23
+ "@voltro/sql-mysql": "0.45.0",
24
24
  "effect": "^3.22.0"
25
25
  },
26
26
  "devDependencies": {
27
- "@voltro/testing": "0.44.1",
27
+ "@voltro/testing": "0.45.0",
28
28
  "typescript": "^6.0.3",
29
29
  "@vitest/coverage-v8": "^4.1.10",
30
30
  "vitest": "^4.1.10"