@voltro/cli 0.20.1 → 0.21.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 (64) hide show
  1. package/CHANGELOG.md +171 -0
  2. package/THIRD-PARTY-NOTICES.md +61 -61
  3. package/dist/{apiBuild-qZBEu59d.js → apiBuild-BHOtsQkp.js} +93 -80
  4. package/dist/apiBuild-K7M6Rt0U.js +2 -0
  5. package/dist/bin.js +41 -20
  6. package/dist/{commands-CA7jwejs.js → commands-BZoimvvS.js} +2752 -2633
  7. package/dist/{dbCommand-DFw2UoC-.js → dbCommand-CDBbZ-Ac.js} +26 -12
  8. package/dist/dbCommand-oADAj6ez.js +2 -0
  9. package/dist/dev-CZQ73Yoh.js +3 -0
  10. package/dist/{dev-OHLsAq4K.js → dev-DKb92NYX.js} +1537 -1527
  11. package/dist/index.js +1 -1
  12. package/dist/{inspectMetrics-DvPNXmGA.js → inspectMetrics-DMjWBQif.js} +576 -567
  13. package/dist/serveCommand-qHJCs0cX.js +1162 -0
  14. package/dist/serveEntry.js +2 -2
  15. package/dist/{start-CXQ7WL1W.js → start-B1x6Z60h.js} +389 -389
  16. package/dist/startEntry.js +2 -2
  17. package/package.json +27 -27
  18. package/templates/AGENTS.md +1 -1
  19. package/templates/agent-docs/_index.md +1 -1
  20. package/templates/agent-docs/cli.md +63 -0
  21. package/templates/agent-docs/database/migrations.md +33 -11
  22. package/templates/agent-docs/database/schema.md +7 -0
  23. package/templates/agent-docs/database/seedsdialects.md +3 -3
  24. package/templates/agent-docs/whats-new.md +93 -81
  25. package/templates/apps/api-ai/package.json +12 -12
  26. package/templates/apps/api-auth/package.json +13 -13
  27. package/templates/apps/api-backend/package.json +12 -12
  28. package/templates/apps/api-backend-deactivation/package.json +12 -12
  29. package/templates/apps/api-backend-mail/package.json +11 -11
  30. package/templates/apps/api-backend-mariadb/package.json +14 -14
  31. package/templates/apps/api-backend-storage/package.json +11 -11
  32. package/templates/apps/api-data-advanced/package.json +13 -13
  33. package/templates/apps/api-durable/package.json +13 -13
  34. package/templates/apps/api-feature-flags/package.json +14 -14
  35. package/templates/apps/api-governance/package.json +13 -13
  36. package/templates/apps/api-kv/package.json +13 -13
  37. package/templates/apps/api-moderation/package.json +13 -13
  38. package/templates/apps/api-observability/package.json +13 -13
  39. package/templates/apps/api-ratelimit/package.json +13 -13
  40. package/templates/apps/api-rbac/package.json +13 -13
  41. package/templates/apps/api-rest/package.json +12 -12
  42. package/templates/apps/api-saas/package.json +16 -16
  43. package/templates/apps/api-search/package.json +13 -13
  44. package/templates/apps/api-versioning/package.json +13 -13
  45. package/templates/apps/api-webhooks/package.json +14 -14
  46. package/templates/apps/changelog/package.json +8 -8
  47. package/templates/apps/edge-functions/package.json +5 -5
  48. package/templates/apps/frontend-admin/package.json +10 -10
  49. package/templates/apps/frontend-app/package.json +10 -10
  50. package/templates/apps/frontend-blank/package.json +9 -9
  51. package/templates/apps/frontend-contact/package.json +11 -11
  52. package/templates/apps/frontend-dashboard/package.json +9 -9
  53. package/templates/apps/frontend-docs/package.json +9 -9
  54. package/templates/apps/frontend-i18n/package.json +8 -8
  55. package/templates/apps/frontend-landing/package.json +9 -9
  56. package/templates/apps/frontend-spa/package.json +9 -9
  57. package/templates/apps/frontend-ssr/package.json +9 -9
  58. package/templates/apps/frontend-ssr-api/package.json +10 -10
  59. package/templates/apps/frontend-static-blog/package.json +8 -8
  60. package/dist/apiBuild-C9aHz4Yh.js +0 -2
  61. package/dist/dbCommand-DA_RFj5K.js +0 -2
  62. package/dist/dev-2AvdzDg2.js +0 -3
  63. package/dist/serveCommand-BKqTKWTX.js +0 -1136
  64. /package/templates/patches/{@effect__cluster@0.59.0.patch → @effect__cluster@0.60.0.patch} +0 -0
@@ -1,4 +1,4 @@
1
- # What's new in 0.20.1
1
+ # What's new in 0.21.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,135 +7,147 @@ 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
- ### Changed
10
+ ### ⚠ BREAKING
11
11
 
12
- - **@voltro/database, @voltro/runtime, @voltro/plugin-versioning, @voltro/plugin-presence, @voltro/voltro** — Five framework-table indexes were holding GENERIC names in a namespace that is shared with your tables. Index names are unique per SCHEMA on every supported dialect, so `_voltro_row_history.index('byTrace')` reserved `byTrace` for the whole database and `byTrace` is the first thing anyone reaches for when indexing a `traceId`. A consumer added `traceId` to their own audit table, indexed it the obvious way, and collided with ours; the framework's own error message even suggested renaming the framework's index as the fix.
12
+ - **@voltro/database, @voltro/runtime, @voltro/workflow, @voltro/voltro** — `text().maxLength(n)` on an EXISTING column now actually applies. It planned zero operations and reported "schema is up to date" while the live column stayed `longtext` the documented remedy for MariaDB's hash long-unique was a silent no-op, which is worse than no remedy because you stop looking.
13
13
 
14
- Renamed: `_voltro_row_history` `byTrace` `byRowHistoryTrace`, `bySubject` `byRowHistorySubject`; `_voltro_api_keys` `byTenant` `byApiKeyTenant`; `_voltro_presence` `byChannel` `byPresenceChannel`; `_voltro_connections` `bySubject` `byConnectionSubject`. These are `_voltro_*` tables, so the rename rides the declarative differ on `voltro db apply` / boot no codemod. Adopters see a one-time index rebuild.
14
+ **The differ was not comparing lengths wrongly it could not see them.** `maxLength` was absent from the schema snapshot entirely: it lived on the column definition, was read only when rendering CREATE DDL, and never reached the comparison. A consumer pinned the mechanism with a contrast: a column bounded AT CREATION was `varchar(64)` (DDL path, fine); one bounded afterwards stayed `longtext` (diff path, blind).
15
15
 
