@voltro/cli 0.48.0 → 0.49.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 (98) hide show
  1. package/CHANGELOG.md +67 -0
  2. package/dist/{apiBuild-Dz8TJSxp.js → apiBuild-QmYHaCF4.js} +1 -1
  3. package/dist/apiBuild-dZbHdqMg.js +2 -0
  4. package/dist/bin.js +1 -1
  5. package/dist/{build-DLme2ppa.js → build-Bi4O_g7T.js} +6 -6
  6. package/dist/{checkCommand-Dals7g-I.js → checkCommand-D8vjQXQt.js} +3 -3
  7. package/dist/{checkCommand-BUBqPBlI.js → checkCommand-udbQpphy.js} +1 -1
  8. package/dist/{codegenCommand-L6vacVRW.js → codegenCommand-DApSH7hK.js} +39 -47
  9. package/dist/{codemodRunner-DSZjgyCL.js → codemodRunner-De1_316I.js} +562 -492
  10. package/dist/{commands-CAxFLqG4.js → commands-a5PxCaIg.js} +68 -60
  11. package/dist/{dashboardCommand-CJC8Kg-s.js → dashboardCommand-DaoUUqJS.js} +1 -1
  12. package/dist/dataCommand-CJv_OMoS.js +1156 -0
  13. package/dist/dataProfile-Du3ztYxv.js +51 -0
  14. package/dist/{dbCommand-ZGNItjoJ.js → dbCommand-Bf7Cy0mn.js} +2 -2
  15. package/dist/dbCommand-Bnl7MSGC.js +2 -0
  16. package/dist/{dev-lvnVq5RD.js → dev-C31Nvlbv.js} +1 -1
  17. package/dist/{dev-cGGsLNn6.js → dev-DFVuzOj7.js} +1 -1
  18. package/dist/{doctorCommand-BK0qu7eD.js → doctorCommand-B3uraeAe.js} +12 -12
  19. package/dist/doctorCommand-CPL6EhrW.js +2 -0
  20. package/dist/{dormancyCommand-CyIk-zq4.js → dormancyCommand-QUXQ9yoz.js} +1 -1
  21. package/dist/{embeddingsCommand-D7TLklps.js → embeddingsCommand-Zw1-rYSF.js} +1 -1
  22. package/dist/{envCommand-BHxKkGXq.js → envCommand-DtaJ4xEf.js} +1 -1
  23. package/dist/{evolveCommand-CQh6dw21.js → evolveCommand-JDD3hE1i.js} +2 -2
  24. package/dist/{frameworkTableAssembly-BNod_DKN.js → frameworkTableAssembly-BwJVEKLr.js} +3 -3
  25. package/dist/frameworkTableAssembly-CVDB2hCq.js +2 -0
  26. package/dist/index.js +1 -1
  27. package/dist/{infoCommand-CuNl9cbh.js → infoCommand-COsUC1DB.js} +1 -1
  28. package/dist/{migrate-CL5Ed01M.js → migrate-D3MK9BpK.js} +2 -2
  29. package/dist/{runtimeTrace-C0SlIoQY.js → runtimeTrace-Di6fcndp.js} +1 -1
  30. package/dist/{sdkgen-BXm7zx7F.js → sdkgen-CIi5gM17.js} +1 -1
  31. package/dist/serveCommand-CzSDngKm.js +2 -0
  32. package/dist/serveCommand-N3udT49p.js +2362 -0
  33. package/dist/serveEntry.js +5 -5
  34. package/dist/{subcommandNames-CKG5Dz3a.js → subcommandNames-DpYs3DXr.js} +12 -2
  35. package/dist/{updateCommand-BlyXavoG.js → updateCommand-2BfbeMvp.js} +1 -1
  36. package/dist/updateCommand-CtYm7aKH.js +2 -0
  37. package/dist/{webhooksCommand-QlAk_tED.js → webhooksCommand-CwX9IzvJ.js} +1 -1
  38. package/package.json +29 -17
  39. package/templates/AGENTS.md +1 -1
  40. package/templates/agent-docs/_index.md +1 -1
  41. package/templates/agent-docs/cli.md +71 -13
  42. package/templates/agent-docs/whats-new.md +34 -47
  43. package/templates/apps/api-ai/package.json +7 -7
  44. package/templates/apps/api-auth/package.json +8 -8
  45. package/templates/apps/api-backend/package.json +7 -7
  46. package/templates/apps/api-backend-deactivation/package.json +7 -7
  47. package/templates/apps/api-backend-mail/package.json +8 -8
  48. package/templates/apps/api-backend-mariadb/package.json +9 -9
  49. package/templates/apps/api-backend-sqlite/package.json +8 -8
  50. package/templates/apps/api-backend-storage/package.json +8 -8
  51. package/templates/apps/api-cms/package.json +10 -10
  52. package/templates/apps/api-collab/package.json +8 -8
  53. package/templates/apps/api-data-advanced/package.json +8 -8
  54. package/templates/apps/api-durable/package.json +8 -8
  55. package/templates/apps/api-feature-flags/package.json +9 -9
  56. package/templates/apps/api-governance/package.json +8 -8
  57. package/templates/apps/api-kv/package.json +8 -8
  58. package/templates/apps/api-moderation/package.json +8 -8
  59. package/templates/apps/api-observability/package.json +8 -8
  60. package/templates/apps/api-ratelimit/package.json +8 -8
  61. package/templates/apps/api-rbac/package.json +8 -8
  62. package/templates/apps/api-rest/package.json +7 -7
  63. package/templates/apps/api-saas/package.json +11 -11
  64. package/templates/apps/api-saas-starter/package.json +10 -10
  65. package/templates/apps/api-search/package.json +8 -8
  66. package/templates/apps/api-status/package.json +8 -8
  67. package/templates/apps/api-versioning/package.json +8 -8
  68. package/templates/apps/api-webhooks/package.json +9 -9
  69. package/templates/apps/changelog/package.json +6 -6
  70. package/templates/apps/edge-functions/package.json +2 -2
  71. package/templates/apps/frontend-admin/package.json +8 -8
  72. package/templates/apps/frontend-app/package.json +9 -9
  73. package/templates/apps/frontend-auth/package.json +8 -8
  74. package/templates/apps/frontend-blank/package.json +7 -7
  75. package/templates/apps/frontend-cms/package.json +9 -9
  76. package/templates/apps/frontend-collab/package.json +10 -10
  77. package/templates/apps/frontend-contact/package.json +7 -7
  78. package/templates/apps/frontend-dashboard/package.json +7 -7
  79. package/templates/apps/frontend-docs/package.json +7 -7
  80. package/templates/apps/frontend-i18n/package.json +6 -6
  81. package/templates/apps/frontend-landing/package.json +7 -7
  82. package/templates/apps/frontend-portal/package.json +8 -8
  83. package/templates/apps/frontend-saas/package.json +8 -8
  84. package/templates/apps/frontend-spa/package.json +7 -7
  85. package/templates/apps/frontend-ssr/package.json +7 -7
  86. package/templates/apps/frontend-ssr-api/package.json +8 -8
  87. package/templates/apps/frontend-static-blog/package.json +6 -6
  88. package/templates/apps/frontend-status/package.json +8 -8
  89. package/templates/apps/mobile-app/package.json +4 -4
  90. package/dist/apiBuild-DOnvi2zm.js +0 -2
  91. package/dist/dataCommand-hIaq2iKY.js +0 -1052
  92. package/dist/dataProfile-Cm0YVKSy.js +0 -18
  93. package/dist/dbCommand-D1Q33ktl.js +0 -2
  94. package/dist/doctorCommand-CWgUPye-.js +0 -2
  95. package/dist/frameworkTableAssembly-Rft-DPUg.js +0 -2
  96. package/dist/serveCommand-CV3YO9L3.js +0 -2035
  97. package/dist/serveCommand-ZP_Cb-yz.js +0 -2
  98. package/dist/updateCommand-CT5AvVg7.js +0 -2
