@voltro/cli 0.43.1 → 0.44.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 (90) hide show
  1. package/CHANGELOG.md +65 -0
  2. package/dist/{apiBuild-DF0f69Gf.js → apiBuild-DvWreIQP.js} +1 -1
  3. package/dist/apiBuild-XaW40W8x.js +2 -0
  4. package/dist/bin.js +1 -1
  5. package/dist/{build-LC_IJaHL.js → build-Ckc-FA7d.js} +6 -6
  6. package/dist/{checkCommand-DBpTSK8d.js → checkCommand-AlbawIal.js} +1 -1
  7. package/dist/{checkCommand-DDxwJCja.js → checkCommand-BNfEs4KG.js} +2 -2
  8. package/dist/{codegenCommand-LjxfK4Am.js → codegenCommand-iClXoRCW.js} +1 -1
  9. package/dist/{codemodRunner-Cnnz4-I7.js → codemodRunner-BKTnaGOz.js} +22 -0
  10. package/dist/{commands-CbQcuWsE.js → commands-fsnezHLH.js} +18 -18
  11. package/dist/{dashboardCommand-CAHjCe_9.js → dashboardCommand-DHDmlo9A.js} +1 -1
  12. package/dist/dataCommand-sfpGV694.js +867 -0
  13. package/dist/{dev-D0E5x2Ue.js → dev-BZO5aLdr.js} +1 -1
  14. package/dist/{dev-B2rZfqxw.js → dev-D-JZ6CGQ.js} +930 -916
  15. package/dist/doctorCommand-BeLOwq7S.js +2 -0
  16. package/dist/{doctorCommand-Ct_3BHgd.js → doctorCommand-BlUyESpD.js} +9 -9
  17. package/dist/{dormancyCommand-D1DKnYce.js → dormancyCommand-lQxdrU8C.js} +1 -1
  18. package/dist/{embeddingsCommand-1sZRBKlM.js → embeddingsCommand-ByBTju6X.js} +1 -1
  19. package/dist/{envCommand-7qjlQgLD.js → envCommand-CpCbYAgR.js} +1 -1
  20. package/dist/{evolveCommand-D5opB48I.js → evolveCommand-CBV3-TtK.js} +2 -2
  21. package/dist/index.js +1 -1
  22. package/dist/{infoCommand-Bs_8RU22.js → infoCommand-DJoA9cKD.js} +1 -1
  23. package/dist/{runtimeTrace-Wmi7V-wN.js → runtimeTrace-DJ9tPhrc.js} +1 -1
  24. package/dist/{sdkgen-DbTFGCjg.js → sdkgen-7dr8-Onw.js} +1 -1
  25. package/dist/serveCommand-BlnAlNQg.js +1891 -0
  26. package/dist/serveCommand-Dg_E1ev4.js +2 -0
  27. package/dist/serveEntry.js +1 -1
  28. package/dist/{updateCommand-CrI9B4d7.js → updateCommand-BvRidHp6.js} +1 -1
  29. package/dist/updateCommand-DcKVmLVN.js +2 -0
  30. package/dist/{webhooksCommand-BXUP5YjW.js → webhooksCommand-BfO_JF2b.js} +1 -1
  31. package/package.json +23 -17
  32. package/templates/AGENTS.md +1 -1
  33. package/templates/agent-docs/_index.md +1 -1
  34. package/templates/agent-docs/cli.md +144 -4
  35. package/templates/agent-docs/database/seedsdialects.md +12 -0
  36. package/templates/agent-docs/plugins/versioning.md +2 -0
  37. package/templates/agent-docs/whats-new.md +16 -51
  38. package/templates/apps/api-ai/package.json +7 -7
  39. package/templates/apps/api-auth/package.json +8 -8
  40. package/templates/apps/api-backend/package.json +7 -7
  41. package/templates/apps/api-backend-deactivation/package.json +7 -7
  42. package/templates/apps/api-backend-mail/package.json +8 -8
  43. package/templates/apps/api-backend-mariadb/package.json +9 -9
  44. package/templates/apps/api-backend-sqlite/package.json +8 -8
  45. package/templates/apps/api-backend-storage/package.json +8 -8
  46. package/templates/apps/api-cms/package.json +10 -10
  47. package/templates/apps/api-collab/package.json +8 -8
  48. package/templates/apps/api-data-advanced/package.json +8 -8
  49. package/templates/apps/api-durable/package.json +8 -8
  50. package/templates/apps/api-feature-flags/package.json +9 -9
  51. package/templates/apps/api-governance/package.json +8 -8
  52. package/templates/apps/api-kv/package.json +8 -8
  53. package/templates/apps/api-moderation/package.json +8 -8
  54. package/templates/apps/api-observability/package.json +8 -8
  55. package/templates/apps/api-ratelimit/package.json +8 -8
  56. package/templates/apps/api-rbac/package.json +8 -8
  57. package/templates/apps/api-rest/package.json +7 -7
  58. package/templates/apps/api-saas/package.json +11 -11
  59. package/templates/apps/api-saas-starter/package.json +10 -10
  60. package/templates/apps/api-search/package.json +8 -8
  61. package/templates/apps/api-status/package.json +8 -8
  62. package/templates/apps/api-versioning/package.json +8 -8
  63. package/templates/apps/api-webhooks/package.json +9 -9
  64. package/templates/apps/changelog/package.json +6 -6
  65. package/templates/apps/edge-functions/package.json +2 -2
  66. package/templates/apps/frontend-admin/package.json +8 -8
  67. package/templates/apps/frontend-app/package.json +9 -9
  68. package/templates/apps/frontend-auth/package.json +8 -8
  69. package/templates/apps/frontend-blank/package.json +7 -7
  70. package/templates/apps/frontend-cms/package.json +9 -9
  71. package/templates/apps/frontend-collab/package.json +10 -10
  72. package/templates/apps/frontend-contact/package.json +7 -7
  73. package/templates/apps/frontend-dashboard/package.json +7 -7
  74. package/templates/apps/frontend-docs/package.json +7 -7
  75. package/templates/apps/frontend-i18n/package.json +6 -6
  76. package/templates/apps/frontend-landing/package.json +7 -7
  77. package/templates/apps/frontend-portal/package.json +8 -8
  78. package/templates/apps/frontend-saas/package.json +8 -8
  79. package/templates/apps/frontend-spa/package.json +7 -7
  80. package/templates/apps/frontend-ssr/package.json +7 -7
  81. package/templates/apps/frontend-ssr-api/package.json +8 -8
  82. package/templates/apps/frontend-static-blog/package.json +6 -6
  83. package/templates/apps/frontend-status/package.json +8 -8
  84. package/templates/apps/mobile-app/package.json +4 -4
  85. package/dist/apiBuild-B7NgSMhX.js +0 -2
  86. package/dist/dataCommand-CrqvbVOv.js +0 -679
  87. package/dist/doctorCommand-CdsctSwg.js +0 -2
  88. package/dist/serveCommand-C8xzYCwa.js +0 -1776
  89. package/dist/serveCommand-CGNTXafv.js +0 -2
  90. package/dist/updateCommand-BvkKlV7D.js +0 -2