16
- A test now enforces the rule that most framework tables already followed: a framework index name must MENTION its own table. Mechanical, so it cannot rot the way a curated list of "generic" names would, and it does not demand the full `_voltro_<table>_<name>` form — which would force renaming ~20 already-safe indexes for no benefit. It also asserts no two framework tables claim the same index name, since installing two such plugins together would fail at migrate time for a reason neither plugin's author could see.
16
+ It is now carried on both sides declared from the definition, live from introspection compared as its own dimension, and rendered by the applier from the declared snapshot (a bare `text` tag would emit DDL that applies successfully and changes nothing, the silent no-op the applier's convergence check exists to catch).
17
17
 
18
- ### Fixed
18
+ **Three guards against the real risk, which is not missing a change but re-emitting one forever:**
19
+
20
+ - Only the VARCHAR family contributes a live length. MariaDB reports `character_maximum_length = 4294967295` for `longtext` and 65535 for `text`; postgres reports NULL. Reading a type's theoretical maximum would make a declared `text()` differ from its own live column on every boot. - Only `text()` columns are compared. `id()` renders as `VARCHAR(64)` on mysql/mariadb while its declaration carries no length — measured on live MariaDB while building this, and it would have emitted an ALTER for every id column, forever. - Never on sqlite (no length-enforced type), and never when the caller omitted the dialect — an unknown dialect behaves exactly as before this field existed.
21
+
22
+ Proven by round-trip suites against live MariaDB and live Postgres: the same schema re-plans to NOTHING, a length change produces exactly one operation on the right column, applying it lands the new width, and re-planning after that is clean.
23
+
24
+ Classification follows the nullability precedent: **widening is `safe`** (no data can be lost), **narrowing is `needs-backfill`** and says so, with the count query to run first. Narrowing is deliberately not blocked — blocking it would leave the remedy just as unusable as the silence did.
25
+
26
+ **Also: `_voltro_api_keys.hashedKey` is now bounded at 64**, since the value is `sha256Hex(token)` and narrowing it can never fail. The other four unbounded unique columns in framework tables are deliberately left alone, each with the reason at the column: `_voltro_kv.key` is the caller's own key, `idempotencyKey` comes from a user-supplied function, and the two workflow `executionId`s have no shape the framework guarantees. A narrowing ALTER that fails on existing data during a framework upgrade is a worse outcome than the index-size concern it would fix — and the MariaDB hash long-unique is harmless on those tables anyway, since `_voltro_*` is filtered out of the binlog reader's include list.
27
+
28
+ **Migration** — `voltro update` prints it (`0.21.0/01_maxlength-now-migrates`, `manual`, and it fires only for projects that declare a bound). Your source does not change; every `.maxLength(n)` already written keeps compiling. What changes is that the next `voltro db apply` — or a `voltro dev` / `voltro serve` boot with auto-migrate — emits ALTERs it used to skip. Run `voltro db plan` first: it prints exactly which columns would be altered without touching anything, and an empty plan means this does not affect you. Widening is `safe` and can simply run; narrowing is `needs-backfill` and the plan carries the count query to run before it.
29
+
30
+ *Why this is `BREAKING` and not `Fixed`: the API is compatible — nothing is removed, renamed or narrowed, and the same call compiles. But an upgrade now performs DDL against YOUR tables that the previous version silently skipped, and on MariaDB `longtext → varchar(n)` is a full table rebuild that locks. The DB-changes-need-no-codemod exemption is written for `_voltro_*` tables riding the differ; this reaches user tables, so the operator deserves the warning at `voltro update` time rather than in a changelog section they may never open.*
31
+
32
+ ### Added
33
+
34
+ - **@voltro/cli, @voltro/voltro** — **`voltro schedule run <name>`** fires one scheduled job on demand, against `voltro dev` or `voltro serve`.
35
+
36
+ Asked for by a consumer whose nightly jobs correct business data and whose workaround was: edit the cron expression to a minute out, wait for the reload, put it back. "Run it once now and watch" is a normal thing to want.
19
37
 
20
- - **@voltro/sql-mysql, @voltro/voltro** A MariaDB table with a UNIQUE constraint on an UNBOUNDED text column can never be decoded from the binlog. The reader now says so ONCE with the real cause and a remedy that worksand excludes the table, instead of looping on it forever.
38
+ **Most of it already existed, and that is why it took a measurement to find the gap.** `SchedulerHandle.fireNow` has been there, and so has `POST /_voltro/inspect/schedules/:name/fire`. What was missing was the way in: no CLI verb, and the part that mattered**`voltro serve` mounted no inspect surface at all**. Every `/_voltro/inspect/*` route existed only under `voltro dev`, which is the one place a nightly data-correcting job is not running.
21
39
 
22
- **The mechanism.** MariaDB backs an unbounded UNIQUE with a **HASH long-unique index**, which adds a hidden `DB_ROW_HASH_n` column to the InnoDB row. That column IS in the binlog row image and is NOT in `information_schema.COLUMNS`, so the reader compares N+1 against N and throws on every write to that table:
40
+ So production now mounts it. Two things make that safe rather than a new attack surface, and both were checked rather than assumed:
23
41
 
24
- ```text
25
- Table app.sessions schema changed between binlog event and metadata fetch:
26
- the event has 9 columns, fetched metadata has 8
27
- ```
42
+ - `handleInspectRequest` is **closed by default**: with no `VOLTRO_INSPECT_TOKEN` every request is 401 carrying the remedy, the compare is constant-time, and the token is never minted outside dev. Opening it is an operator's deliberate act. - Only the handlers production can answer TRUTHFULLY are wired. Everything else stays absent and replies "not configured" — an empty array would claim the app has no procedures. `inspectSchedules` is deliberately still absent: dev computes `nextFiringAt` and the EFFECTIVE coordination from its own boot closure, and reporting a guessed coordination mode to a post-deploy gate is worse than reporting nothing.
28
43
 
29
- Nothing is broken; the table is shaped that way, permanently. The previous recovery (skip to the current binlog end) recovered nothing, because the end is exactly where the next failing write appears — a loop a consumer measured at roughly every 9 seconds, re-signalling resync to the whole fleet each pass.
44
+ The manifest's `rpc` / `workflows` entries come from a builder both boot paths now share (`inspectEntries.ts`). They are the same facts on both sides, and a second hand-written copy of the descriptor→entry mapping is the shape this repo keeps paying for.
30
45
 
31
- **The cause we shipped in the previous entry was WRONG, and this retracts it.** It blamed a `DROP COLUMN` that ran as `ALGORITHM=INSTANT` leaving a phantom column, and told people to run `ALTER TABLE … FORCE`. The same consumer measured that: 9 InnoDB columns before the rebuild, 9 after, hidden column still present the rebuild recreates the index and therefore recreates the hidden column. The repair line sent readers in a circle. They also disproved the version theory, being on the same MariaDB 11.8 we had tested on and failed to reproduce a phantom column with.
46
+ **A run id of `null` is reported as its own outcome**, not as success: the run was coordinated awayanother replica holds the lock, or `onOverlap: 'skip'` found the previous run still going. Printing "ok" would claim work that never started.
32
47
 
33
- **Now:** affected tables are found at CDC start by a privilege-free probe the direct evidence in `INNODB_SYS_COLUMNS` needs `PROCESS`, which an app DB user does not have, so the constraint SHAPE is inferred from `information_schema.STATISTICS` + `COLUMNS` instead reported once as an error naming `text().maxLength(n)` as the remedy and `ALTER TABLE FORCE` as explicitly not one, and EXCLUDED from the reader.
48
+ **Also fixed, and it affects every command in the inspect family.** `fetchJson` collapsed a non-2xx into the body's `error` field alone, discarding `message`. The surface answers `{ error: <category>, message: <what happened> }`, so firing an unknown schedule printed "fire failed" while the server had said `scheduler.fireNow: unknown schedule "…"`, and a closed surface printed "unauthorized" while the body named the missing env var. It now prefers `message`, then `reason`, then `error` fixed in the shared fetch rather than per command.
34
49
 
35
- Excluding is what makes it converge, and that is measured rather than assumed: an excluded table with a hidden hash column produces no reader error at all, while the same table included throws on the first write. Cross-instance change events for such a table are lost until it is bounded; own-node reactivity is unaffected (writes still emit inline).
50
+ Verified end to end against `voltro-starter/apps/v-api-durable`: the fire returns a run id and the job's own output appears in the server log; an unknown name reports the server's reason; `--format json` round-trips.
36
51
 
37
- Framework `_voltro_*` tables cannot hit this — they are filtered out of the reader's include list before it reaches the replication client, and exclusion demonstrably shields the metadata fetch.
52
+ ### Fixed
53
+
54
+ - **@voltro/database, @voltro/sql-postgres, @voltro/sql-mysql, @voltro/sql-sqlite, @voltro/sql-mssql, @voltro/voltro** — <!-- apiSurface: compatible — reasoned, not rubber-stamped. `attributionFields()` gained an OPTIONAL parameter and `withCapturedAttribution` is new. Both are additive, and it was CHECKED rather than assumed, because this repo has already paid once for a narrowing that read as additive: a zero-arg call still compiles, the value is still assignable to the old `() => …` type, and it still passes as a callback typed with the old shape. All three probed under `--strict`. Nothing was removed. -->
55
+
56
+ Write attribution is now CARRIED down the write path instead of re-read from the ambient async-local scope, so a connection-pool handoff can no longer strand a write's `traceId` / `subjectId`.
57
+
58
+ `routeEvent` read the identity with `attributionFields()`, and it runs after `await runPromise(...)`. `AsyncLocalStorage` propagates through continuations the current context CREATES; one scheduled by ANOTHER context — exactly what a pool handoff does when an acquisition queues — resumes with that other context's store. So under contention the write landed with the identity absent. Absent is a LEGAL value there meaning "no request behind this write", so the result does not look like a defect: it looks like a schedule. In a compliance trail that asymmetry is the whole problem, and it is why this is closed structurally rather than left as unlikely.
59
+
60
+ The value is captured ONCE at each public store method, before any await (`withCapturedAttribution`), and threaded explicitly through every `execute*` and into `routeEvent` — in all four dialect stores, including the transactional view, which is the path every framework mutation takes. The ambient scope is kept as a FALLBACK: a site that has not been threaded behaves exactly as before rather than worse, which is what made the change verifiable site by site.
38
61
 
39
- **Caveat worth reading if you are already affected:** on a table that ALREADY exists, adding `.maxLength(n)` currently changes nothing the schema differ does not diff text length, so it plans 0 operations and reports "up to date". That is a separate defect, reported in the same round and not yet fixed; until it is, the remedy only applies to newly created tables.
40
- - **@voltro/cli, @voltro/voltro** — `voltro codegen` no longer writes a silently plugin-less `rpcGroup.generated.ts`, and it now reports what it merged.
62
+ **On what is and is not proven.** A pool handoff cannot be reproduced deterministically from a test the resume context is the driver's choice. Two attempts are worth recording because both produced misleading green: a load-based regression test passed in isolation and failed in the full gate twice (a coin flip that also blocked releases), and a "deterministic" replacement that left the scope before the write finished PASSED against the unthreaded store, because the store re-enters its own scope internally. It proved nothing while looking like proof, so it was deleted.
41
63
 
42
- `loadApiConfig` swallows every failure into `null`, and `config?.plugins ?? []` turned that into "this app has no plugins". So an `app.config.ts` that threw while importing produced a generated file with **no plugin error union and no plugin routes** followed by `voltro codegen: wrote rpcGroup.generated.ts`. The file typechecks, so nothing downstream catches it; the only symptom is a client branching on an error tag that never arrives.
64
+ What is proven: the pure semantics (`writeAttributionCapture.test.ts` explicit wins over the ambient scope, explicit wins over ANOTHER request's scope, "no request" stays "no request", keys omitted rather than `undefined`), and the threading itself (`attributionThreadingParity.test.ts` every store accepts and uses the carried value, no store still calls a bare `attributionFields()`, every transactional view carries it). The guarantee is structural, and it is stated that way rather than dressed up as a reproduction.
43
65
 
44
- A consumer with ~140 declarative `guards:` measured that file 2781 lines shorter after a version bump, with the `ScopeError` import and the whole `__voltroPluginErrors` union gone. For the record, since they were careful to separate measurement from conclusion: the generator did NOT drop the feature — the plugin-codegen path is byte-identical between 0.19.0 and 0.20.0, and the published `@voltro/cli@0.20.0` does contain the identifier they grepped for. Their `grep` came back empty because the bundled chunk contained a literal NUL byte, which makes a file binary to most search tools (fixed separately, and it had been hiding files from our own audits too). What was real is the artefact diff, and this is the path that produces it without a word.
66
+ Raised by a consumer who could NOT reproduce the loss across 2700 writes at 96-way concurrency with every core saturated, and who asked for the fix anyway on the right grounds: *"impossible beats unlikely when the failure is invisible."*
67
+ - **@voltro/database, @voltro/sql-postgres, @voltro/sql-mysql, @voltro/voltro** — <!-- apiSurface: compatible — `beginLocalWrite`, `endLocalWrite` and `resolveEchoAttribution` are new exports on `@voltro/database`; nothing was removed, renamed or narrowed. Checked rather than assumed, because this repo has already paid once for a narrowing that read as additive. -->
45
68
 
46
- Now: a config that EXISTS but fails to load is a refusal with a non-zero exit and the underlying cause, not a quiet downgrade. An app with no `app.config.ts` at all still generates absence is legitimate, failure is not. And every run prints `(plugins N, error schemas N, plugin routes N)`, because a count that drops from 7 to 0 has to be visible in the success line or the next occurrence is found the same way: by diffing artefacts during a debugging session.
69
+ Under CDC, a plain `store.insert(...)` could deliver its change with `traceId` and `subjectId` ABSENT — not always, and more often the busier the process.
47
70
 
48
- `loadApiConfigDiagnosed` is the new seam (`{ config, present, error }`); `loadApiConfig` is unchanged for every existing caller.
49
- - **@voltro/cli, @voltro/voltro** — `ssr cold-compile` log lines now carry the compile's duration, and `voltro start` emits them at all.
71
+ **The registration was racing the echo, and only winning on a margin.** The write path registers the request identity so the transport echo (a postgres NOTIFY, a mysql binlog row) can be re-united with it, and `pendingAttribution.ts` stated the ordering as a guarantee: the registration happens "BEFORE the transport can possibly echo it, because a NOTIFY fires at COMMIT". That is true INSIDE a transaction, where `routeEvent` runs before COMMIT. It is false for a plain write, whose statement commits ITSELF — the trigger fires while the write path is still awaiting the driver, and the registration lands afterwards. The echo has to make a round trip through the LISTEN/binlog connection, and that round trip was the only thing keeping the order right.
50
72
 
51
- The lines had a `start` and an `end` and no timing, which looks readable and is not: cold compiles run concurrently up to `VOLTRO_DEV_SSR_COMPILE_CONCURRENCY`, so the pairs INTERLEAVE. Subtracting adjacent timestamps names the wrong module, and above a limit of two they cannot be paired by eye at all which is what a user reading a pod log actually hit, with three `start` lines before their `end`s:
73
+ **Why it stayed hidden.** An unattributed event is a LEGAL event meaning "no request behind this write", so a lost identity is indistinguishable from a background job there is no error, no warning, and nothing that looks wrong in a trace. It surfaced as one failing assertion in the full test suite, which is the only place this machine is loaded enough to flip the order, and it passed on every isolated re-run.
52
74
 
53
- ```
54
- …:59.704 ssr cold-compile start id=…/layout.tsx
55
- …:59.704 ssr cold-compile start id=…/(main)/layout.tsx
56
- …:03.447 ssr cold-compile end 3743ms id=…/layout.tsx
57
- ```
75
+ **Closed by construction, not by widening the margin.** A store now brackets each non-transactional write (`beginLocalWrite` / `endLocalWrite`) and delivers echoes through `resolveEchoAttribution`. An echo arriving while a write to that table is mid-registration is HELD until that write has had its chance, then answered. Held echoes keep arrival order — a subscriber seeing an update before the insert it updates would be worse off than one missing a `traceId` — and a write that never registers releases the table after a bound rather than parking its stream.
58
76
 
59
- The gate had the number for free and threw it away. It is measured INSIDE the concurrency permit, so it is the module's own compile cost rather than the time it spent queued behind the limit — those are different numbers and only one of them is a property of the module. A slow first paint is usually one slow module, and this is the line that names it.
77
+ Three narrowings, each deliberate:
60
78
 
61
- A failed compile now says `FAILED` instead of `end`. Without that, a 3.7-second line for a module that threw read exactly like a slow but successful compile.
79
+ - **Only under `cdc`.** Nothing is injected in `inline` mode, so there is no echo to order against. - **Only non-transactional writes.** `transactional()` registers before COMMIT already; bracketing it would hold OTHER replicas' echoes for the length of the transaction to fix a race that path does not have. Asserted, so the narrowing is on the record rather than something a later reader "fixes". - **A remote write is still never attributed.** The barrier may delay an answer; it must never invent one. Nothing is registered locally for another replica's write, and that stays true while echoes are held.
62
80
 
63
- `voltro start`'s middleware fallback constructed the same gate with NO callbacks, so an on-demand compile there produced no line whatsoever; it is wired now.
64
- - **@voltro/cli, @voltro/voltro** — `voltro db plans`, `db drift` and `db restore-snapshot` worked on postgres only. On mysql/mariadb (and mssql and sqlite) all three died with:
81
+ **Both CDC stores had a hand-written copy of the claim-and-emit tail**, which is the shape of the previous three attribution defects in this package. The decision now lives in one function both call, and `echoBarrierParity.test.ts` fails if either store claims for itself, leaves a public write outside the bracket, or brackets the transactional path. The ordering itself is proven against the primitive (`pendingAttribution.test.ts`) — a live suite cannot force it, because the resume point is the driver's choice, and the four barrier tests were verified to go red against the pre-fix behaviour before being trusted.
65
82
 
66
- ```text
67
- fatal unhandled cli error (FiberFailure) SqlError: Failed to execute statement
68
- ```
83
+ Also fixed: `mysql`'s `updateMany` / `deleteMany` route their events after their transaction commits, so they carried the same race despite not going through the per-row write path. They are bracketed too.
84
+ - **@voltro/cli, @voltro/voltro** `voltro build` could fail to build the **start bundle** with `Could not resolve "@voltro/cli/startEntry"`, degrading `voltro start` to the slower per-module boot. The serve bundle carried the identical latent failure.
69
85
 
70
- The cause is three `${sql('col')}::text AS ${sql('col')}` castsPOSTGRES syntax, in read paths whose helper is still called `buildPgLayer`. `db apply`, which WRITES the same ledger table, has no cast and worked, which is exactly the split a consumer reported: the commands that read were broken, the one that writes was fine.
86
+ Both bundles generate an entry importing a narrow CLI export and build it with `absWorkingDir: <app root>` so esbuild resolved that bare specifier from the APP's `node_modules`. Under strict pnpm the app has `@voltro/cli` there only if it DECLARES it, and an app normally depends on `voltro` / `@voltro/web` and gets the CLI transitively. The CLI now resolves its own entry from `import.meta.url` and hands esbuild an absolute alias.
71
87
 
72
- The casts existed to stop a driver handing back a `jsonb` object or a `Date`. Normalising in JS gets the same result and cannot be dialect-specific, since drivers differ in whether a json column arrives parsed and whether a timestamp arrives as a `Date`.
88
+ Same shape as the tsx bug (`tsxLoader.ts`): a package that is OUR dependency, resolved from the user's directory, invisible under strict pnpm. Same answer the CLI knows where it lives, so it stops asking the app.
73
89
 
74
- Worth naming what it cost: `db drift` is the command whose whole job is "alert if live diverged from declared", and the consumer who found this had live divergence at the time. The specific detector and the general one were blind together.
90
+ **The published export was not the problem.** It is present in the tarball checked against the real 0.20.1 and 0.20.2 packages, `./startEntry` `./dist/startEntry.js`, file included. Only resolution failed, which is why re-adding the export would have changed nothing.
75
91
 
76
- **And the error now names the failing statement.** Their verdict was the actionable part of the report:
92
+ This monorepo hoists everything, so the bare specifier resolves here and the build passes with or without the alias. The test therefore asserts the alias directly rather than inferring it from a green build — the hoisted layout is exactly what hid the strict-pnpm failure in the first place.
77
93
 
78
- > *"the error names no statement the statement text (or even the operation name) > would turn this from a dead end into a bug report. We would have sent you the > failing SQL if the error had contained it."*
94
+ **Also pinned, after a wrong turn worth recording.** A consumer reported that the framework provides no Suspense boundary, so any suspend during SSR throws. The obvious repair a root `<Suspense>` was implemented, measured, and REVERTED:
79
95
 
80
- Right twice over they could not diagnose it, and neither could we from the report; it took reading our own source. `@effect/sql`'s `SqlError` carries the driver error in `cause`, and every supported driver puts the useful part there (mysql2: `code`, `errno`, `sqlState`, `sqlMessage`, usually `sql`; pg: `code`, `detail`, `hint`, `position`). The CLI's fatal reporter printed only the wrapper. It now walks the cause chain and prints the driver message, the codes and the statement — collapsed to one line, and saying `<not attached by the driver>` when there genuinely is none, because that is information too.
96
+ - With a root boundary, a page that THROWS answered **200** with `<template data-msg="Switched to client rendering">`. React downgrades an errored boundary to client rendering, which silently undid the hard failure shipped moments earlier. - Without one, a suspending page renders fine anyway: `renderToPipeableStream` treats the root as an implicit boundary, so a suspend delays the shell flush rather than failing.
81
97
 
82
- Shape-based rather than `instanceof SqlError`, deliberately: the CLI catches errors that have crossed the serve/start bundle boundary, where two copies of `@effect/sql` make `instanceof` silently false the failure mode this repo has already paid for elsewhere.
83
- - **@voltro/cli, @voltro/voltro** — `voltro doctor`'s `plaintext-secret` rule no longer flags metadata ABOUT a credential. An audit row denormalising the public facts of an api key — `apiKeyId`, `apiKeyKeyId`, `apiKeyType`, `apiKeyOwnerId`, `apiKeyName` had three columns already excluded by the `*Id` suffix, while `apiKeyType` and `apiKeyName` fired. Telling a team to encrypt the LABEL of a credential is how a rule earns being ignored.
98
+ So the reported problem does not exist on the streaming path, and the obvious fix for it breaks something that does. Both halves are now fixtures with assertions side by side (`ssr-suspends` must render, `ssr-throws` must 500) — adding a root boundary flips the second, and that pair is what makes it visible instead of shipping it. Docs corrected in both languages, including the two places a suspend genuinely is unsupported (`renderToString` behind static prerender, and the client render), neither of which is the framework's choice.
99
+ - **@voltro/cli, @voltro/voltro** — <!-- apiSurface: compatible — `CliRuntime` keeps its exported signature and its behaviour (NodeContext + logger); only `runCli`'s internal composition changed. `loggerConfig` is module-private. -->
84
100
 
85
- The exclusion now covers final words that cannot BE the credential — `Name`, `Type`, `Kind`, `Label`, `Prefix`, `Suffix`, `Status`, `State`, `Scope(s)`, `Version`, `Count`, `Provider`, `Format`, `Note`/`Description`/`Comment`, plus the existing `Id` and the hash family. Deliberately NOT on the list: `Value`, `Secret`, `Token`, `Key`, `Password` — the words that name the thing itself. A false negative from an over-wide list is silent, so that is the failure mode the list is built against, and a test pins the words that must still fire.
86
- - **@voltro/database, @voltro/cli, @voltro/voltro** — `voltro doctor` no longer contradicts itself about the `.serverOnly()` wire audit. The same command on the same tree reported:
101
+ Every one-shot `voltro` command printed each log line TWICE. Measured on `voltro agents-md`: 76 lines for 38 events.
87
102
 
88
- ```
89
- human: serverOnly: NOT CHECKED | json: {'checked': True, 'leaks': 0}
90
- ```
103
+ `runCli` installed two loggers. It provided `CliRuntime` (which contains a `LoggerLayer`) to the program, then provided a second `LoggerLayer` around the `matchCauseEffect` wrapping it — so the program ran inside both. Two `LoggerLayer`s in one fiber do not compose the way the name suggests: `LoggerLayer` is `Logger.replace(Logger.defaultLogger, …)`, which removes the DEFAULT logger and adds its own. The second one finds no default left to remove, the removal is a no-op, and the add still happens. Replace composes as replace only against the default — never against another replace.
91
104
 
92
- Two causes, both fixed. `registerRelations` refused a re-registration of the IDENTICAL relation object, so a process that executes a module twice looked like two conflicting declarations — it now mirrors `registerTable`'s `existing === table` tolerance (a DIFFERENT block claiming the same name still throws). And doctor loaded the app three times per run; it now loads once, so every report sees the same outcome instead of the first one succeeding and the next failing.
105
+ The duplicates were distinguishable only because the outer layer was built without the command's `defaultScope`, so half the output carried `scope` and half did not. Had both been configured identically the output would have been byte-identical pairs, which is a good deal harder to notice than a stray field.
93
106
 
94
- The consequence was worse than the noise: the throw aborted the wire audit, so the check that a token cannot reach a client had not run since the reporting app adopted the marker and a CI gate written exactly as we documented (`fail on serverOnly.checked === false`) reported green on an app where the audit provably had not run. That is the "reads as coverage without being coverage" failure the `serverOnly` field was added to remove, reappearing in the field added to prevent it.
95
- - **@voltro/sql-mysql, @voltro/voltro** — `insertIgnore` on MariaDB no longer reports a cause it cannot know, and no longer turns a REJECTED write into a silent "conflict". `INSERT IGNORE` downgrades EVERY error to a warning — foreign key, NOT NULL, CHECK, truncation — so the post-check's premise ("the insert was skipped ⇒ a unique constraint fired") does not hold on this dialect. It asserted a second unique index that did not exist; the real cause was an FK (an auto-stamped `createdBy` with no matching `actors` row), and a consumer spent the diagnosis looking for a phantom index.
107
+ **The failure branch is the half that survives a partial fix**, and it did. Moving the second layer from around `matchCauseEffect` onto the error handler repairs the success path and leaves the failure path doubling, because that branch runs while the program's scope is still open and INHERITS its logger. Verified by measuring three shapes rather than reasoning about scopes: handler-provides 2 lines, handler-inherits 1, provide-once-outermost 1.
96
108
 
97
- The message now reads the real error from `SHOW WARNINGS` on the same connection — BEFORE the existing-row lookup, since that lookup is itself a statement and resets the warning list. A non-duplicate warning is reported as a rejection and throws, because returning there is data loss presented as a normal outcome: the row is not written and the caller is told it already was. A genuine duplicate on an unnamed constraint now names the constraint that fired. Outside a transaction the warning cannot be attributed to our own statement (each statement acquires from the pool independently), so the message says the constraint is unknown rather than guessing framework mutations are auto-transactional, so the common path has the cause.
98
- - **@voltro/logger, @voltro/cli, @voltro/database, @voltro/voltro** — `voltro doctor --json` and `voltro capabilities --json` now emit exactly one JSON document on stdout. A `log.warn` from module discovery landed there ahead of it, so:
109
+ The last is what shipped. The logger is a FiberRef, not a service the program requires, so it is provided ONCE at the outermost boundary and the program gets only `NodeContext`. That covers both branches by construction, rather than by the handler happening to still be inside a scope that has not closed yet. `CliRuntime` is unchanged and still used by `runCliMain`, which provides it once and never had the problem.
99
110
 
100
- ```console
101
- $ voltro doctor --json 2>/dev/null | python3 -c 'import json,sys; json.load(sys.stdin)'
102
- JSONDecodeError: Extra data: line 2 column 1
103
- ```
111
+ Guarded by `cliRuntime.test.ts`, which asserts the COUNT (any second provision doubles it regardless of what it logs) and that every line carries the command scope. It was checked against the reverted fix in both of its shapes before being trusted. Its capture spies on stdout AND stderr, deliberately: diagnostics route to stderr by level, and a stdout-only capture reported zero lines for the failure path — reading as "nothing was logged" when the truth was "logged on the other stream", which had the test accusing the fix it was written to protect.
112
+ - **@voltro/cli, @voltro/voltro** Three corrections to the `db drift` baseline shipped in 0.20.2, all reported by the consumer who verified the fix — and all of them defects in that fix rather than in older code.
104
113
 
105
- Note the `2>/dev/null` in that repro stderr was already redirected, so there was no shell-side workaround. And it only happened when a warning fired, so a consumer's CI parsed the document correctly until one file out of 368 tripped one. That is the same failure the `serverOnly.checked` field was added to remove an automat unable to separate the normal case from the special case one layer out, in the surface added to fix it.
114
+ **1. The first `db drift` after upgrading CRASHED.** `liveFingerprint` is a new column and does not exist until a `db apply` adds it, so naming it in the ledger read died on `SqlError: Failed to execute statement` instead of reaching the "no baseline yet" branch written for exactly that moment. The documented sequence was `0 → apply → clean`; the real one was `crash apply clean`, with the crash landing in the first CI run after an upgrade. Proven by dropping and re-adding the column: absent exit 1 and a driver error, present → exit 0 and the honest message. The read is now `SELECT *`, which cannot go stale against an older ledger.
106
115
 
107
- A command that owns stdout for machine output now calls `claimStdoutForJson()` before doing any work that could log, and every record goes to stderr from then on. The stream decision itself moved into ONE place (`@voltro/logger`'s `stream.ts`, exported as `routeDiagnosticsToStderr`): the Effect surface and the direct surface each carried their own copy of `level === 'error' ? stderr : stdout`, and two copies of one rule is how the rule failed to change.
116
+ **2. A no-op `db apply` established no baseline.** The baseline is written per applied plan row, so an already-current schema produced none and "cannot compare" then persisted indefinitely rather than for one run, for any app whose schema was current when it upgraded. A no-op apply now backfills the latest row's `liveFingerprint` instead of inserting a history entry for a migration that did not happen.
108
117
 
109
- **Also fixed, same report:** the warning that started it was itself wrong. A `*.relations.ts` whose `relations(...)` map is EMPTY was reported as *"no relations(...) export found"*pointing the reader at a missing export that is right there. `isRelationsSpec` rejects an empty map (correctly there is nothing to register), but the caller could not tell that apart from a module with no export at all. It now says the map is empty and names the export.
110
- - **@voltro/cli, @voltro/voltro** — The SSR bundle build now externalises a bare specifier it cannot resolve instead of aborting, so an uninstalled OPTIONAL peer no longer makes `voltro build` impossible.
118
+ **3. "No baseline" gets its own exit code: 3.** Previously it exited 0, so a CI gate could not distinguish "compared and matched" from "did not compare" — and on a stable schema the second could persist forever. The consumer named that as their reason for NOT adding a drift gate: it would pass vacuously, which is the failure this whole thread is about. Now `0` = matched, `3` = no baseline, `4` = diverged.
111
119
 
112
- The SSR step runs with `ssr: { noExternal: true }` inlining everything is what lets a production web image ship without a framework dependency tree and that left no escape for a package that cannot be resolved at all. The commonest such package is an optional native peer reached through a library's Node entry:
120
+ **And the SQL-error reporter added in the same release did not work on this path.** It walked `.cause`, and an `Effect` `FiberFailure` has none its cause hides behind `Symbol(effect/Runtime/FiberFailure/Cause)`, with only `stack`, `message` and `name` as own keys. So the helper returned `undefined` and not even its "no statement attached" fallback fired. The consumer reproduced that against an empty database and checked field by field; all absent.
113
121
 
114
- ```
115
- Rolldown failed to resolve import "canvas"
116
- from ".../konva/lib/index-node.js"
117
- ```
122
+ The reason the tests missed it is worth recording: every fixture was a hand-built object WITH a `.cause` — the shape assumed, not the shape the runtime produces. The suite now builds a real `FiberFailure` through `Effect.runPromise`, and the walk unwraps the symbol and flattens the `Cause` tree (`Fail`/`Die`/`Sequential`).
118
123
 
119
- `konva`'s `main` is its Node build, which requires the optional native `canvas`; its `browser` field points at one that does not. An app that never renders to a canvas server-side has nothing to install.
124
+ Their note on the irony is fair and is the reason this is one entry rather than two: defect 1 above IS the "next DB-shaped error in your CI" that the reporter existed to make readable, and it arrived as a bare wrapper plus a driver stack. With the statement printed it would have named the missing column immediately.
120
125
 
121
- A consumer measured that there was no way out from their side either, and each measurement is worth keeping: the import was ALREADY dynamic (rolldown must still resolve it to form the chunk), `renderMode: 'spa'` does not help (`.framework/app.tsx` imports every page statically for the router, so the module is in the SSR graph whatever the render mode), and an `ssr.external` passthrough in `app.config.ts` is not read. So `voltro build` and with it the production image was unavailable for that app.
126
+ **A FOURTH copy of both defects was found in `voltro dev`'s migrations inspect endpoint, and it was the worst one.** It carried the same postgres-only `::text` casts, wrapped in `orElseSucceed(() => [])` so on every non-postgres dialect the syntax error became an EMPTY history rather than a failure: the devtools migrations panel showed nothing, and with no history row the drift verdict came out `false`. A silent, permanent "no drift" on every mysql/mariadb/mssql/sqlite app. It also compared the declared hash against a live one, exactly like the CLI did.
122
127
 
123
- The api serve bundle and the web start bundle already did exactly this; that plugin is esbuild's and this step is vite/rolldown, so it is the same probe behind a different interface. Framework packages (`@voltro/*`, `@effect/*`, `effect`) are never externalised, so the "needs nothing from node_modules" property still holds.
128
+ Both are fixed there too, and `ledgerReadPortability.test.ts` now fails if any query touching `_voltro_migration_plans` grows a `::type` cast again. Three copies were fixed in one change and the fourth was missed in the same change, which is the argument for the test rather than another paragraph in a maintainer note.
129
+ - **@voltro/cli, @voltro/voltro** — `voltro dev` served a client-only shell — with a **200** — whenever a `renderMode: 'ssr'` page failed to render on the server. It now answers 500 with the cause, exactly as `voltro start` does.
124
130
 
125
- Every externalised specifier is NAMED in the `SSR bundle ready` line. Externalising is right for an uninstalled optional peer and wrong for a genuine missing dependency it trades a loud build failure for a quiet runtime one and only the reader can tell which, so it is reported rather than swallowed.
126
- - **@voltro/sql-mysql, @voltro/sql-sqlite, @voltro/sql-mssql, @voltro/sql-postgres, @voltro/database, @voltro/voltro** — A typed error thrown inside a mutation now reaches the client TYPED, on every dialect. It arrived as an untagged `Die` defect on mysql/mariadb, sqlite and mssql: `transactional()` settled its program with `runPromise`, which rejects with Effect's `FiberFailure` wrapper, and the wrapper copies `message` and a decorated `name` but nothing else — no `_tag`, no payload, no prototype. So the rpc encoder could not match the failure against the mutation descriptor's `error:` union:
131
+ **Reported as "voltro dev does not SSR". It does**, and has since before 0.20.0 — the middleware, its intent stated in a comment ("mirroring what `voltro start` does in production"), is an ancestor of every 0.20.x tag. What the reporter saw was the masking: their pages suspended during the server render (a lazily-loaded i18n catalog above any Suspense boundary), all 225 degraded to Vite's SPA shell, and an empty `<div id="root">` is indistinguishable from a framework that never server-renders. Their conclusion was the only one the evidence supported.
127
132
 
128
- ```
129
- └─ ["error"] └─ ["_tag"] └─ is missing
130
- Expected never, actual (FiberFailure) NotFoundError: …
131
- ```
133
+ **The 200 is the part that mattered.** The same render is a hard 500 under `voltro start`, so those pages were down in production while dev reported success — the inverse of the usual "works in dev, breaks in prod", and worse, because nothing prompts you to look. Measured before the fix: `HTTP 200`, no `x-voltro-rendered-by` header at all, and the thrown error present in the dev log but nowhere in the response.
132
134
 
133
- Framework mutations are auto-transactional, so this was EVERY typed mutation error in an app. Nothing failed `defineMutation({ error: })` compiled, the client's type still said `NotFoundError`, and the `error._tag === 'NotFoundError'` branch was simply never taken at runtime. Actions, which are not auto-transactional, marshalled correctly the whole time, which is what made the transaction the discriminator. A hand-rolled error class lost its fields and its `instanceof` too; only `message` survived, which is why a workaround built on `error.message` looked like it worked and hid this.
135
+ Three sites did this (both streaming `onShellError` handlers and the outer catch); all three now fail through one helper. The pattern was already in the file the `isDeferralNotSupported` branch refuses rather than degrades and says why in the same words ("falling through would leave the developer with an unstyled page and a log line, which is exactly the silent degradation the hard error exists to prevent"). One of the removed fallbacks sat directly under a comment stating that falling through would mask the bug.
134
136
 
135
- Postgres already had the unwrap, with a comment describing this exact consequence, and the three sibling dialects kept the broken call so the fix is now one shared `settleTransactionExit` in `@voltro/database` that all four import, plus a parity test that fails if any store's `transactional()` reaches `runtime.runPromise` again. Reported by a consumer on MariaDB who verified it against 0.19.0 too, so it is not a 0.20.0 regression.
137
+ Dev puts the cause and stack in the response body; production keeps its bare `server error`, so a stack never reaches a public response. That is a difference in what the failure says, never in whether it fails.
138
+
139
+ **Also fixed, found while reproducing it: a page added while `voltro dev` runs was never server-rendered.** The middleware matched against a route table built once at boot, so a new page missed matching entirely and returned before its module was ever loaded — Vite's SPA shell, 200, and *no log line at all*, because the middleware never ran. The page tree the middleware reads is now refreshed by the same regeneration that rewrites the route table (`dirs` too, or a layout added after boot would be invisible to the spa-shell decision).
140
+
141
+ Both are covered by `webDevSsrLayoutLoader.test.ts` against a real dev server: `/ssr-throws` must answer 500 + `x-voltro-rendered-by: ssr-dev-failed` + the cause and must never contain the empty shell, and a page written while the server runs must be server-rendered without a restart. Each assertion was verified red against its own reverted fix — separately, because the first failure aborts the test and would have left the second unproven.
142
+
143
+ One thing this does NOT change: the framework still provides a Suspense boundary only for its own deferral (`<Await>`), not a blanket one at the root. Code that suspends outside it needs a boundary you mount yourself. That is now documented next to the failure behaviour, since a hard 500 is how you will meet it.
136
144
 
137
145
  ### Internal (no consumer-facing effect)
138
146
 
139
- - **@voltro/runtime, @voltro/database, @voltro/protocol, @voltro/cli, @voltro/plugin-billing, @voltro/plugin-mail, @voltro/plugin-sso-saml, @voltro/plugin-storage** — Fourteen source files carried a LITERAL NUL byte the house idiom for a composite map key, written as the raw character instead of an escape. That makes the file BINARY to every text tool: `grep` skips it entirely and reports nothing, which is indistinguishable from a clean file. It was found because a new guard test scanning for framework index names came back clean on `runtime/src/connectionVault.ts` — 1020 lines that every previous grep-based audit in this repo had also silently skipped, including the one looking for exactly the index name that file declares.
147
+ - **@voltro/cli** — Maintainer notes onlyno shipped behaviour changes.
148
+
149
+ `mssqlClusterPatch.ts`'s header said the `@effect/cluster` patch covers "two mssql-only bugs" (it is four: the `deliver_at` INT-overflow, the MERGE…OUTPUT with correlated sub-SELECTs, `FOR UPDATE`, and `USING (SELECT * FROM (VALUES …))`) and implied that a version bump needs nothing but a re-key, because 0.59.0 → 0.60.0 happened to apply unchanged. On 0.60.2 the same patch fails on 3 of its 6 files — upstream refactored `SqlMessageStorage` and moved the context the hunks match on. A bump can require REGENERATING the patch.
150
+
151
+ The regeneration recipe now lives in `packages/sql-mssql/CLAUDE.md`, together with the two measurements that produced confidently wrong answers while working this out: reading an already-patched `node_modules/.pnpm/*patch_hash=*` copy and concluding upstream had fixed it, and un-patching one of the several installed copies and concluding the patch was not load-bearing. Both look like evidence.
140
152
 
141
- Replaced with the JavaScript escape for U+0000. Identical runtime value, files are text again. No behaviour change.
153
+ Also recorded there: verify by BREAKING it. `git apply --check` proves the patch lands, not that it still fixes anything. Un-patched, the mssql cluster suite fails with `Incorrect syntax near ')'`; patched, 5/5 against the live fixture.
@@ -9,19 +9,19 @@
9
9
  "typecheck": "tsc --noEmit"
10
10
  },
11
11
  "dependencies": {
12
- "@effect/platform": "^0.96.1",
13
- "@effect/rpc": "^0.75.1",
14
- "@voltro/ai": "0.20.1",
15
- "@voltro/cli": "0.20.1",
16
- "@voltro/database": "0.20.1",
17
- "@voltro/env": "0.20.1",
18
- "@voltro/protocol": "0.20.1",
19
- "@voltro/runtime": "0.20.1",
20
- "effect": "^3.21.2"
12
+ "@effect/platform": "^0.97.0",
13
+ "@effect/rpc": "^0.76.0",
14
+ "@voltro/ai": "0.21.0",
15
+ "@voltro/cli": "0.21.0",
16
+ "@voltro/database": "0.21.0",
17
+ "@voltro/env": "0.21.0",
18
+ "@voltro/protocol": "0.21.0",
19
+ "@voltro/runtime": "0.21.0",
20
+ "effect": "^3.22.0"
21
21
  },
22
22
  "devDependencies": {
23
- "@voltro/testing": "0.20.1",
24
- "typescript": "^5.7.0",
25
- "vitest": "^3.0.0"
23
+ "@voltro/testing": "0.21.0",
24
+ "typescript": "^6.0.3",
25
+ "vitest": "^4.1.10"
26
26
  }
27
27
  }
@@ -10,20 +10,20 @@
10
10
  "typecheck": "tsc --noEmit"
11
11
  },