@@ -1,5 +1,5 @@
1
- import { t as e } from "./loadEnv-D9nEOClM.js";
2
- import { r as t } from "./appModuleLoader-C9r9mxZt.js";
3
- import { i as n } from "./dialectDriver-czCHYpeH.js";
4
- import { t as r } from "./serveCommand-CV3YO9L3.js";
5
- export { e as loadDotEnv, t as registerAppModules, n as registerDriver, r as runServe };
1
+ import { t as e } from "./serveCommand-N3udT49p.js";
2
+ import { t } from "./loadEnv-D9nEOClM.js";
3
+ import { r as n } from "./appModuleLoader-C9r9mxZt.js";
4
+ import { i as r } from "./dialectDriver-czCHYpeH.js";
5
+ export { t as loadDotEnv, n as registerAppModules, r as registerDriver, e as runServe };
@@ -19,9 +19,19 @@ var e = [
19
19
  "scan-credentials",
20
20
  "encrypt-column"
21
21
  ], t = [
22
+ "export",
23
+ "import",
24
+ "transfers",
25
+ "inspect",
26
+ "unpack",
27
+ "backup",
28
+ "restore",
29
+ "clear-replace-marker",
30
+ "clear-staging"
31
+ ], n = [
22
32
  "scope",
23
33
  "export",
24
34
  "erase"
25
- ], n = (e) => `<${e.join("|")}>`;
35
+ ], r = (e) => `<${e.join("|")}>`;
26
36
  //#endregion
27
- export { t as n, n as r, e as t };
37
+ export { r as i, e as n, n as r, t };
@@ -1,4 +1,4 @@
1
- import { n as e, r as t, t as n } from "./codemodRunner-DSZjgyCL.js";
1
+ import { n as e, r as t, t as n } from "./codemodRunner-De1_316I.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";
@@ -0,0 +1,2 @@
1
+ import { f as e, p as t } from "./updateCommand-2BfbeMvp.js";
2
+ export { e as runApplyCodemods, t as runUpdateCommand };
@@ -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-cGGsLNn6.js"), { outgoingFromEvents: r } = await import("./webhookDiscovery-il9ti-HE.js");
225
+ let { walk: t, loadDiscovered: n } = await import("./dev-DFVuzOj7.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.48.0",
3
+ "version": "0.49.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",
@@ -652,6 +652,18 @@
652
652
  "title": "an aggregate’s `incremental.source` is typechecked against the app’s tables",
653
653
  "kind": "manual"
654
654
  },