@@ -0,0 +1,2 @@
1
+ import { n as e } from "./serveCommand-BlnAlNQg.js";
2
+ export { e as runServeDispatch };
@@ -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-C8xzYCwa.js";
4
+ import { t as r } from "./serveCommand-BlnAlNQg.js";
5
5
  export { e as loadDotEnv, t as registerAppModules, n as registerDriver, r as runServe };
@@ -1,4 +1,4 @@
1
- import { n as e, r as t, t as n } from "./codemodRunner-Cnnz4-I7.js";
1
+ import { n as e, r as t, t as n } from "./codemodRunner-BKTnaGOz.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-BvRidHp6.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-D0E5x2Ue.js"), { outgoingFromEvents: r } = await import("./webhookDiscovery-il9ti-HE.js");
225
+ let { walk: t, loadDiscovered: n } = await import("./dev-BZO5aLdr.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.43.1",
3
+ "version": "0.44.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",
@@ -634,6 +634,12 @@
634
634
  "title": "`DanglingReferenceError` → `RowsRefusedError` (named for the outcome, not a guessed cause)",
635
635
  "kind": "transform"
636
636
  },
637
+ {
638
+ "version": "0.44.0",
639
+ "id": "0.44.0/01_import-outcome",
640
+ "title": "`runImport` returns an ImportOutcome — read `.manifest` for the bundle",
641
+ "kind": "manual"
642
+ },
637
643
  {
638
644
  "version": "0.5.0",
639
645
  "id": "0.5.0/01_one-terminal-exactly-one",
@@ -721,22 +727,22 @@
721
727
  "@effect/platform-node": "^0.108.0",
722
728
  "@effect/sql": "^0.52.0",
723
729
  "@effect/workflow": "^0.19.0",
724
- "@voltro/ai": "0.43.1",
725
- "@voltro/cache": "0.43.1",
726
- "@voltro/data-transfer": "0.43.1",
727
- "@voltro/database": "0.43.1",
728
- "@voltro/env": "0.43.1",
729
- "@voltro/kv": "0.43.1",
730
- "@voltro/logger": "0.43.1",
731
- "@voltro/plugin-auth": "0.43.1",
732
- "@voltro/plugin-broadcast": "0.43.1",
733
- "@voltro/plugin-mail": "0.43.1",
734
- "@voltro/plugin-storage": "0.43.1",
735
- "@voltro/plugin-webhooks": "0.43.1",
736
- "@voltro/protocol": "0.43.1",
737
- "@voltro/runtime": "0.43.1",
738
- "@voltro/serverless": "0.43.1",
739
- "@voltro/workflow": "0.43.1",
730
+ "@voltro/ai": "0.44.0",
731
+ "@voltro/cache": "0.44.0",
732
+ "@voltro/data-transfer": "0.44.0",
733
+ "@voltro/database": "0.44.0",
734
+ "@voltro/env": "0.44.0",
735
+ "@voltro/kv": "0.44.0",
736
+ "@voltro/logger": "0.44.0",
737
+ "@voltro/plugin-auth": "0.44.0",
738
+ "@voltro/plugin-broadcast": "0.44.0",
739
+ "@voltro/plugin-mail": "0.44.0",
740
+ "@voltro/plugin-storage": "0.44.0",
741
+ "@voltro/plugin-webhooks": "0.44.0",
742
+ "@voltro/protocol": "0.44.0",
743
+ "@voltro/runtime": "0.44.0",
744
+ "@voltro/serverless": "0.44.0",
745
+ "@voltro/workflow": "0.44.0",
740
746
  "chokidar": "^5.0.0",
741
747
  "ioredis": "^5.11.1",
742
748
  "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.43.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.44.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.43.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.44.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. |
@@ -2643,6 +2643,30 @@ over-read is worse than no preview.
2643
2643
  what it does — a silently-ignored table name is how a run scoped to one table writes the
2644
2644
  whole bundle.
2645
2645
 
2646
+ ### Every `import` option, in one place
2647
+
2648
+ | Option | Default | What it does |
2649
+ |---|---|---|
2650
+ | `--mode upsert\|append\|replace` | `upsert` | How rows are written. See the table below. |
2651
+ | `--on-conflict skip\|fail` | `skip` | `append` only: what to do when the primary key already exists. |
2652
+ | `--atomic` | **on for `replace`**, off otherwise | Wrap the whole table phase in ONE transaction — readers see the import all-or-nothing. |
2653
+ | `--no-atomic` | — | Opt out of that. See the trade below. |
2654
+ | `--tables a,b` | every table | Import only these tables from the bundle. |
2655
+ | `--dry-run` | off | Run every pre-flight and report what WOULD move; write nothing. |
2656
+ | `--force` | off | Proceed despite schema drift AND cross-dialect warnings. |
2657
+ | `--no-verify` | off | Skip per-table checksum verification (direct target only). |
2658
+ | `--allow-live` | off | Override the refusal to write directly into a database a live instance is serving (direct target only). |
2659
+ | `--assets` | off | Restore blobs as well as rows. |
2660
+ | `--passphrase <s>` | — | Decrypt an `--encrypt`ed bundle (direct target only; over the api the CLI decrypts before sending). |
2661
+ | `--target direct\|api` | `direct` | Write straight to the database, or through a running instance's admin endpoint. |
2662
+ | `--api-url <url>` / `--token <secret>` | — | Required by `--target api`. The token must equal the instance's data-transfer secret. |
2663
+ | `--bundle-key <key>` | — | `--target api`: have the instance PULL the archive from object storage instead of uploading it. |
2664
+ | `--chunk-size <mb>` | 16 | `--target api`: bytes per upload chunk. Chunking engages automatically above one chunk. |
2665
+ | `--timeout <seconds>` | none | `--target api`: give up waiting for the instance. Default is to wait as long as the import takes. |
2666
+ | `--json` | off | Machine-readable result. |
2667
+
2668
+ A flag this subcommand does not read is refused, not ignored — see below.
2669
+
2646
2670
  ### A flag this command does not read is an ERROR
2647
2671
 
2648
2672
  Every `voltro data` subcommand declares the flags it reads, per target, and refuses anything
@@ -2729,9 +2753,67 @@ Anything still unresolvable — the parent row exists in **neither the bundle no
2729
2753
  |---|---|---|---|
2730
2754
  | `upsert` (default) | INSERT-or-UPDATE by primary key | sync / idempotent re-import | overwrites per row |
2731
2755
  | `append` | INSERT only | additive data (event log, new seed) | `--on-conflict skip` (default) or `fail` |
2732
- | `replace` | TRUNCATE the target tables (children-first) then INSERT | full refresh — target ends up **exactly** the bundle | — |
2756
+ | `replace` | empty the target tables, then INSERT | full refresh — target ends up **exactly** the bundle | — |
2757
+
2758
+ `replace` **refuses a partial bundle** (a subset / tenant / table scope): emptying would delete rows the bundle never carried. Re-export with full scope, or use `upsert`. (`append --on-conflict fail` throws on a duplicate primary key only where the store enforces the constraint — every SQL dialect does; the in-memory dev store overwrites.)
2759
+
2760
+ #### How `replace` empties the target, and what it refuses
2761
+
2762
+ The bundle's tables are emptied **as one unit, in one transaction, with referential integrity suspended for the duration** — not table by table. Both halves matter:
2763
+
2764
+ - **All or nothing — the emptying AND the load.** `--mode replace` runs the whole table phase in ONE transaction by default, so a run that cannot finish leaves the target exactly as it found it. That default is a correction: the guarantee used to cover only the emptying, and a replace that died partway through the *load* left the target emptied of its old rows and holding part of the new ones. There is no useful state for a replace to stop in, which is why it is the default rather than a flag you have to know about.
2765
+
2766
+ One transaction also closes a window that is easy to miss. Between the delete and the load the target is empty, and if the database is being served, the application writes into that gap — a row it creates on demand is then a primary-key conflict against the same row arriving from the bundle. With one transaction the concurrent writer waits instead of racing.
2767
+
2768
+ `--no-atomic` opts out, and the trade is real: every write to those tables waits for the load, so on a bundle that takes minutes, so does the wait. On postgres it also re-enables the bulk `COPY` loader, which cannot join a transaction it does not own — an atomic run says so once rather than being quietly slower.
2769
+ - **No ordering can replace the suspension.** MySQL, MariaDB and SQL Server check a foreign key as each *row* is deleted, so a table that references **itself** cannot be emptied at all by ordering tables — the conflict is between two rows of one of them. `createdBy → actors` on the `actors` table is exactly that shape, and it is what an audit mixin on an actor table produces. (Postgres needs no suspension: a multi-table `TRUNCATE` covers the whole set at once.)
2770
+
2771
+ What it will **not** do is reach outside the bundle. If a table the bundle does **not** carry holds rows that reference one it does, `replace` refuses **before deleting anything** and names them:
2772
+
2773
+ ```
2774
+ replace cannot empty this target: 1 table(s) OUTSIDE the bundle hold rows that reference
2775
+ tables INSIDE it. Emptying the bundle's tables would leave those rows pointing at nothing,
2776
+ and the bundle cannot restore them.
2777
+ webhookDeliveries (4021 rows): createdBy → actors, updatedBy → actors
2778
+ Nothing has been deleted. Either re-export with a scope that includes those tables, or use
2779
+ --mode upsert if those rows are meant to survive.
2780
+ ```
2781
+
2782
+ An **empty** table outside the bundle blocks nothing — a schema always carries tables an environment has never written to.
2733
2783
 
2734
- `replace` **refuses a partial bundle** (a subset / tenant / table scope): truncating would delete rows the bundle never carried. Re-export with full scope, or use `upsert`. (`append --on-conflict fail` throws on a duplicate primary key only where the store enforces the constraint every SQL dialect does; the in-memory dev store overwrites.)
2784
+ One consequence of emptying in bulk: the delete itself emits **no change events** (one per row is not affordable at bundle scale). Over `--target api` that is covered — an import through the instance's own process asks every live subscription to re-read once it lands, the same coarse refresh the framework uses after a broadcast gap. Over `--target direct` there is no live instance to tell, which is the whole reason that path is guarded.
2785
+
2786
+ #### Which mode for which job
2787
+
2788
+ | You want | Mode | Why |
2789
+ |---|---|---|
2790
+ | Keep a staging database in step with a production export, re-runnable | `upsert` | Idempotent per row: run it as often as you like, the result is the same. |
2791
+ | Add an event log / new seed data without touching what is there | `append --on-conflict skip` | Insert-only, and a row that already exists is left alone rather than overwritten. |
2792
+ | Catch a duplicate instead of silently skipping it | `append --on-conflict fail` | The import stops and names the row. Use when a duplicate means the bundle is wrong. |
2793
+ | Make a target become **exactly** a bundle — replace an environment | `replace` (full-scope bundle) | Empties the bundle's tables first, so rows the bundle does not carry are gone. |
2794
+ | The same, against a database that is being served right now | `replace --atomic` | One transaction: readers see the old state until commit, the new one after. Never a half-loaded table. |
2795
+ | The same, against a live instance, honouring app invariants | `replace --target api` | Runs in the instance's process: validation, mixins, field encryption, hooks, and live subscriptions re-read afterwards. |
2796
+
2797
+ Two things `replace` will refuse, both before writing anything: a **partial bundle** (subset / tenant / table scope), because emptying would delete rows the bundle never carried; and a target where a table **outside** the bundle holds rows referencing one inside it, because those rows cannot be put back. Both refusals name what to do instead.
2798
+
2799
+ #### Recipe: replace a dev environment from your local database
2800
+
2801
+ ```sh
2802
+ # 1. Full-scope export of the source, locally.
2803
+ voltro data export ./dev-refresh
2804
+
2805
+ # 2. Preview against the target first — every pre-flight runs, nothing is written.
2806
+ voltro data import ./dev-refresh --target api \
2807
+ --api-url https://dev.example --token "$VOLTRO_DATA_TRANSFER_SECRET" \
2808
+ --mode replace --dry-run
2809
+
2810
+ # 3. Do it. A big bundle chunks itself; a failed run can be re-run and resumes.
2811
+ voltro data import ./dev-refresh --target api \
2812
+ --api-url https://dev.example --token "$VOLTRO_DATA_TRANSFER_SECRET" \
2813
+ --mode replace --atomic
2814
+ ```
2815
+
2816
+ If step 3 dies halfway — a dropped connection, a laptop closing — run **exactly the same command again**. The upload continues where it stopped, and the import is a single all-or-nothing step, so the target is either the old state or the new one.
2735
2817
 
2736
2818
  ### Importing into a LIVE instance
2737
2819
 
@@ -2740,10 +2822,10 @@ A plain import connects **straight to the database** (not through the running ap
2740
2822
  | Target | How | Guarantees | Use for |
2741
2823
  |---|---|---|---|
2742
2824
  | **direct** (default) | writes straight to the DB | none while live — **guarded**; pass `--allow-live` to override | a stopped target, a replica, a dev/staging DB not serving traffic |
2743
- | **direct + `--atomic`** | wraps truncate+load in ONE transaction | MVCC readers see the import **all-or-nothing** (old until commit, new after) — the live-safe `replace` | replacing a live target's data with no partial-state window |
2825
+ | **direct + `--atomic`** (the default for `replace`) | wraps empty+load in ONE transaction | MVCC readers see the import **all-or-nothing** (old until commit, new after) — the live-safe `replace` | replacing a live target's data with no partial-state window |
2744
2826
  | **in-process (`--target api`)** | `voltro data import <dir> --target api --api-url <url> --token <secret>` **uploads** the packed bundle (or, with `--bundle-key`, has the instance **pull it from storage**) to the instance's secret-gated admin endpoint, which imports it **in its own process** through its store | full app pipeline — validation, mixins, **field encryption**, hooks — AND automatic reactivity (in-process writes emit change events, so subscriptions update; no separate resync) | a live merge (incl. PROD) that must honour app invariants |
2745
2827
 
2746
- `--atomic` is the recommended live `replace`: one transaction, so live reads never see a half-loaded table. (It holds a write transaction for the load duration — writes to those tables block, readers don't. A shadow-table rename would shorten that lock window, but for a full replace concurrent writes are discarded on swap anyway, so it isn't the default.)
2828
+ `--atomic` is what `replace` does by default (see above): one transaction, so live reads never see a half-loaded table. (It holds a write transaction for the load duration — writes to those tables block, readers don't. A shadow-table rename would shorten that lock window, but for a full replace concurrent writes are discarded on swap anyway, so it isn't the default.)
2747
2829
 
2748
2830
  ### The `--target api` data-transfer endpoints (prod-safe)
2749
2831
 
@@ -2753,6 +2835,49 @@ A plain import connects **straight to the database** (not through the running ap
2753
2835
  - **Secret gate, constant-time.** Every request must present that secret as a **Bearer token** (`Authorization: Bearer <secret>`), compared in constant time over SHA-256 digests (neither length nor content leaks via timing). The gate is a framework-owned secret **independent of app RBAC** — enabling data transfer can never accidentally ride on a user role. The CLI sends it via `--token` / `VOLTRO_DATA_TRANSFER_SECRET` (it must equal the server's secret). Wrong/absent token → `401`.
2754
2836
  - **One secret, both directions.** The same secret gates export (read) and import (write). If you need to grant export without import (e.g. a backup job that must never overwrite), split it into a per-operation credential deliberately — the default is one credential for the whole surface.
2755
2837
 
2838
+ #### A big bundle goes up in chunks, and resumes (`--chunk-size`)
2839
+
2840
+ A bundle bigger than one chunk is uploaded as a **series of short requests** instead of one long one, and the switch is automatic: the packer's stream is buffered one chunk ahead, so a bundle that fits inside that buffer is sent exactly as before — one request, no protocol — and a bigger one chunks itself. **You never have to know in advance which table is the big one.**
2841
+
2842
+ Each chunk is its own request, so a proxy body cap or an ingress read timeout has nothing large to choke on. The **import still runs once**, at the end, over the whole bundle — same modes, same deferred-FK repair, same all-or-nothing emptying. Only the transport changes.
2843
+
2844
+ **Resume is real.** The upload id is derived from the bundle itself, so re-running the same command after a failure asks the instance how far it got and continues from there:
2845
+
2846
+ ```
2847
+ api import: resuming a chunked upload the instance already holds { bytes: 50331648 }
2848
+ api import: uploaded in chunks { chunks: 7, bytes: 62914560, resumedFrom: 50331648 }
2849
+ ```
2850
+
2851
+ That is safe because `packBundle` over a bundle directory is byte-identical across runs, and because each chunk carries the bundle's **key** (the hash of its manifest). Uploading a *different* bundle under the same id is refused rather than spliced into the partial one, and a chunk that does not start exactly where the instance left off is refused with the offset it does expect.
2852
+
2853
+ `--chunk-size <mb>` overrides the default of 16 MiB. Unfinished uploads are discarded by the instance after 24 hours.
2854
+
2855
+ ##### The chunk protocol, for when you are reading proxy logs
2856
+
2857
+ Worth knowing if an ingress sits in the way, because these requests are what it will show you. All of them are `POST` to the same `/_voltro/admin/import` path, gated by the same Bearer secret:
2858
+
2859
+ | Request | Headers | Answer |
2860
+ |---|---|---|
2861
+ | probe | `x-import-upload-id`, `x-import-probe: 1`, empty body | `200 {uploadId, bytes}` — how much the instance already holds |
2862
+ | chunk | `x-import-upload-id`, `x-import-upload-key`, `x-import-chunk-offset` | `202 {uploadId, bytes}` — accepted, nothing imported yet |
2863
+ | final chunk | the same plus `x-import-chunk-final: 1` | `200` with the import's own result — this is the long one |
2864
+
2865
+ A `409` means the instance refused the chunk and says which of three things happened: `offset-mismatch` (with the `expectedOffset` to continue from), `key-mismatch` (a different bundle under this upload id — use a new id), or `bad-id`. None of them is retryable by simply repeating the request, which is why each one names the fix.
2866
+
2867
+ Only the FINAL request runs the import, so only that one is long. If it is the request your proxy times out on, that is the one to raise `proxy_read_timeout` for — or use `--bundle-key` and have the instance pull the archive from object storage instead of receiving it.
2868
+
2869
+ #### How long an `--target api` call may take (`--timeout`)
2870
+
2871
+ Both api-target calls **wait as long as the instance needs**. There is no default deadline, and that is deliberate: the response arrives only when the *import* (or export) has finished, so any fixed bound is really a bound on the size of your database. A full bundle of a grown database routinely takes longer than five minutes to apply.
2872
+
2873
+ Pass `--timeout <seconds>` when you want one. If it is hit, the message says what it means — the upload finished long ago and **the instance is very probably still importing**:
2874
+
2875
+ ```
2876
+ voltro data import ./out --target api --api-url https://api.example --token $SECRET --timeout 900
2877
+ ```
2878
+
2879
+ 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.
2880
+
2756
2881
  **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:
2757
2882
  - **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.
2758
2883
  - **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.
@@ -2817,6 +2942,17 @@ Net: interrupt an export or import at any point — network drop, `Ctrl-C`, OOM-
2817
2942
 
2818
2943
  The `--target api` path is unaffected: the instance unpacks each upload into a fresh temp directory, so it never carries a ledger between runs.
2819
2944
 
2945
+ ### What the summary line counts
2946
+
2947
+ `import complete … rows: N` counts the rows **this run wrote**, not the rows the bundle carries. The two differ more often than you would think, and the case where they differ most used to read as a success:
2948
+
2949
+ ```
2950
+ import complete — 0 rows written, every table was already applied by an earlier run
2951
+ of this bundle directory { rows: 0, carried: 242950, skippedTables: 114, skippedRows: 242950 }
2952
+ ```
2953
+
2954
+ The resume ledger lives **inside the bundle directory**, so copying a bundle copies its ledger, and the copy then imports nothing — correctly, and with a warning that says so and names the file to delete. But the warning is not the last line, and an operator piping the output through `tail -1` sees only the last line. So the last line now tells the truth on its own: `rows` is what was written, `carried` is what the bundle holds, and `skippedRows` is what an earlier run had already applied.
2955
+
2820
2956
  ### Progress & observability
2821
2957
 
2822
2958
  A multi-hour job is not a black box. Both pipelines signal per **table** — never per row, so the reporting never slows the hot path:
@@ -3128,6 +3264,10 @@ flag is the one-off override.
3128
3264
  | GDPR / per-tenant takeout | `voltro data export --tenant <id> --assets` |
3129
3265
  | A small self-consistent slice of the data | `voltro data export --profile <env>` with a `subset` |
3130
3266
  | Move Postgres → MySQL | `voltro data export` then `import` (cross-dialect lint applies) |
3267
+ | Replace a dev/staging environment with your local state | `voltro data import <dir> --target api --mode replace --atomic` |
3268
+ | Seed a fresh cluster through the running app (invariants + encryption) | `voltro data import <dir> --target api` (default `upsert`) |
3269
+ | Move a bundle too big for one request | nothing extra — the upload chunks itself; `--chunk-size` only if you need a different size |
3270
+ | Resume an upload that died | re-run the identical command |
3131
3271
 
3132
3272
 
3133
3273
 
@@ -1004,6 +1004,18 @@ The usual cause on MariaDB is a **UNIQUE constraint on an unbounded text column*
1004
1004
 
1005
1005
  The reader finds such tables when CDC starts, reports each once, and **excludes** it — so there is no reconnect loop. Cross-instance change events for that table are lost; own-node reactivity is unaffected, because writes still emit inline.
1006
1006
 
1007
+ **How long that verdict lasts.** It is a reading of the schema at the moment the reader attached, and applying the remedy does not lift it by itself. `voltro dev` builds its store before it migrates — kv, cross-replica broadcast and the analytics mirror all need one — so on a boot whose own migration bounds the column, the finding is a second older than the fix. That boot therefore **re-checks the exclusion set once all its schema work is done and re-admits the table in the same run**:
1008
+
1009
+ ```
1010
+ cdc: table 'sessions' looks undecodable … Not a verdict yet; the definitive line follows.
1011
+ auto-migrate: applied 74 op(s) in 1157ms
1012
+ cdc: table 'sessions' re-admitted to binlog capture — the hidden-column condition is gone.
1013
+ ```
1014
+
1015
+ The re-check runs in both directions: schema work that *creates* an unbounded unique on a captured table excludes it there and then, rather than after the reader has failed on three writes.
1016
+
1017
+ `voltro serve` has no equivalent step and needs none — it builds its store *after* every schema step and never applies DDL itself, so its reading at attach is already the final one. Everywhere else the exclusion holds until the process restarts.
1018
+
1007
1019
  **The remedy is to bound the column:**
1008
1020
 
1009
1021
  ```ts
@@ -134,6 +134,8 @@ Version numbers are 1-based in **both** timings, so switching `timing` does not
134
134
 
135
135
  **The price is not optional.** That guarantee is only obtainable by being willing to REFUSE. When the history insert fails — disk, lock timeout, constraint, dropped connection — a transaction offers exactly two outcomes: the mutation fails with it, or the error is swallowed and the change commits without its entry, which is post-commit's hole with the cost already paid. There is no third option, so a rare, explained rejection is the shape of the guarantee rather than a defect.
136
136
 
137
+ **The refusal takes the row with it.** When the trail's insert fails, the write it covers is rolled back — including a write made OUTSIDE any transaction of your own. That has not always been true: the row's statement committed on its own and the trail ran as a second statement afterwards, so a failing trail left a committed row behind a write that reported failure. Anything that retried that write then met its own row and reported a duplicate key for a row nobody wrote twice. A table with recorders is written inside a transaction now, on every SQL dialect, so "the mutation fails with it" means what it says.
138
+
137
139
  **Why the default is still `'post-commit'`.** In-transaction makes `_voltro_row_history` a hard dependency of every write path it covers: its availability becomes your write path's availability, and every covered write holds its locks longer. Post-commit loses at worst *one entry*; in-transaction can, at worst, stop writes to the covered tables entirely. For a compliance trail the second trade is the right one — for the undo / time-travel use this plugin also serves, it is not.
138
140
 
139
141
  ### Under CDC, and inside a transaction
@@ -1,4 +1,4 @@
1
- # What's new in 0.43.1
1
+ # What's new in 0.44.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,67 +7,32 @@ 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
- ### Fixed
11
-
12
- - **@voltro/database** — A write recorder that fails inside someone's transaction now says which recorder, on which write, and what the database actually said.
13
-
14
- `@effect/sql` renders every driver failure as `SqlError: Failed to execute statement` — one sentence that fits a missing column, a dangling foreign key, an over-long value and a duplicate key equally. The driver's own words hang off a SYMBOL on a `FiberFailure`, so a caller who reaches for `.cause` gets `undefined` and concludes there is nothing there.
15
-
16
- That is expensive precisely where recorders run: the caller's write was ordinary, and what failed was framework machinery one table over. The message now reads
17
-
18
- ```
19
- write recorder '_voltro_row_history' failed while recording an update on 'users':
20
- Duplicate entry 'rowver_…' for key 'PRIMARY' [code=ER_DUP_ENTRY errno=1062 …]
21
- ```
22
-
23
- and the original error is kept as `cause` for anyone who does walk the chain. The failure still takes the transaction down — that is the guarantee and it is unchanged.
24
-
25
- `describeDriverError` (`@voltro/database`) is the shared summariser, built on the existing cause extractor rather than a second walker. It returns nothing when the chain carries nothing driver-shaped, so an ordinary programming error from a recorder arrives as itself instead of wrapped in prose about a database.
26
- - **@voltro/database** — On MariaDB, a `varchar` column could introspect as `json` because a DIFFERENT table had a json column with the same column name.
10
+ ### ⚠ BREAKING
27
11
 
28
- MariaDB names a column-level CHECK after the COLUMN, and those names are unique per table, not per schema. `information_schema.check_constraints` on MySQL has no `TABLE_NAME`, so the introspector recovered it by joining `table_constraints` on `(schema, constraint_name)` which on MariaDB cross-products every same-named check across every table. Measured on 11.8:
12
+ - **@voltro/data-transfer, @voltro/cli** `runImport` returns an `ImportOutcome` instead of the bundle's `Manifest`, and `--mode replace` runs as ONE transaction by default.
29
13
 
30
- ```
31
- a.payload LONGTEXT CHECK (json_valid(`payload`))
32
- b.payload VARCHAR(255) CHECK (`payload` in ('x','y'))
33
-
34
- join result: a → json_valid, a → in(…), b → json_valid, b → in(…)
35
- ```
14
+ **Why the return type changed.** The summary line counted the rows the BUNDLE carries, not the rows the run wrote. Those differ most exactly where it matters: a bundle directory carries its own resume ledger, so a copied directory imports nothing — correctly, with a warning naming the file to delete — and the run then printed `import complete … rows: 242950` over a target it had not touched. The warning was one line above, which is one line too far for anyone piping the output through `tail -1`. The outcome carries `rowsWritten`, `rowsSkipped` and `fullyResumed`, the CLI reports written-vs-carried, and a fully-skipped run says so on its LAST line.
36
15
 
37
- So `b.payload` reads as `json`, and `a.payload` picks up an enum it does not have. Downstream that is not a cosmetic label: the planner emits a blocked `alter-column-type` with `from: 'json'` that no `.narrowedFrom()` can honestly acknowledge, because the premise is false — and since the data-transfer manifest records introspected types, the same misreading travels into the bundle and reappears as schema drift on import.
16
+ Migration: `runImport(...)` now resolves to `{ manifest, tablesWritten, rowsWritten, tablesSkipped, rowsSkipped, fullyResumed }`. Read `.manifest` where you read the manifest before.
38
17
 
39
- MariaDB's own `check_constraints` HAS `TABLE_NAME`. The introspector asks for it directly now and keeps the join as the MySQL path, where check-constraint names are schema-unique and the join is sound. That asymmetry is why a single-engine test could not see this: the wrong query passes on MySQL.
40
- - **@voltro/database** — `voltro db apply` could not drop a CHECK constraint on the mysql family at all, and each of the three reasons hid the next.
18
+ **Why replace is atomic now.** The all-or-nothing guarantee was written for the emptying step, and read reasonably as covering the run. A replace that died partway through the LOAD left the target emptied of its old rows and holding part of the new ones, measured on a live instance over sixteen minutes. There is no useful state for a replace to stop in, so it is a default rather than a flag you have to know about. It also closes the window that produced that failure: with the tables emptied and the load uncommitted, a concurrent writer in the application waits instead of inserting a row the bundle is about to insert too.
41
19
 
42
- **1. `DROP CHECK` is MySQL-8 syntax.** MariaDB has never had it measured on 11.8, `ALTER TABLE t DROP CHECK c` is `ERROR 1064`, while `DROP CONSTRAINT c` works on both engines. A plan containing a `drop-check` therefore died on the first one, on a family whose migrations are NOT atomic: the run stopped with the earlier statements committed and no rollback.
20
+ `--no-atomic` (CLI) / `atomic: false` (API) opts out. The trade is stated where it bites: every write to those tables waits for the load, and on postgres the bulk `COPY` loader cannot join a transaction it does not own an atomic run now says that once rather than being quietly slower.
43
21
 
44
- **2. The name was assumed, not read.** The applier dropped `<table>_<column>_check` which is only what its own `add-check` would have named it. A CHECK created at table bring-up is INLINE and UNNAMED, so the server names it (`CONSTRAINT_1` / `<table>_chk_1` / the column name). Dropping a name that does not exist reports "does not exist", which is indistinguishable from the "already gone" a resume legitimately produces so the statement succeeded, the constraint stayed, and the plan re-proposed the identical `drop-check` forever. The name comes from the catalog now.
22
+ Two smaller things from the same report: `--mode replace` against a running instance warns about the empty-target window when it is NOT atomic, and the `--target api` upload reports progress per chunk plus a line explaining that the final request stays open for the whole import sixteen minutes of silence is indistinguishable from a hang, and one operator killed a run that had finished.
45
23
 
46
- **3. A column-level CHECK cannot be dropped by name on MariaDB at all.** Measured: the catalog lists it under the column's name, `DROP CONSTRAINT` on that name answers 1091, and only redefining the column removes it. The applier now drops by catalog name, ASKS whether the constraint survived, and redefines the column when it did so a rebuild happens only in the case that needs one.
24
+ **`voltro update` carries you across this**codemod `0.44.0/01_import-outcome`.
47
25
 
48
- **And the same investigation closed the MySQL `.oneOf()` round-trip gap.** `parseEnumCheck` was documented as handling MySQL's rendering and did not: MySQL backslash-escapes the string DELIMITERS (`_utf8mb4\'draft\'`), and the parser rewrote those to the SQL doubling `''` — which is how a quote INSIDE a value is written. Every delimiter became escaped content, every value came back empty, and the filter dropped them. `.oneOf()` now round-trips on MySQL, and the suite that asserted the gap as a known one asserts the round-trip instead.
49
-
50
- Plus, on a failed apply: the error now states how many operations were already applied and whether this dialect rolls back. The ledger held that number; it never reached the operator, who had to re-plan and diff the counts to learn how far the run got.
51
- - **@voltro/plugin-versioning** — `versioningPlugin({ timing: 'in-transaction' })` built its history row's primary key from `(rowId, version)` while the version counter three lines above was scoped to `(tableName, rowId)`. Two versioned tables carrying the same row id therefore collided — permanently.
52
-
53
- The shape is not exotic: `actors.id === users.id` is what the framework's own audit trail asks for, an `actors` row whose id is the user's so an audited write satisfies `createdBy → actors`. In an app that follows it, every user row has a twin.
54
-
55
- The collision does not heal, and that is what turns a duplicate into an outage. The second table's insert fails, so its history row is never written, so `maxOf` for that table stays `null`, so the next attempt computes the same version and the same id. Every write to that row is dead from then on — surfacing as `ER_DUP_ENTRY` on an ordinary `store.update`, naming a row id in a table the caller never wrote to.
56
-
57
- The key is `(table, rowId, version)` now — the same shape the post-commit path always built. It stays deterministic (no clock, no process-local counter), which is what lets it survive a replica restart; it just carries every part of the key it claims to be unique over.
58
-
59
- **No cleanup is needed for rows already written.** They keep their old ids and belong to whichever table wrote them; the new keys cannot collide with them, and `byRow` is not unique. An app blocked by this is unblocked by the upgrade alone.
60
-
61
- Covered twice: the recorder against a port that refuses duplicates (the mechanism, including that a repeat does not settle), and two versioned tables sharing an id against live postgres (the real primary key, inside the caller's transaction). The suite that existed exercised ONE table, which cannot produce a collision at all — and read exactly like a suite that covered this.
26
+ ### Fixed
62
27
 
63
- ### Internal (no consumer-facing effect)
28
+ - **@voltro/sql-mysql, @voltro/sql-postgres, @voltro/sql-sqlite, @voltro/sql-mssql, @voltro/data-transfer** — A write and its write-recorders now succeed or fail together on every SQL dialect, and the data importer's retry is idempotent.
64
29
 
65
- - **@voltro/sql-mysql** Two test-only defects in `sql-mysql`, both found by a release gate, both of the same family: a check that could not fail, and a failure reported in the wrong place. No product code changed.
30
+ A recorder (a versioning trail, an audit log) runs on the caller's connection and is ALLOWED to fail — a recorder that throws must take the write down with it, that is its contract. Outside a caller transaction the two were not one unit: the row's statement committed on its own, and the recorder's INSERT ran afterwards as a second autocommit statement. So a recorder that threw left a COMMITTED row behind a write that reported failure. Measured directly: `insert` throws, the row is in the table, and a second attempt at the same row is `ER_DUP_ENTRY` on PRIMARY.
66
31
 
67
- **An assertion that could not fail.** `dropCheckSyntax.integration.test.ts` fell back to a HAND-BUILT plan when the planner produced no operations and the fabricated operation was a `drop-check`, which is exactly what the next line asserts the plan contains. So an engine whose planner stopped emitting it would have been handed one and reported green. The fallback is deleted; both engines produce the operation now, which is what this release fixed, and the assertion is load-bearing again (4/4 on mysql AND mariadb without it).
32
+ Anything that retries a failed write then meets its own row. The data importer retries by designit holds a row whose write failed and tries again once the remaining tables have streamed so a `--mode replace` that had just emptied a table failed on a duplicate key IN that table. From outside, that is the impossible-looking thing: an import that emptied a table and then failed because something was already in it. Reproduced verbatim, including the table name and `[ER_DUP_ENTRY/1062]`, by putting the old retry back.
68
33
 
69
- It surfaced as a TYPE error rather than a false pass, because the fallback's object widened `plan` into a union `applyPlan` does not accept. Worth noting which check caught it: `vitest` transpiles without type-checking, so the suite was green and only `tsc` objected — the gate's `typecheck` and `lint` steps are what went red.
34
+ Both ends are closed, and they are independent on purpose:
70
35
 
71
- **A wait that gave up in silence.** `waitFor` in both binlog CDC suites looped to a deadline and then RETURNED, so a "prove the reader is live" wait that expired let the test carry on, kill the binlog dump thread, and fail twenty lines later on `expect(ids).toContain('todo_wd_before')` an assertion about a different claim, in a different place. It throws now, naming the wait and the window, and all twelve call sites carry a label.
36
+ - **The cause.** A table that HAS recorders writes inside a transaction now, so the row and the trail commit together or not at all — on mysql, mariadb, postgres, sqlite and mssql, verified by one suite that asks all five the same question. A table with no recorders — the default takes the direct path unchanged; `recordsTable` is a Map-size check first, so it costs one comparison. - **The defence.** The importer's retry upserts instead of inserting in `replace` mode. That covers every OTHER way a write can land while reporting failure: a driver timeout on a write the server applied, a connection lost after the commit, a concurrent writer inserting the same key. It is sound precisely because the table was emptied by this same run there is nothing in it that is not ours.
72
37
 
73
- The window is named too: `FIRST_ATTACH_MS = 30_000`, up from 12 s. The reasoning is the 40 s window already in the same file, whose comment says a re-attach plus binlog catch-up takes longer under a loaded full-suite run — a FIRST attach does both and only skips the backoff, so 12 s beside 40 s was an asymmetry the file's own reasoning did not support. That is an argument from the neighbouring comment, not a measurement; if it expires again, `waitFor` now says which wait and for how long, and that number is the one to argue with rather than raising this one twice.
38
+ The test that pinned the divergence went red when the fix landed, exactly as its own note said it would, and is inverted with that note kept.
@@ -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.43.1",
16
- "@voltro/cli": "0.43.1",
17
- "@voltro/database": "0.43.1",
18
- "@voltro/env": "0.43.1",
19
- "@voltro/protocol": "0.43.1",
20
- "@voltro/runtime": "0.43.1",
15
+ "@voltro/ai": "0.44.0",
16
+ "@voltro/cli": "0.44.0",
17
+ "@voltro/database": "0.44.0",
18
+ "@voltro/env": "0.44.0",
19
+ "@voltro/protocol": "0.44.0",
20
+ "@voltro/runtime": "0.44.0",
21
21
  "effect": "^3.22.0"
22
22
  },
23
23
  "devDependencies": {
24
- "@voltro/testing": "0.43.1",
24
+ "@voltro/testing": "0.44.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.43.1",
17
- "@voltro/database": "0.43.1",
18
- "@voltro/env": "0.43.1",
19
- "@voltro/plugin-auth": "0.43.1",
20
- "@voltro/protocol": "0.43.1",
21
- "@voltro/runtime": "0.43.1",
22
- "@voltro/sql-postgres": "0.43.1",
16
+ "@voltro/cli": "0.44.0",
17
+ "@voltro/database": "0.44.0",
18
+ "@voltro/env": "0.44.0",
19
+ "@voltro/plugin-auth": "0.44.0",
20
+ "@voltro/protocol": "0.44.0",
21
+ "@voltro/runtime": "0.44.0",
22
+ "@voltro/sql-postgres": "0.44.0",
23
23
  "effect": "^3.22.0"
24
24
  },
25
25
  "devDependencies": {
26
- "@voltro/testing": "0.43.1",
26
+ "@voltro/testing": "0.44.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.43.1",
20
- "@voltro/database": "0.43.1",
21
- "@voltro/env": "0.43.1",
22
- "@voltro/plugin-multitenancy": "0.43.1",
23
- "@voltro/protocol": "0.43.1",
24
- "@voltro/runtime": "0.43.1",
19
+ "@voltro/cli": "0.44.0",
20
+ "@voltro/database": "0.44.0",
21
+ "@voltro/env": "0.44.0",
22
+ "@voltro/plugin-multitenancy": "0.44.0",
23
+ "@voltro/protocol": "0.44.0",
24
+ "@voltro/runtime": "0.44.0",
25
25
  "effect": "^3.22.0"
26
26
  },
27
27
  "devDependencies": {
28
- "@voltro/testing": "0.43.1",
28
+ "@voltro/testing": "0.44.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.43.1",
17
- "@voltro/database": "0.43.1",
18
- "@voltro/env": "0.43.1",
19
- "@voltro/plugin-deactivation": "0.43.1",
20
- "@voltro/protocol": "0.43.1",
21
- "@voltro/runtime": "0.43.1",
16
+ "@voltro/cli": "0.44.0",
17
+ "@voltro/database": "0.44.0",
18
+ "@voltro/env": "0.44.0",
19
+ "@voltro/plugin-deactivation": "0.44.0",
20
+ "@voltro/protocol": "0.44.0",
21
+ "@voltro/runtime": "0.44.0",
22
22
  "effect": "^3.22.0"
23
23
  },
24
24
  "devDependencies": {
25
- "@voltro/testing": "0.43.1",
25
+ "@voltro/testing": "0.44.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.43.1",
17
- "@voltro/database": "0.43.1",
18
- "@voltro/env": "0.43.1",
19
- "@voltro/plugin-mail": "0.43.1",
20
- "@voltro/plugin-multitenancy": "0.43.1",
21
- "@voltro/protocol": "0.43.1",
22
- "@voltro/runtime": "0.43.1",
16
+ "@voltro/cli": "0.44.0",
17
+ "@voltro/database": "0.44.0",
18
+ "@voltro/env": "0.44.0",
19
+ "@voltro/plugin-mail": "0.44.0",
20
+ "@voltro/plugin-multitenancy": "0.44.0",
21
+ "@voltro/protocol": "0.44.0",
22
+ "@voltro/runtime": "0.44.0",
23
23
  "effect": "^3.22.0",
24
24
  "react": "^19.0.0"
25
25
  },
26
26
  "devDependencies": {
27
- "@voltro/testing": "0.43.1",
27
+ "@voltro/testing": "0.44.0",
28
28
  "typescript": "^6.0.3",
29
29
  "@vitest/coverage-v8": "^4.1.10",
30
30
  "vitest": "^4.1.10"