12
12
  "dependencies": {
13
- "@effect/platform": "^0.96.1",
14
- "@effect/rpc": "^0.75.1",
15
- "@voltro/cli": "0.20.1",
16
- "@voltro/database": "0.20.1",
17
- "@voltro/env": "0.20.1",
18
- "@voltro/plugin-auth": "0.20.1",
19
- "@voltro/protocol": "0.20.1",
20
- "@voltro/runtime": "0.20.1",
21
- "@voltro/sql-postgres": "0.20.1",
22
- "effect": "^3.21.2"
13
+ "@effect/platform": "^0.97.0",
14
+ "@effect/rpc": "^0.76.0",
15
+ "@voltro/cli": "0.21.0",
16
+ "@voltro/database": "0.21.0",
17
+ "@voltro/env": "0.21.0",
18
+ "@voltro/plugin-auth": "0.21.0",
19
+ "@voltro/protocol": "0.21.0",
20
+ "@voltro/runtime": "0.21.0",
21
+ "@voltro/sql-postgres": "0.21.0",
22
+ "effect": "^3.22.0"
23
23
  },
24
24
  "devDependencies": {
25
- "@voltro/testing": "0.20.1",
26
- "typescript": "^5.7.0",
27
- "vitest": "^3.0.0"
25
+ "@voltro/testing": "0.21.0",
26
+ "typescript": "^6.0.3",
27
+ "vitest": "^4.1.10"
28
28
  }