655
+ {
656
+ "version": "0.49.0",
657
+ "id": "0.49.0/01_data-imports-to-transfers",
658
+ "title": "`voltro data imports` → `voltro data transfers`",
659
+ "kind": "manual"
660
+ },
661
+ {
662
+ "version": "0.49.0",
663
+ "id": "0.49.0/02_import-run-symbols",
664
+ "title": "`ImportRun` / `describeImportRun` / `IMPORT_RUNS_TABLE` → their `…TransferRun` names",
665
+ "kind": "transform"
666
+ },
655
667
  {
656
668
  "version": "0.5.0",
657
669
  "id": "0.5.0/01_one-terminal-exactly-one",
@@ -739,22 +751,22 @@
739
751
  "@effect/platform-node": "^0.108.0",
740
752
  "@effect/sql": "^0.52.0",
741
753
  "@effect/workflow": "^0.19.0",
742
- "@voltro/ai": "0.48.0",
743
- "@voltro/cache": "0.48.0",
744
- "@voltro/data-transfer": "0.48.0",
745
- "@voltro/database": "0.48.0",
746
- "@voltro/env": "0.48.0",
747
- "@voltro/kv": "0.48.0",
748
- "@voltro/logger": "0.48.0",
749
- "@voltro/plugin-auth": "0.48.0",
750
- "@voltro/plugin-broadcast": "0.48.0",
751
- "@voltro/plugin-mail": "0.48.0",
752
- "@voltro/plugin-storage": "0.48.0",
753
- "@voltro/plugin-webhooks": "0.48.0",
754
- "@voltro/protocol": "0.48.0",
755
- "@voltro/runtime": "0.48.0",
756
- "@voltro/serverless": "0.48.0",
757
- "@voltro/workflow": "0.48.0",
754
+ "@voltro/ai": "0.49.0",
755
+ "@voltro/cache": "0.49.0",
756
+ "@voltro/data-transfer": "0.49.0",
757
+ "@voltro/database": "0.49.0",
758
+ "@voltro/env": "0.49.0",
759
+ "@voltro/kv": "0.49.0",
760
+ "@voltro/logger": "0.49.0",
761
+ "@voltro/plugin-auth": "0.49.0",
762
+ "@voltro/plugin-broadcast": "0.49.0",
763
+ "@voltro/plugin-mail": "0.49.0",
764
+ "@voltro/plugin-storage": "0.49.0",
765
+ "@voltro/plugin-webhooks": "0.49.0",
766
+ "@voltro/protocol": "0.49.0",
767
+ "@voltro/runtime": "0.49.0",
768
+ "@voltro/serverless": "0.49.0",
769
+ "@voltro/workflow": "0.49.0",
758
770
  "chokidar": "^5.0.0",
759
771
  "ioredis": "^5.11.1",
760
772
  "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.48.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. |
710
+ | **What's new in 0.49.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.48.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. |
12
+ | **What's new in 0.49.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. |
@@ -2885,7 +2885,7 @@ recorder is for. Every suspended run says so, and the suspension is scoped to th
2885
2885
  run rather than the process, so requests served alongside it keep recording.
2886
2886
 
2887
2887
  Instead of per-row history, the operation records **itself**. One row in
2888
- `_voltro_data_imports` per run: the mode, the transport, the bundle, the source
2888
+ `_voltro_data_transfers` per run — in either direction: the mode, the transport, the bundle, the source
2889
2889
  deployment's schema fingerprint, the counts — and the failure, for the run you
2890
2890
  are usually looking for. A trail that only records successes goes quiet exactly
2891
2891
  when it is needed.
@@ -2895,9 +2895,57 @@ interlock and a run which cannot write it must not proceed, while this is
2895
2895
  history. A target whose schema is not migrated yet still imports, and says the
2896
2896
  trace could not be written.
2897
2897
 
2898
- ### Watching an import you cannot see
2898
+ ### Every request fits a budget
2899
2899
 
2900
- Over `--target api` the import runs INSIDE the instance. Everything it decides
2900
+ Some environments cap a single request: a job runner that kills a client after
2901
+ ten minutes, an ingress with a 30-second ceiling, a CI step with a deadline. A
2902
+ whole transfer may still take an hour — it just has to do so as a series of short
2903
+ requests, each individually abandonable and individually retryable.
2904
+
2905
+ The rule the endpoints follow:
2906
+
2907
+ > **A request that carries bytes never runs a transfer. A request that starts a
2908
+ > transfer never carries bytes.**
2909
+
2910
+ ```bash
2911
+ # Nothing here may take longer than 30 seconds per request.
2912
+ voltro data import ./bundle --target api --api-url https://app.example \
2913
+ --mode replace --max-request-seconds 30
2914
+
2915
+ # Or: start it and walk away. The outcome is NOT known when this returns.
2916
+ voltro data import ./bundle --target api --api-url https://app.example --detach
2917
+ ```
2918
+
2919
+ Both staging areas — the uploaded bundle on its way in, the produced one on its
2920
+ way out — sit in the system temp directory by default and move with
2921
+ `VOLTRO_IMPORT_UPLOAD_DIR` / `VOLTRO_EXPORT_ARTIFACT_DIR`. Worth setting on a
2922
+ container: a bundle is the size your database is, and a default `/tmp` is
2923
+ frequently a small tmpfs.
2924
+
2925
+ `--max-request-seconds` is DECLARED rather than probed. The thing that kills a
2926
+ request is a policy on your side, and only you know it — in the one environment
2927
+ this was measured against, the ingress would happily hold a connection for an
2928
+ hour and the caller's own toolchain killed the client at ten minutes.
2929
+
2930
+ **An import is three steps.** The bundle goes up (in 16 MiB chunks when it is
2931
+ big, resumable, one plain request when it is small); `import/start` begins the
2932
+ run and answers as soon as the run is recorded; then the client watches the
2933
+ record. `start` is idempotent — retrying it under a budget returns the run that
2934
+ is already going rather than beginning a second destructive one.
2935
+
2936
+ **An export is three steps too.** Ask, wait for the record, then fetch the bytes
2937
+ in ranges from `GET /_voltro/admin/export/download`. The archive used to arrive
2938
+ in the response body, which made the request last as long as reading your whole
2939
+ database.
2940
+
2941
+ **`--detach` returns once the run has started** and says so plainly: exit `0`
2942
+ there means "it started", not "it worked". Attached, the exit code comes from the
2943
+ run's record — `0` finished, `1` failed, `2` still going when you stopped
2944
+ watching. Ctrl-C loses the watching and never the run.
2945
+
2946
+ ### Watching a transfer you cannot see
2947
+
2948
+ Over `--target api` the run happens INSIDE the instance. Everything it decides
2901
2949
  — whether it stages, whether recorders are suspended, how far it has got — is
2902
2950
  printed in the pod's log, and someone reaching for `--target api` is by
2903
2951
  construction someone who cannot reach the database directly and usually cannot
@@ -2906,9 +2954,12 @@ killing the client does not stop it, which is what keeps a dead client from
2906
2954
  leaving a half-emptied target. Both properties are right, and together they used
2907
2955
  to mean an operator could neither see the run nor learn how it ended.
2908
2956
 
2909
- Two things close that, and neither is a streamed response a chunked body has
2910
- to survive every proxy in between, and a buffering reverse proxy turns a
2911
- progress feed into exactly the silence it was meant to replace.
2957
+ Two things close that, and neither is a streamed response on the upload
2958
+ connection. That connection is the thing an operator is most likely to lose: a
2959
+ client killed by a job timeout while the import runs on inside the instance is
2960
+ the ordinary case, and a stream would go quiet at exactly the moment somebody
2961
+ needs to know what happened. A poll can be run from a different machine than
2962
+ the one that started the import.
2912
2963
 
2913
2964
  **Ask before you upload.** A `replace` over the api asks the instance what it is
2914
2965
  going to do, before a byte of the bundle goes up. The instance answers from the
@@ -2918,7 +2969,8 @@ same function the run itself calls, so the answer cannot drift from the run:
2918
2969
  $ voltro data import ./bundle --target api --api-url https://app.example --mode replace
2919
2970
  api replace: the instance WILL stage — 104 table(s) load into copies and the
2920
2971
  target keeps its rows until one short swap at the end. Interrupting the load leaves the target intact.
2921
- api import: uploading the bundle this request stays open until the instance has imported it
2972
+ importing 41200 row(s) across 18 table(s) (staged; the target still holds its own rows)
2973
+ done — 228866 row(s) across 104 table(s)
2922
2974
  ```
2923
2975
 
2924
2976
  With `--bundle-key` the client never holds the bundle, so it sends the key and
@@ -2932,15 +2984,16 @@ That is the difference between "interrupting this is safe" and "interrupting
2932
2984
  this empties the target", which is exactly the decision an operator is making
2933
2985
  while they watch.
2934
2986
 
2935
- **Read the run from anywhere.** `_voltro_data_imports` is opened before the
2936
- first table, advanced every couple of seconds as tables land, and closed with the
2937
- outcome — so polling it IS the progress feed:
2987
+ **Read the run from anywhere.** `_voltro_data_transfers` carries one row per run
2988
+ in EITHER direction: opened before the first table, advanced every couple of
2989
+ seconds as tables land, closed with the outcome — so polling it IS the progress
2990
+ feed:
2938
2991
 
2939
2992
  ```
2940
- $ voltro data imports --target api --api-url https://app.example
2993
+ $ voltro data transfers --target api --api-url https://app.example
2941
2994
  1 import(s) IN FLIGHT — re-run this to watch the counters move
2942
- 2026-08-22T09:04:01.000Z · replace — started and never reported finishing · via api · from /tmp/b
2943
- 2026-08-20T10:00:00.000Z · replace — 228866 row(s) across 104 table(s) · via api · from /tmp/a
2995
+ 2026-08-23T09:04:01.000Z · replace — started and never reported finishing · via api · staged (target untouched until the swap) · from /tmp/b
2996
+ 2026-08-23T08:00:00.000Z · export all — 228866 row(s) across 104 table(s) · via api · from backups/nightly.vbundle
2944
2997
  ```
2945
2998
 
2946
2999
  Same data over `GET /_voltro/admin/imports?limit=20`, behind the same
@@ -2948,6 +3001,11 @@ data-transfer secret (the row names bundles and schema fingerprints). It works
2948
3001
  from a different machine than the one that started the import, and through
2949
3002
  anything that forwards a GET.
2950
3003
 
3004
+ The `staged` part is a separate claim from the preflight's, and both are needed.
3005
+ The preflight says what the instance WILL do; this says what it did. Without it
3006
+ the two could only be closed by reading the pod's log, which is the one place a
3007
+ `--target api` caller cannot reach.
3008
+
2951
3009
  ### An interrupted `replace` cannot be silent
2952
3010
 
2953
3011
  The capture only helps if somebody knows to reach for it. A half-replaced database is indistinguishable from an empty one **from the inside** — every table exists, every constraint holds, every query returns nothing without erroring — so a run that emptied a target and disappeared can be served over for hours before anyone asks the right question.
@@ -1,4 +1,4 @@
1
- # What's new in 0.48.0
1
+ # What's new in 0.49.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,78 +7,65 @@ 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
- ### Added
11
-
12
- - **@voltro/cli, @voltro/data-transfer** — An import over `--target api` can be asked what it will do, and watched while it does it.
13
-
14
- The transport's two correct properties combined into one blind spot: the run happens INSIDE the instance, so every line it produces goes to a pod log; and it is deliberately decoupled from the caller, so killing the client does not stop it (which is what keeps a dead client from leaving a half-emptied target). Someone reaching for `--target api` cannot reach the database directly and usually cannot read that log either, so "watch for the staging line" was advice they could not follow.
10
+ ### ⚠ BREAKING
15
11
 
16
- **A preflight, before a byte is uploaded.** A `replace` over the api now asks the instance whether it will stage, and prints the answer — including the reason when it will not. It answers from `decideStaging`, the same function the run itself calls, so the two cannot drift; a second copy of that decision would answer confidently and diverge on the next change. With `--bundle-key`, where the client never holds the bundle, it sends the key and the instance reads the table list out of the archive's own manifest the caller about to have an instance empty its own database is the last one who should be told to check a log they cannot read.
12
+ - **@voltro/cli, @voltro/data-transfer, @voltro/database, @voltro/sql-sqlite, @voltro/voltro** A data transfer is a series of short requests nowno single one may outlive a caller's budget.
17
13
 
18
- **`voltro data imports`** (and `GET /_voltro/admin/imports`, behind the same data-transfer secret) reads the history and the run in flight. `_voltro_data_imports` was write-only; it is now opened before the first table, **advanced every couple of seconds as tables land**, and closed with the outcome so polling it is the progress feed. Not a streamed response on the upload connection: a chunked body has to survive every proxy in between, and a buffering reverse proxy turns a progress feed into exactly the silence it was meant to replace.
14
+ **The rule:** *a request that carries bytes never runs a transfer; a request that starts a transfer never carries bytes.* It was broken in the worst available place. The upload was already chunked and resumable many short requests, each abandonable and then the FINAL chunk fell through and ran the whole import. So the longest request of the flow arrived AFTER the entire upload had succeeded, and a caller under a policy that caps a single request (a job runner that kills a client at ten minutes; a 30 s ingress ceiling) lost the most expensive thing they had already paid for. A single-request upload had the same shape without the excuse, and the export had no protocol at all: one request that read the whole database and streamed it back.
19
15
 
20
- **And a `replace` that does not stage now says why.** The staging set was an early `return []` three conditions deep, and the empty array met a `length > 0` further down and read as "do not stage". A bundle table the target does not have the ordinary case for a development source against a production target turned the non-destructive path off in silence. Every reason routes through one message now; the cycle case additionally used to be gated on `atomic`, so the mode where an interrupted run leaves the worst outcome was also the one that said the least.
16
+ **Import.** `POST /_voltro/admin/import` accumulates and answers `202`. `POST /_voltro/admin/import/start` begins the run and answers `202 { runId }` as soon as the run's first row exists. `start` is idempotent per upload it is a short request and therefore a retryable one, and without a claim a retry would begin a second destructive run from the same bytes. A claim whose run has ENDED is taken over rather than honoured forever, so a process that dies holding one cannot poison a bundle.
21
17
 
22
- ### Changed
18
+ **Export.** `POST /_voltro/admin/export` answers `202 { runId }` and produces in the background; the bytes come back from `GET /_voltro/admin/export/download?runId=&offset=&length=` in ranges, resumable, with the total in a header. Object storage (`--bundle-key`) remains for a bundle you want to KEEP — it is no longer the only way to get one out, because requiring it would leave an instance without storage unable to export at all.
23
19
 
24
- - **@voltro/cli**The declared framework schema no longer depends on `NODE_ENV`.
20
+ **The client.** `--max-request-seconds` (or `VOLTRO_MAX_REQUEST_SECONDS`) declares the budget — declared rather than probed, because the thing that kills a request is a policy on the caller's side and only they know it. `--detach` returns once the run has started and says the outcome is NOT known. Attached, the CLI polls the record and prints per-table progress; Ctrl-C then loses the watching and never the run.
25
21
 
26
- `_voltro_traces` and `_voltro_undo_log` defaulted to on outside production. That was allowed with an explicit justification one decider makes every command in a deployment agree and the justification was about the schema FINGERPRINT, which only ever compares processes inside ONE deployment.
22
+ **The trap, stated because it is the one way to get this wrong:** a failure used to arrive in the response (409 on drift, 409 on a refused mode, 500 otherwise). After the split the response is a `202`, so **a client deriving its exit code from the status line reports a failed import as a success.** The exit code comes from the polled record, in one shared function, and the drift check moved into `start` where it can still be a refusal that leaves no history.
27
23
 
28
- The declared set has a second reader that spans two, and it was never considered: `voltro data`. A bundle exported from a development database carries the tables that database has, and a staged (non-destructive) `replace` needs every bundle table to exist in the target. So one source tree produced a bundle a production target could not stage, and the run fell back to truncating it with nothing red anywhere, on the one path where that difference is the entire point.
24
+ **Two knobs that were constants.** `VOLTRO_IMPORT_UPLOAD_DIR` and `VOLTRO_EXPORT_ARTIFACT_DIR` move the staging areas off the default temp filesystem which on a container is frequently a small tmpfs, so an instance simply could not accept a bundle the size a grown database produces, and the failure arrived as a write error halfway through an upload somebody had been waiting on.
29
25
 
30
- Both tables are declared in **every environment** now. The trade is the one `_voltro_cdc_offsets` already makes: an unused declared table costs one empty table and buys agreement. `VOLTRO_UNDO` / `VOLTRO_TRACING_PERSIST` still decide what a process WRITES; they never decided the schema and still do not. Set `schema: { traces: false, undo: false }` in `app.config.ts` to keep one out in every environment, or the divergence is back by hand.
26
+ **Renamed:** `voltro data imports` `voltro data transfers`, and `_voltro_data_imports` `_voltro_data_transfers` with a `direction` column. The command showed one direction and now shows both; the table rename carries its rows via the declarative differ on every dialect and needs nothing from you. The CLI rename ships a `manual` codemod the command lives in scripts, CI jobs and runbooks, which `voltro update` cannot see or rewrite. The four PUBLIC exports that named the same record were renamed with it (`ImportRun`, `describeImportRun`, `IMPORT_RUNS_TABLE`, `_voltroImportRunsTable`); those are application source, they ship their own `transform` codemod, and they have their own entry.
27
+ - **@voltro/database, @voltro/voltro** — The run-history exports say `transfer`, not `import`.
31
28
 
32
- **Upgrade:** a production app that never declared them gains two empty tables. `voltro db apply` (or a `voltro dev` boot) plans and applies them like any other framework table, on every dialect. Run it before the pods roll, as with any schema change — a migrate job and a fleet that disagree is exactly what this removes.
33
-
34
- `voltro doctor` now prints the three decided tables and what decided each, on a HEALTHY run. That text existed and was reachable only from the `prod-mismatch` refusal — a message that appears exclusively after a fleet is down is an explanation, not a warning.
35
-
36
- ### Fixed
29
+ `@voltro/database` (and `voltro/database`) renamed four public exports along with the table behind them:
37
30
 
38
- - **@voltro/cli, @voltro/data-transfer** `voltro data export --exclude a,b` made the bundle BIGGER, and made it unusable for a `replace`.
31
+ | was | is | |---|---| | `ImportRun` | `DataTransferRun` | | `describeImportRun` | `describeTransferRun` | | `IMPORT_RUNS_TABLE` | `DATA_TRANSFERS_TABLE` | | `_voltroImportRunsTable` | `_voltroDataTransfersTable` |
39
32
 
40
- It expanded into `{ kind: 'tables', tables: <everything else> }`, on the reasoning that a manifest should record what was exported rather than claim "everything". Right goal, wrong mechanism, and it cost two things at once. `all` is the only scope that filters out the tables which describe a deployment, so excluding two names silently added nine others back — the migration ledger among them, whose foreign row takes an environment down at the next boot. And `replace` refuses a named scope, so the honest way to leave a table out was also the way to make the bundle unusable for the mode it was being prepared for.
33
+ An EXPORT writes to this record now — the row carries a `direction` so every one of those names described half of what it holds.
41
34
 
42
- The exclusion is a FIELD of the `all` scope now: the filter still runs, the manifest still says "everything except these" (a different and truer claim than "these"), and `replace` accepts it while naming the tables it will therefore not touch. `--exclude` also works over `--target api` now it no longer expands against a table list only the instance has, so the instance resolves it where that list already is.
35
+ A `transform` codemod rewrites all four, alias-aware, from either module spelling. It also rewrites a hand-spelled `_voltro_data_imports` in a string, template or raw-SQL fragment, and that is the half worth stating: the four identifiers announce themselves as compile errors, while a query that addresses the table by name has nothing to fail on. The table itself moves with its rows via the declarative differ on every dialect.
43
36
 
44
- Every framework table is classified as portable or environment-local, with the reason, and a new one fails the build until somebody decides. That guard existed and did not help: it was satisfied by a second, hand-kept list inside its own test, and the two disagreed about `_voltro_traces` and `_voltro_undo_log` — nothing was unclassified, something was classified twice, once wrongly. There is one list now, read by both guards. Traces, undo, the outbox and its attempts, idempotency keys, storage grants, spend and usage accounting, delivery attempts and schedule-firing history joined the environment-local side: a row from elsewhere would make the target act, or claim history it did not live.
45
- - **@voltro/sql-turso** — Opening a second pooled turso connection could fail instantly with `database is locked` — from inside the constructor, before a single query ran.
37
+ This is filed apart from the transfer-protocol entry beside it deliberately. That one's codemod is `manual` and is about a CLI invocation living in scripts and CI jobs; this one is application source and is rewritten for you. Reading the first as covering both is what would leave a build broken with a note saying nothing in your source was affected.
46
38
 
47
- `makeConnection` set its pragmas in the order `journal_mode` → `foreign_keys` → `busy_timeout`. The engine defaults `busy_timeout` to **0**, so a statement that meets a held lock fails on the spot instead of waiting — and `journal_mode= experimental_mvcc` needs the file exclusively. Since `makeConnection` runs once per POOLED connection (default 4), opening connection two while connection one held the file hit that exclusive pragma with no lock-wait configured yet:
48
-
49
- SqlError: Failed to enable Turso MVCC (journal_mode=experimental_mvcc): database is locked
50
-
51
- `busy_timeout` is set FIRST now. Nothing else changed — same value, same pragmas, same connection.
52
-
53
- **Why it hid for so long.** The file already carried a long, correct note about `busy_timeout` being mandatory with a pool, and a separate fix had closed the DDL half (`retryFilter` + bounded retries in `applySchema`). Both are about the same lock class, so the constructor read as covered — but a setting cannot protect the two pragmas that run before it.
39
+ ### Added
54
40
 
55
- It surfaces as an unrelated flaky test, because the failure lands wherever the second connection happens to be opened: a `CREATE TABLE` in one run, an MVCC pragma in the next. It cost three release gates — twice locally, once on a CI runner — and was twice diagnosed as machine contention and closed. It is contention-DEPENDENT, which is not the same as being the machine's fault.
41
+ - **@voltro/database, @voltro/data-transfer, @voltro/cli** An import records WHETHER it staged, and a soft-dropped column says so in a drift refusal.
56
42
 
57
- Verified: 8 serial runs and 6 concurrent suites at load 120 failures, 0 occurrences of the message. The failure was intermittent before, so this is evidence rather than proof; the mechanism, however, is not in doubt.
58
- - **@voltro/runtime, @voltro/data-transfer, @voltro/cli** — The `source:` recorder broke every WRITE on the in-memory store, and actions are recorded now.
43
+ **`_voltro_data_imports.staged`.** The pre-upload preflight says what the instance WILL do; the run's own line says what it did and that line is printed inside the instance, which is exactly where an operator using `--target api` cannot read it. So "we were told it would stage" and "it staged" were two claims with no way to close the gap between them from outside. The column is `null` for a mode where the question does not arise; `voltro data imports` and `GET /_voltro/admin/imports` both carry it.
59
44
 
60
- The recording wrapper is a `Proxy`, and it handed methods back unbound so `this` was the PROXY, and a class with `#private` fields answers that with `TypeError: Receiver must be an instance of class InMemoryDataStore`. Under `voltro dev` on the memory store, where the recorder is installed by default, that is every write in the app.
45
+ **A soft-dropped column is named as one.** `<original>__dropped_<stamp>` is what the differ leaves behind when an app stops declaring a column only the database that did the drop has it, so it drifts against every target. The refusal reported it as an ordinary missing column ("the value has nowhere to go"), which points at the TARGET's schema: the one place the fix does not lie. It now says where the column lives and how to reclaim it.
61
46
 
62
- Every test passed throughout, and the reason is worth more than the fix: `query` is the one method the wrapper invokes with an explicit receiver, so everything that only READ through it worked. The wrapper's whole purpose is reading, so nothing in its own suite ever wrote. It was found by a test about something else entirely — asking what `crud.create` reads — which needed a write to answer.
47
+ ### Fixed
63
48
 
64
- **Actions are recorded too now**, and an action's `source:` means something different from a query's. A query's is a reactive trigger set; an action's declares what it TOUCHES, and the field's own documentation records what an undeclared read costs: `voltro check` reported a table five action paths read and wrote as an orphan, and advised removing it. That is not a quiet subscription, it is advice to delete a live table.
49
+ - **@voltro/data-transfer, @voltro/sql-sqlite, @voltro/database** A successful `--mode replace --no-atomic` left a marker that refused the next boot.
65
50
 
66
- **Measured rather than reasoned about:** `crud.create`, `crud.update` and `crud.remove` issue NO read at all, so a read recorder has nothing to say about them — but `crud.getById` does read, and with `include:` it eager-loads, so it is covered like `crud.list`.
51
+ Two defects, one visible symptom, and both were silent by construction.
67
52
 
68
- **And a kill test for the one moment nobody had reproduced.** The existing test kills mid-LOAD, which staging turned into the harmless part; the destructive second went untested precisely because it became short. The new case kills as the swap begins and asserts the property rather than the race: the target is one state or the other, never a mix, and never empty. Two defects in the harness came out of writing it a worker that died SILENTLY (its failure now goes into the marker the parent already reads, instead of looking like a slow start), and an `exit` listener attached only after the SIGKILL, which hung to the full timeout whenever the child finished first.
53
+ **The clear was gated on the WRITE's condition.** `!(useLedger && ledger.truncated)` means "an earlier attempt already recorded this destructive run, do not write a second row" correct on the write. Copied down to the clear its meaning inverts: `ledger.truncated` is set by the emptying step of THIS run, so on `--no-atomic` (the only mode where `useLedger` is true) the clear was skipped by the very run that wrote the marker.
69
54
 
70
- **And the import trace was never written on the path most likely to be used.** A deployment ran a successful `voltro data import` against a database that HAD the table, and got no row and no message at all. The write was gated on this PROCESS's table registry, and `voltro data`'s own boot builds a store and introspects the live schema it never registers the framework set, so the gate was `undefined` exactly there. The authority for "does the target have this table" is the target's SCHEMA, which that boot already introspects; the table is also registered before the write, because a CLI run has not done it and the write needs the column metadata.
55
+ **And a `Date` in a predicate is not bindable on sqlite.** `better-sqlite3` binds numbers, strings, bigints, buffers and null; the row path has coerced Dates since that store was written, and the eager-join compiler carried its own private copy of the fix, but `query` / `updateMany` / `deleteMany` bound the raw value. So every comparison of a timestamp column against a `Date` failed with `Failed to execute statement` including the marker's clear, which swallows its errors ON PURPOSE (a store with no marker table must not fail an import over a bookkeeping row) and therefore said nothing. The retention sweep compares `lt(column, cutoff)` the same way.
71
56
 
72
- The sharper half is the silence, and it was self-inflicted: the skip was written three lines under a comment about how an absent table cannot be detected by the write failing. The message now lives in `voltro data import`, which is the layer that INTROSPECTED a first attempt put it in the importer, where a `targetSnapshot` may legitimately be narrow rather than complete, so it fired at callers whose snapshot simply did not mention a framework table.
57
+ Together: a fully successful replace left the marker standing, and the next boot REFUSED with "a destructive import did not finish" over a database that was completely fine. The one recovery is a command the operator has no reason to think they need. It stayed invisible because `atomic` defaults to true for `replace`, and because a staged replace writes no marker at all two defaults hiding the one mode that exists for large, interruptible loads.
73
58
 
74
- ### Internal (no consumer-facing effect)
59
+ The coercion is shared now and applied at all three predicate sites, with a guard that fails on a fourth that forgets. A dry run also closes its trace row: a preview that finished instantly used to leave the record open, so a polling caller waited out its whole budget over a run that was long done.
60
+ - **@voltro/cli** — `voltro codegen` declared twenty framework tables fewer than a boot.
75
61
 
76
- - **@voltro/data-transfer** — `SAVEPOINT_BATCH_SIZE` carries its documentation again.
62
+ The app half of this was fixed last release and looked like the whole thing. It was not: `codegen` derived the FEATURE MIX from the file list it had just walked, and that list is the entity/relations set which contains no `*.workflow.tsx`, no `*.agent.ts`, no `*.cron.tsx`. So every feature flag came back false, and the dialect was never passed at all, taking `_voltro_cdc_offsets` with it. Measured on an app with two workflow files: 18 framework tables where the shared assembly produces 33.
77
63
 
78
- A new `TRACE_ADVANCE_MS` was declared BETWEEN the constant's doc block and the constant, so TypeScript attached the block to whatever now followed it and the exported symbol was left bare — the api golden recorded it as `// @public (undocumented)` and the published report shipped it that way.
64
+ It calls `assembleFrameworkTables({ root })` now the same entry `voltro db plan/apply` uses, which detects the mix from the root rather than being handed one.
79
65
 
80
- Third time this exact shape has appeared (`sourceKeys`, `recordsTable`, now this one), always the same mechanism: an insertion above a documented declaration silently re-homes the comment. Nothing warns, because both the code and the doc block are individually validonly the golden's `(undocumented)` marker notices, and it reads as noise unless someone diffs it against the last TAG.
66
+ The parity guard moved with it. Comparing the two WALKS could not see this: both walks were right about files, and the divergence was introduced one layer past them. It compares the assembled SETS now, and runs codegen's own table function on a tree whose only feature signals are a workflow file and an agent file a source assertion that the right function is CALLED cannot see a wrong argument handed to it, and a wrong argument is what this was.
67
+ - **@voltro/cli** — `voltro data --help` advertised four subcommands out of nine.
81
68
 
82
- Documentation only; no behaviour, no signature change.
69
+ It printed `<export|import|backup|restore>` while the command dispatched those four plus `imports`, `inspect`, `unpack`, `clear-replace-marker` and `clear-staging`. A quoted enumeration is read as exhaustive — the same failure `subcommandNames.ts` was written for after a `db` list cost two wrong conclusions — and the missing entry here is the one that answers "what is my import doing right now" for somebody who cannot reach the pod.
83
70
 
84
- **`apiSurface: compatible`, and the reason is the whole point of the change.** The golden line that moved is `// @public (undocumented)` `// @public`: an api-extractor MARKER describing whether a doc comment is present. No type, no signature, no name. `SAVEPOINT_BATCH_SIZE` is still `= 200`, still exported, still the same literal type nothing that compiled can stop compiling.
71
+ `subcommandHelpParity.test.ts` had listed `data` among the commands it could not check, loudly and correctly: there was no name list to check against. There is one now (`DATA_SUBCOMMANDS`), the dispatch is a keyed `Record`, so a subcommand with no name and a name with no handler are both compile errors, and the usage line is generated from the same array. `data` is out of the unchecked list.
@@ -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.48.0",
16
- "@voltro/cli": "0.48.0",
17
- "@voltro/database": "0.48.0",
18
- "@voltro/env": "0.48.0",
19
- "@voltro/protocol": "0.48.0",
20
- "@voltro/runtime": "0.48.0",
15
+ "@voltro/ai": "0.49.0",
16
+ "@voltro/cli": "0.49.0",
17
+ "@voltro/database": "0.49.0",
18
+ "@voltro/env": "0.49.0",
19
+ "@voltro/protocol": "0.49.0",
20
+ "@voltro/runtime": "0.49.0",
21
21
  "effect": "^3.22.0"
22
22
  },
