@voltro/cli 0.19.0 → 0.20.1

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 (62) hide show
  1. package/CHANGELOG.md +224 -0
  2. package/dist/apiBuild-C9aHz4Yh.js +2 -0
  3. package/dist/{apiBuild-CY5pEwwq.js → apiBuild-qZBEu59d.js} +2 -2
  4. package/dist/bin.js +41 -9
  5. package/dist/{commands-BvvoQL0v.js → commands-CA7jwejs.js} +1930 -1770
  6. package/dist/dbCommand-DA_RFj5K.js +2 -0
  7. package/dist/{dbCommand-DJSxnjPt.js → dbCommand-DFw2UoC-.js} +24 -24
  8. package/dist/{dev-C_E1XxNF.js → dev-2AvdzDg2.js} +1 -1
  9. package/dist/{dev-B6rfgKfo.js → dev-OHLsAq4K.js} +1488 -1462
  10. package/dist/index.js +1 -1
  11. package/dist/{inspectMetrics-D1DmLeJs.js → inspectMetrics-DvPNXmGA.js} +208 -179
  12. package/dist/{serveCommand-vPF5ucXC.js → serveCommand-BKqTKWTX.js} +341 -341
  13. package/dist/serveEntry.js +2 -2
  14. package/dist/{start-Clvz4IJb.js → start-CXQ7WL1W.js} +376 -368
  15. package/dist/startEntry.js +2 -2
  16. package/package.json +17 -17
  17. package/templates/AGENTS.md +1 -1
  18. package/templates/agent-docs/_index.md +1 -1
  19. package/templates/agent-docs/cli.md +33 -0
  20. package/templates/agent-docs/database/migrations.md +35 -2
  21. package/templates/agent-docs/database/misc.md +16 -0
  22. package/templates/agent-docs/database/seedsdialects.md +25 -0
  23. package/templates/agent-docs/database/transactions.md +15 -0
  24. package/templates/agent-docs/routing.md +13 -0
  25. package/templates/agent-docs/whats-new.md +78 -94
  26. package/templates/apps/api-ai/package.json +7 -7
  27. package/templates/apps/api-auth/package.json +8 -8
  28. package/templates/apps/api-backend/package.json +7 -7
  29. package/templates/apps/api-backend-deactivation/package.json +7 -7
  30. package/templates/apps/api-backend-mail/package.json +8 -8
  31. package/templates/apps/api-backend-mariadb/package.json +9 -9
  32. package/templates/apps/api-backend-storage/package.json +8 -8
  33. package/templates/apps/api-data-advanced/package.json +8 -8
  34. package/templates/apps/api-durable/package.json +8 -8
  35. package/templates/apps/api-feature-flags/package.json +9 -9
  36. package/templates/apps/api-governance/package.json +8 -8
  37. package/templates/apps/api-kv/package.json +8 -8
  38. package/templates/apps/api-moderation/package.json +8 -8
  39. package/templates/apps/api-observability/package.json +8 -8
  40. package/templates/apps/api-ratelimit/package.json +8 -8
  41. package/templates/apps/api-rbac/package.json +8 -8
  42. package/templates/apps/api-rest/package.json +7 -7
  43. package/templates/apps/api-saas/package.json +11 -11
  44. package/templates/apps/api-search/package.json +8 -8
  45. package/templates/apps/api-versioning/package.json +8 -8
  46. package/templates/apps/api-webhooks/package.json +9 -9
  47. package/templates/apps/changelog/package.json +6 -6
  48. package/templates/apps/edge-functions/package.json +2 -2
  49. package/templates/apps/frontend-admin/package.json +8 -8
  50. package/templates/apps/frontend-app/package.json +8 -8
  51. package/templates/apps/frontend-blank/package.json +7 -7
  52. package/templates/apps/frontend-contact/package.json +7 -7
  53. package/templates/apps/frontend-dashboard/package.json +7 -7
  54. package/templates/apps/frontend-docs/package.json +7 -7
  55. package/templates/apps/frontend-i18n/package.json +6 -6
  56. package/templates/apps/frontend-landing/package.json +7 -7
  57. package/templates/apps/frontend-spa/package.json +7 -7
  58. package/templates/apps/frontend-ssr/package.json +7 -7
  59. package/templates/apps/frontend-ssr-api/package.json +8 -8
  60. package/templates/apps/frontend-static-blog/package.json +6 -6
  61. package/dist/apiBuild-9NXH53Sd.js +0 -2
  62. package/dist/dbCommand-C3R5LrBZ.js +0 -2