29
29
  }
@@ -10,19 +10,19 @@
10
10
  "typecheck": "tsc --noEmit"
11
11
  },
12
12
  "dependencies": {
13
- "@effect/platform": "^0.96.1",
14
- "@effect/rpc": "^0.75.1",
15
- "@voltro/cli": "0.20.1",
16
- "@voltro/database": "0.20.1",
17
- "@voltro/env": "0.20.1",
18
- "@voltro/plugin-multitenancy": "0.20.1",
19
- "@voltro/protocol": "0.20.1",
20
- "@voltro/runtime": "0.20.1",
21
- "effect": "^3.21.2"
13
+ "@effect/platform": "^0.97.0",
14
+ "@effect/rpc": "^0.76.0",
15
+ "@voltro/cli": "0.21.0",
16
+ "@voltro/database": "0.21.0",
17
+ "@voltro/env": "0.21.0",
18
+ "@voltro/plugin-multitenancy": "0.21.0",
19
+ "@voltro/protocol": "0.21.0",
20
+ "@voltro/runtime": "0.21.0",
21
+ "effect": "^3.22.0"
22
22
  },
23
23
  "devDependencies": {
24
- "@voltro/testing": "0.20.1",
25
- "typescript": "^5.7.0",
26
- "vitest": "^3.0.0"
24
+ "@voltro/testing": "0.21.0",
25
+ "typescript": "^6.0.3",
26
+ "vitest": "^4.1.10"
27
27
  }