23
23
  "devDependencies": {
24
- "@voltro/testing": "0.48.0",
24
+ "@voltro/testing": "0.49.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.48.0",
17
- "@voltro/database": "0.48.0",
18
- "@voltro/env": "0.48.0",
19
- "@voltro/plugin-auth": "0.48.0",
20
- "@voltro/protocol": "0.48.0",
21
- "@voltro/runtime": "0.48.0",
22
- "@voltro/sql-postgres": "0.48.0",
16
+ "@voltro/cli": "0.49.0",
17
+ "@voltro/database": "0.49.0",
18
+ "@voltro/env": "0.49.0",
19
+ "@voltro/plugin-auth": "0.49.0",
20
+ "@voltro/protocol": "0.49.0",
21
+ "@voltro/runtime": "0.49.0",
22
+ "@voltro/sql-postgres": "0.49.0",
23
23
  "effect": "^3.22.0"
24
24
  },
25
25
  "devDependencies": {
26
- "@voltro/testing": "0.48.0",
26
+ "@voltro/testing": "0.49.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.48.0",
20
- "@voltro/database": "0.48.0",
21
- "@voltro/env": "0.48.0",
22
- "@voltro/plugin-multitenancy": "0.48.0",
23
- "@voltro/protocol": "0.48.0",
24
- "@voltro/runtime": "0.48.0",
19
+ "@voltro/cli": "0.49.0",
20
+ "@voltro/database": "0.49.0",
21
+ "@voltro/env": "0.49.0",
22
+ "@voltro/plugin-multitenancy": "0.49.0",
23
+ "@voltro/protocol": "0.49.0",
24
+ "@voltro/runtime": "0.49.0",
25
25
  "effect": "^3.22.0"
26
26
  },