@@ -1,3 +1,3 @@
1
- import { Q as e } from "./inspectMetrics-D1DmLeJs.js";
2
- import { t } from "./start-Clvz4IJb.js";
1
+ import { et as e } from "./inspectMetrics-DvPNXmGA.js";
2
+ import { t } from "./start-CXQ7WL1W.js";
3
3
  export { e as loadDotEnv, t as runStartCommand };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voltro/cli",
3
- "version": "0.19.0",
3
+ "version": "0.20.1",
4
4
  "description": "The `voltro` CLI — dev server, codegen, migrations, project scaffolding, agent-docs seeding, and production serve.",
5
5
  "keywords": [
6
6
  "voltro",
@@ -62,22 +62,22 @@
62
62
  "@effect/platform-node": "^0.107.0",
63
63
  "@effect/sql": "^0.51.1",
64
64
  "@effect/workflow": "^0.18.2",
65
- "@voltro/ai": "0.19.0",
66
- "@voltro/cache": "0.19.0",
67
- "@voltro/data-transfer": "0.19.0",
68
- "@voltro/database": "0.19.0",
69
- "@voltro/env": "0.19.0",
70
- "@voltro/kv": "0.19.0",
71
- "@voltro/logger": "0.19.0",
72
- "@voltro/plugin-auth": "0.19.0",
73
- "@voltro/plugin-broadcast": "0.19.0",
74
- "@voltro/plugin-mail": "0.19.0",
75
- "@voltro/plugin-storage": "0.19.0",
76
- "@voltro/plugin-webhooks": "0.19.0",
77
- "@voltro/protocol": "0.19.0",
78
- "@voltro/runtime": "0.19.0",
79
- "@voltro/serverless": "0.19.0",
80
- "@voltro/workflow": "0.19.0",
65
+ "@voltro/ai": "0.20.1",
66
+ "@voltro/cache": "0.20.1",
67
+ "@voltro/data-transfer": "0.20.1",
68
+ "@voltro/database": "0.20.1",
69
+ "@voltro/env": "0.20.1",
70
+ "@voltro/kv": "0.20.1",
71
+ "@voltro/logger": "0.20.1",
72
+ "@voltro/plugin-auth": "0.20.1",
73
+ "@voltro/plugin-broadcast": "0.20.1",
74
+ "@voltro/plugin-mail": "0.20.1",
75
+ "@voltro/plugin-storage": "0.20.1",
76
+ "@voltro/plugin-webhooks": "0.20.1",
77
+ "@voltro/protocol": "0.20.1",
78
+ "@voltro/runtime": "0.20.1",
79
+ "@voltro/serverless": "0.20.1",
80
+ "@voltro/workflow": "0.20.1",
81
81
  "chokidar": "^5.0.0",
82
82
  "ioredis": "^5.11.1",
83
83
  "tinyglobby": "^0.2.17",
@@ -595,7 +595,7 @@ each plugin's own README.
595
595
 
596
596
  | Topic | Open | Summary |
597
597
  |---|---|---|
598
- | **What's new in 0.19.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. |
598
+ | **What's new in 0.20.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. |
599
599
  | 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. |
600
600
  | 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. |
601
601
  | 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.19.0** | `node_modules/@voltro/cli/templates/agent-docs/whats-new.md` | Everything that changed in this version. Read it before hand-rolling something the framework may now ship. |
12
+ | **What's new in 0.20.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. |
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. |
@@ -545,6 +545,21 @@ fully concurrent. Tune the cap with `VOLTRO_DEV_SSR_COMPILE_CONCURRENCY` (below)
545
545
  drop it on a low-memory box, raise it on a big machine.
546
546
 
547
547
 
548
+ Under `VOLTRO_LOG_LEVEL=debug` each cold compile logs its own duration, so a slow
549
+ first paint can be attributed to a specific module:
550
+
551
+ ```
552
+ [voltro:dev:web] ssr cold-compile start id=/app/src/pages/layout.tsx
553
+ [voltro:dev:web] ssr cold-compile start id=/app/src/pages/(main)/layout.tsx
554
+ [voltro:dev:web] ssr cold-compile end 3743ms id=/app/src/pages/layout.tsx
555
+ ```
556
+
557
+ Read the `ms` on the **end** line rather than subtracting timestamps: compiles run
558
+ concurrently up to the cap, so the start and end lines interleave and adjacent
559
+ lines usually belong to different modules. The duration is measured inside the
560
+ concurrency permit, so it is that module's own compile cost and not time spent
561
+ queued behind the cap. A compile that threw says `FAILED` instead of `end`.
562
+
548
563
  ### Fast Refresh: what hot-updates and what reloads
549
564
 
550
565
  Editing a **page or layout component** applies as a hot update — the React tree
@@ -835,6 +850,24 @@ apps/acme/web/.framework/dist/
835
850
  └── ssrEntry.js # SSR bundle for voltro start
836
851
  ```
837
852
 
853
+ ## Unresolvable optional peers
854
+
855
+ The SSR bundle inlines everything it reaches (`ssr: { noExternal: true }`), which is what lets a production web image ship without a framework dependency tree. A package that cannot be **resolved at all** is externalised instead of failing the build — almost always an uninstalled optional native peer reached through a library's Node entry point:
856
+
857
+ ```
858
+ Rolldown failed to resolve import "canvas" from ".../konva/lib/index-node.js"
859
+ ```
860
+
861
+ `konva`'s `main` is its Node build, which requires the optional `canvas`; its `browser` field points at one that does not. An app that never renders to a canvas server-side has nothing to install, and there is no app-side workaround: making the import dynamic does not help (the bundler must still resolve it to form the chunk), and `renderMode: 'spa'` does not either — the generated router imports every page statically, so the module is in the SSR graph whatever the render mode.
862
+
863
+ Every specifier externalised this way is named on the success line:
864
+
865
+ ```
866
+ [voltro:build] SSR bundle ready { path: 'dist/server/ssrEntry.js', externalizedOptionalPeers: 'canvas' }
867
+ ```
868
+
869
+ Read that list. Externalising is right for an optional peer you never use, and wrong for a dependency you forgot to install — it turns a build failure into a runtime one, and only you can tell the two apart. Framework packages (`@voltro/*`, `@effect/*`, `effect`) are never externalised.
870
+
838
871
  ## `voltro start <appDir>`
839
872
 
840
873
  ```bash
@@ -1937,6 +1937,19 @@ source of truth. Just restoring the snapshot brings the data back
1937
1937
  but the declared schema still says the column shouldn't exist; the
1938
1938
  next apply would re-drop it.
1939
1939
 
1940
+ ## The differ leaves snapshots alone
1941
+
1942
+ A soft drop leaves an object in the database that no schema declares —
1943
+ that is the whole point of it. The planner treats every
1944
+ `<name>__dropped_<ts>` as framework-managed and never plans a drop for
1945
+ it, the same way it skips `_voltro_*` and the cluster engine's tables.
1946
+ This holds for columns and for tables.
1947
+
1948
+ You do not need `VOLTRO_DB_IGNORE_TABLES` for a snapshot, and you should
1949
+ not add one: that list is for YOUR unmanaged infra tables, and an entry
1950
+ there would still be in your config long after `gc-snapshots` reclaimed
1951
+ the snapshot.
1952
+
1940
1953
  ## GC
1941
1954
 
1942
1955
  Snapshot columns aren't automatically dropped. They survive until
@@ -2612,7 +2625,15 @@ export default migration({
2612
2625
  // VOLTRO_DESTRUCTIVE_OK=1 voltro db apply --note 'retiring oldUsersTable per ticket #...'
2613
2626
  ```
2614
2627
 
2615
- **Two tables this never proposes dropping:**
2628
+ **Delete the entity and drop the table in the SAME change.** The intuitive
2629
+ order — remove the code first, sort the schema out after — is the broken one:
2630
+ with `VOLTRO_AUTO_MIGRATE=1` the very next boot sees an undeclared table,
2631
+ refuses, and the app crashloops until the drop is authorised. There is nothing
2632
+ to recover from, but the app is down while you work it out. Take the entity out
2633
+ together with the `VOLTRO_DESTRUCTIVE_OK` apply that removes its table, or leave
2634
+ the entity in place until you are ready to run both.
2635
+
2636
+ **Three tables this never proposes dropping:**
2616
2637
 
2617
2638
  - **`actors`** — the framework-provided audit subject. You don't declare an
2618
2639
  `actors.entity.ts`; `db plan` / `db apply` auto-include the built-in `actors`
@@ -2633,6 +2654,10 @@ export default migration({
2633
2654
  Strapi→Voltro `_strapi_id_map` sitting in the DB won't refuse-to-boot on a
2634
2655
  `drop-table`. (The framework already self-excludes its own `_voltro_*` /
2635
2656
  `cluster_*` runtime tables; this is the user list on top of that.)
2657
+ - **Soft-drop snapshots** — a `<name>__dropped_<ts>` left behind by
2658
+ `VOLTRO_SOFT_DROP=1`. The differ treats it as framework-managed until
2659
+ `voltro db gc-snapshots` reclaims it, so do NOT add one to
2660
+ `VOLTRO_DB_IGNORE_TABLES`.
2636
2661
 
2637
2662
  ## A migrate / apply DDL statement failed — find which one
2638
2663
 
@@ -2699,7 +2724,15 @@ it now is, must have nothing left to do.
2699
2724
 
2700
2725
  **It is a framework bug, not a mistake in your schema.** The named operations
2701
2726
  emit DDL that does not take effect. Report the operation kinds plus the column
2702
- types involved. In the meantime the schema is unchanged and safe — nothing was
2727
+ types involved.
2728
+
2729
+ **Read the named operations before you trust the "no-op" wording**, though —
2730
+ the message states a CAUSE, and a cause can be wrong. One release told users
2731
+ `drop-table <name>__dropped_<ts>` was a no-op when the DDL had worked perfectly:
2732
+ `VOLTRO_SOFT_DROP=1` had renamed the table, and the planner then read its own
2733
+ snapshot as one more undeclared table and proposed dropping it again. Fixed, and
2734
+ worth knowing as the shape to look for: an operation naming an object that the
2735
+ PREVIOUS operation created or renamed is a planner blind spot, not dead DDL. In the meantime the schema is unchanged and safe — nothing was
2703
2736
  half-applied, and no fingerprint was written, so `voltro db plan` still shows you
2704
2737
  the truth.
2705
2738
 
@@ -271,6 +271,22 @@ entirely. The downgrades are documented rather than hidden because the
271
271
  alternative to a stated escape hatch is deleting the marker — and a check whose
272
272
  only way out is to disable it gets disabled.
273
273
 
274
+ ### Gate CI on the audit having RUN, not on its silence
275
+
276
+ `voltro doctor` can only run this audit if it can load your descriptors. When it
277
+ cannot, it says so instead of claiming a pass:
278
+
279
+ ```
280
+ • serverOnly: NOT CHECKED — the app's descriptors could not be loaded (not a pass)
281
+ reason: Transform failed with 1 error:
282
+ src/queries/broken.query.ts:2:5: ERROR: Expected ";" but found "is"
283
+ ```
284
+
285
+ `voltro doctor --json` carries the same answer as `serverOnly: { checked, reason?, leaks? }`.
286
+ Assert on `checked` — an app that leans on `.serverOnly()` should treat a
287
+ persistent skip as a failure, because a skipped audit and a clean one look
288
+ identical from the outside.
289
+
274
290
  (`voltro check` does **not** run this audit. It has a live-api mode that has no
275
291
  access to your table definitions, and a rule that fires in one of its two modes
276
292
  would be worse than one that fires in neither.)
@@ -802,6 +802,31 @@ Each replica persists its progress in the **`_voltro_cdc_offsets`** table — on
802
802
 
803
803
  If the persisted offset has been purged (`err 1236`) or rejected after a failover, the reader jumps to the current binlog end and signals a resync so dependent subscriptions re-query rather than missing the gap.
804
804
 
805
+ ### A table the reader cannot decode
806
+
807
+ `@vlasky/zongji` reads each event's column layout from the binlog's `Table_map` (fixed on disk) and compares it to a fresh `information_schema` fetch. A mismatch throws:
808
+
809
+ ```
810
+ Table app.sessions schema changed between binlog event and metadata fetch:
811
+ the event has 9 columns, fetched metadata has 8
812
+ ```
813
+
814
+ The usual cause on MariaDB is a **UNIQUE constraint on an unbounded text column**. MariaDB can only back that with a **HASH long-unique index**, and that index adds a hidden `DB_ROW_HASH_n` column to the InnoDB row — present in the binlog row image, absent from `information_schema.COLUMNS`. So the counts can never agree, and every write to that table trips it. Nothing is broken; the table is shaped that way.
815
+
816
+ 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.
817
+
818
+ **The remedy is to bound the column:**
819
+
820
+ ```ts
821
+ tokenHash: text().maxLength(64).unique() // VARCHAR(64) → ordinary B-tree index
822
+ ```
823
+
824
+ `ALTER TABLE … FORCE` does **not** help. The rebuild recreates the index and therefore recreates the hidden column — measured before and after: same column count both times. If you already tried it, that was not your mistake.
825
+
826
+ A bounded unique is worth having anyway: it is also what keeps the key inside the index-size limits on every dialect.
827
+
828
+ The other cause of the same message is a **backlog event that predates a migration** — the reader was down while a table changed. That one is unreplayable but transient: the reader skips to the current binlog end, signals a resync, and recovers. One warning, then it is over.
829
+
805
830
  ### Boot summary
806
831
 
807
832
  ```
@@ -217,6 +217,21 @@ Common uses:
217
217
 
218
218
  Returns the FINAL row in both cases (newly-inserted or pre-existing).
219
219
 
220
+ ### One conflict target, and only conflicts
221
+
222
+ `conflictColumns` names ONE constraint. A duplicate on a *different* unique index is not something `insertIgnore` can resolve — it cannot know which existing row you meant — so it throws, naming the constraint that actually fired.
223
+
224
+ It also throws when the insert was **rejected** rather than skipped. This matters most on MariaDB, where the statement lowers to `INSERT IGNORE`: that downgrades *every* error to a warning — foreign key, NOT NULL, CHECK, truncation — not just the unique violation the API models. So "no row was inserted" does not imply "a conflict happened", and reporting one as the other would turn a rejected write into a silent no-op: the row is not there and the caller is told it already was.
225
+
226
+ ```
227
+ MysqlStore.insertIgnore: the insert into 'docs' was REJECTED, not skipped as a
228
+ conflict. INSERT IGNORE downgrades every error to a warning, and the warning was:
229
+ [1452] Cannot add or update a child row: a foreign key constraint fails …
230
+ Nothing was written and nothing conflicted — fix the cause above.
231
+ ```
232
+
233
+ The real cause comes from `SHOW WARNINGS` on the same connection, which is only attributable inside a transaction — so outside one the message says the constraint is unknown rather than guessing at it. Framework mutations are auto-transactional, so the common path has the cause.
234
+
220
235
  ## `updateMany` — one-statement bulk update
221
236
 
222
237
  ```ts
@@ -258,6 +258,19 @@ No naming trick is needed to keep something out of the router: the absence of th
258
258
 
259
259
  > Before this convention, every `.tsx` under `src/pages/` became a route, so a co-located component silently got a URL. That route rendered nothing and nobody visited it in dev; the failure surfaced at the first production build. If you are upgrading, `voltro update` renames your pages for you.
260
260
 
261
+ ## Two routes, one screen
262
+
263
+ When two URLs must render the same component — a versioned path kept alive because devices in the field are configured against it, say — re-export it instead of copying it:
264
+
265
+ ```tsx
266
+ // src/pages/v2/page.tsx → /v2, rendering exactly what / renders
267
+ export { default, renderMode } from '../page'
268
+ ```
269
+
270
+ `export { default } from '…'` satisfies the page contract: the module has a default export, it just did not declare it here. Forwarding `renderMode` alongside it is what keeps the two routes from drifting apart — the build follows the forward when it computes the render profile, so `/v2` is classified the same as `/`, not silently as `static`.
271
+
272
+ `export { default as Screen } from '../page'` is the opposite: it renames the default away, leaving this module without one. That still fails the contract.
273
+
261
274
  ## Trailing slashes
262
275
 
263
276
  The canonical form is **no trailing slash** — always link with `<Link to="/about">`, not `<Link to="/about/">`.