28
28
  }
@@ -10,19 +10,19 @@
10
10
  "typecheck": "tsc --noEmit"
11
11
  },
12
12
  "dependencies": {
13
- "@effect/platform": "^0.96.1",
14
- "@effect/rpc": "^0.75.1",
15
- "@voltro/cli": "0.20.1",
16
- "@voltro/database": "0.20.1",
17
- "@voltro/env": "0.20.1",
18
- "@voltro/plugin-deactivation": "0.20.1",
19
- "@voltro/protocol": "0.20.1",
20
- "@voltro/runtime": "0.20.1",
21
- "effect": "^3.21.2"
13
+ "@effect/platform": "^0.97.0",
14
+ "@effect/rpc": "^0.76.0",
15
+ "@voltro/cli": "0.21.0",
16
+ "@voltro/database": "0.21.0",
17
+ "@voltro/env": "0.21.0",
18
+ "@voltro/plugin-deactivation": "0.21.0",
19
+ "@voltro/protocol": "0.21.0",
20
+ "@voltro/runtime": "0.21.0",
21
+ "effect": "^3.22.0"
22
22
  },
23
23
  "devDependencies": {
24
- "@voltro/testing": "0.20.1",
25
- "typescript": "^5.7.0",
26
- "vitest": "^3.0.0"
24
+ "@voltro/testing": "0.21.0",
25
+ "typescript": "^6.0.3",
26
+ "vitest": "^4.1.10"
27
27
  }