27
27
  "devDependencies": {
28
- "@voltro/testing": "0.48.0",
28
+ "@voltro/testing": "0.49.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.48.0",
17
- "@voltro/database": "0.48.0",
18
- "@voltro/env": "0.48.0",
19
- "@voltro/plugin-deactivation": "0.48.0",
20
- "@voltro/protocol": "0.48.0",
21
- "@voltro/runtime": "0.48.0",
16
+ "@voltro/cli": "0.49.0",
17
+ "@voltro/database": "0.49.0",
18
+ "@voltro/env": "0.49.0",
19
+ "@voltro/plugin-deactivation": "0.49.0",
20
+ "@voltro/protocol": "0.49.0",
21
+ "@voltro/runtime": "0.49.0",
22
22
  "effect": "^3.22.0"
23
23
  },
24
24
  "devDependencies": {
25
- "@voltro/testing": "0.48.0",
25
+ "@voltro/testing": "0.49.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.48.0",
17
- "@voltro/database": "0.48.0",
18
- "@voltro/env": "0.48.0",
19
- "@voltro/plugin-mail": "0.48.0",
20
- "@voltro/plugin-multitenancy": "0.48.0",
21
- "@voltro/protocol": "0.48.0",
22
- "@voltro/runtime": "0.48.0",
16
+ "@voltro/cli": "0.49.0",
17
+ "@voltro/database": "0.49.0",
18
+ "@voltro/env": "0.49.0",
19
+ "@voltro/plugin-mail": "0.49.0",
20
+ "@voltro/plugin-multitenancy": "0.49.0",
21
+ "@voltro/protocol": "0.49.0",
22
+ "@voltro/runtime": "0.49.0",
23
23
  "effect": "^3.22.0",
24
24
  "react": "^19.0.0"
25
25
  },
26
26
  "devDependencies": {
27
- "@voltro/testing": "0.48.0",
27
+ "@voltro/testing": "0.49.0",
28
28
  "typescript": "^6.0.3",
29
29
  "@vitest/coverage-v8": "^4.1.10",
30
30
  "vitest": "^4.1.10"