28
28
  }
@@ -12,19 +12,19 @@
12
12
  "dependencies": {
13
13
  "@react-email/components": "^1.0.12",
14
14
  "@react-email/render": "^1.4.0",
15
- "@voltro/cli": "0.20.1",
16
- "@voltro/database": "0.20.1",
17
- "@voltro/env": "0.20.1",
18
- "@voltro/plugin-mail": "0.20.1",
19
- "@voltro/plugin-multitenancy": "0.20.1",
20
- "@voltro/protocol": "0.20.1",
21
- "@voltro/runtime": "0.20.1",
22
- "effect": "^3.21.2",
15
+ "@voltro/cli": "0.21.0",
16
+ "@voltro/database": "0.21.0",
17
+ "@voltro/env": "0.21.0",
18
+ "@voltro/plugin-mail": "0.21.0",
19
+ "@voltro/plugin-multitenancy": "0.21.0",
20
+ "@voltro/protocol": "0.21.0",
21
+ "@voltro/runtime": "0.21.0",
22
+ "effect": "^3.22.0",
23
23
  "react": "^19.0.0"
24
24
  },
25
25
  "devDependencies": {
26
- "@voltro/testing": "0.20.1",
27
- "typescript": "^5.7.0",
28
- "vitest": "^3.0.0"
26
+ "@voltro/testing": "0.21.0",
27
+ "typescript": "^6.0.3",
28
+ "vitest": "^4.1.10"
29
29
  }
30
30
  }
@@ -10,21 +10,21 @@
10
10
  "typecheck": "tsc --noEmit"
11
11
  },
12
12
  "dependencies": {
13
- "@effect/platform": "^0.96.1",
14
- "@effect/rpc": "^0.75.1",
15
- "@voltro/cli": "0.20.1",
16
- "@voltro/database": "0.20.1",
17
- "@voltro/env": "0.20.1",
18
- "@voltro/plugin-multitenancy": "0.20.1",
19
- "@voltro/plugin-storage": "0.20.1",
20
- "@voltro/protocol": "0.20.1",
21
- "@voltro/runtime": "0.20.1",
22
- "@voltro/sql-mysql": "0.20.1",
23
- "effect": "^3.21.2"
13
+ "@effect/platform": "^0.97.0",
14
+ "@effect/rpc": "^0.76.0",
15
+ "@voltro/cli": "0.21.0",
16
+ "@voltro/database": "0.21.0",
17
+ "@voltro/env": "0.21.0",
18
+ "@voltro/plugin-multitenancy": "0.21.0",
19
+ "@voltro/plugin-storage": "0.21.0",
20
+ "@voltro/protocol": "0.21.0",
21
+ "@voltro/runtime": "0.21.0",
22
+ "@voltro/sql-mysql": "0.21.0",
23
+ "effect": "^3.22.0"
24
24
  },
25
25
  "devDependencies": {
26
- "@voltro/testing": "0.20.1",
27
- "typescript": "^5.7.0",
28
- "vitest": "^3.0.0"
26
+ "@voltro/testing": "0.21.0",
27
+ "typescript": "^6.0.3",
28
+ "vitest": "^4.1.10"
29
29
  }
30
30
  }