@voltro/cli 0.19.0 → 0.20.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.
- package/CHANGELOG.md +87 -0
- package/dist/apiBuild-B1FDtx1y.js +2 -0
- package/dist/{apiBuild-CY5pEwwq.js → apiBuild-CSCVI1wC.js} +2 -2
- package/dist/bin.js +2 -2
- package/dist/{commands-BvvoQL0v.js → commands-DQ0mUwDe.js} +1854 -1758
- package/dist/{dev-B6rfgKfo.js → dev-DnHlqqoF.js} +1404 -1395
- package/dist/{dev-C_E1XxNF.js → dev-Z1E_twG9.js} +1 -1
- package/dist/index.js +1 -1
- package/dist/{inspectMetrics-D1DmLeJs.js → inspectMetrics-BL8kZOv3.js} +196 -176
- package/dist/{serveCommand-vPF5ucXC.js → serveCommand-Dh4qd49N.js} +3 -3
- package/dist/serveEntry.js +2 -2
- package/dist/{start-Clvz4IJb.js → start-IVgQe2YD.js} +368 -368
- package/dist/startEntry.js +2 -2
- package/package.json +17 -17
- package/templates/AGENTS.md +1 -1
- package/templates/agent-docs/_index.md +1 -1
- package/templates/agent-docs/database/migrations.md +35 -2
- package/templates/agent-docs/database/misc.md +16 -0
- package/templates/agent-docs/routing.md +13 -0
- package/templates/agent-docs/whats-new.md +43 -109
- package/templates/apps/api-ai/package.json +7 -7
- package/templates/apps/api-auth/package.json +8 -8
- package/templates/apps/api-backend/package.json +7 -7
- package/templates/apps/api-backend-deactivation/package.json +7 -7
- package/templates/apps/api-backend-mail/package.json +8 -8
- package/templates/apps/api-backend-mariadb/package.json +9 -9
- package/templates/apps/api-backend-storage/package.json +8 -8
- package/templates/apps/api-data-advanced/package.json +8 -8
- package/templates/apps/api-durable/package.json +8 -8
- package/templates/apps/api-feature-flags/package.json +9 -9
- package/templates/apps/api-governance/package.json +8 -8
- package/templates/apps/api-kv/package.json +8 -8
- package/templates/apps/api-moderation/package.json +8 -8
- package/templates/apps/api-observability/package.json +8 -8
- package/templates/apps/api-ratelimit/package.json +8 -8
- package/templates/apps/api-rbac/package.json +8 -8
- package/templates/apps/api-rest/package.json +7 -7
- package/templates/apps/api-saas/package.json +11 -11
- package/templates/apps/api-search/package.json +8 -8
- package/templates/apps/api-versioning/package.json +8 -8
- package/templates/apps/api-webhooks/package.json +9 -9
- package/templates/apps/changelog/package.json +6 -6
- package/templates/apps/edge-functions/package.json +2 -2
- package/templates/apps/frontend-admin/package.json +8 -8
- package/templates/apps/frontend-app/package.json +8 -8
- package/templates/apps/frontend-blank/package.json +7 -7
- package/templates/apps/frontend-contact/package.json +7 -7
- package/templates/apps/frontend-dashboard/package.json +7 -7
- package/templates/apps/frontend-docs/package.json +7 -7
- package/templates/apps/frontend-i18n/package.json +6 -6
- package/templates/apps/frontend-landing/package.json +7 -7
- package/templates/apps/frontend-spa/package.json +7 -7
- package/templates/apps/frontend-ssr/package.json +7 -7
- package/templates/apps/frontend-ssr-api/package.json +8 -8
- package/templates/apps/frontend-static-blog/package.json +6 -6
- package/dist/apiBuild-9NXH53Sd.js +0 -2
package/dist/startEntry.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { t } from "./start-
|
|
1
|
+
import { et as e } from "./inspectMetrics-BL8kZOv3.js";
|
|
2
|
+
import { t } from "./start-IVgQe2YD.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.
|
|
3
|
+
"version": "0.20.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",
|
|
@@ -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.
|
|
66
|
-
"@voltro/cache": "0.
|
|
67
|
-
"@voltro/data-transfer": "0.
|
|
68
|
-
"@voltro/database": "0.
|
|
69
|
-
"@voltro/env": "0.
|
|
70
|
-
"@voltro/kv": "0.
|
|
71
|
-
"@voltro/logger": "0.
|
|
72
|
-
"@voltro/plugin-auth": "0.
|
|
73
|
-
"@voltro/plugin-broadcast": "0.
|
|
74
|
-
"@voltro/plugin-mail": "0.
|
|
75
|
-
"@voltro/plugin-storage": "0.
|
|
76
|
-
"@voltro/plugin-webhooks": "0.
|
|
77
|
-
"@voltro/protocol": "0.
|
|
78
|
-
"@voltro/runtime": "0.
|
|
79
|
-
"@voltro/serverless": "0.
|
|
80
|
-
"@voltro/workflow": "0.
|
|
65
|
+
"@voltro/ai": "0.20.0",
|
|
66
|
+
"@voltro/cache": "0.20.0",
|
|
67
|
+
"@voltro/data-transfer": "0.20.0",
|
|
68
|
+
"@voltro/database": "0.20.0",
|
|
69
|
+
"@voltro/env": "0.20.0",
|
|
70
|
+
"@voltro/kv": "0.20.0",
|
|
71
|
+
"@voltro/logger": "0.20.0",
|
|
72
|
+
"@voltro/plugin-auth": "0.20.0",
|
|
73
|
+
"@voltro/plugin-broadcast": "0.20.0",
|
|
74
|
+
"@voltro/plugin-mail": "0.20.0",
|
|
75
|
+
"@voltro/plugin-storage": "0.20.0",
|
|
76
|
+
"@voltro/plugin-webhooks": "0.20.0",
|
|
77
|
+
"@voltro/protocol": "0.20.0",
|
|
78
|
+
"@voltro/runtime": "0.20.0",
|
|
79
|
+
"@voltro/serverless": "0.20.0",
|
|
80
|
+
"@voltro/workflow": "0.20.0",
|
|
81
81
|
"chokidar": "^5.0.0",
|
|
82
82
|
"ioredis": "^5.11.1",
|
|
83
83
|
"tinyglobby": "^0.2.17",
|
package/templates/AGENTS.md
CHANGED
|
@@ -595,7 +595,7 @@ each plugin's own README.
|
|
|
595
595
|
|
|
596
596
|
| Topic | Open | Summary |
|
|
597
597
|
|---|---|---|
|
|
598
|
-
| **What's new in 0.
|
|
598
|
+
| **What's new in 0.20.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. |
|
|
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.
|
|
12
|
+
| **What's new in 0.20.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. |
|
|
@@ -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
|
-
**
|
|
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.
|
|
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.)
|
|
@@ -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/">`.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# What's new in 0.
|
|
1
|
+
# What's new in 0.20.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
|
|
@@ -9,149 +9,83 @@ BREAKING entries name a codemod; run `voltro update` to apply it.
|
|
|
9
9
|
|
|
10
10
|
### ⚠ BREAKING
|
|
11
11
|
|
|
12
|
-
- **@voltro/plugin-
|
|
12
|
+
- **@voltro/plugin-versioning, @voltro/database, @voltro/voltro, @voltro/sql-postgres, @voltro/sql-mysql, @voltro/sql-sqlite, @voltro/sql-mssql** — `versioningPlugin({ timing: 'in-transaction' })` produced a WRONG trail, not merely a slow one. Reported and reproduced against MariaDB 11 by a team that wired both plugins and measured before migrating a single call site.
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
**It recorded every change twice.** The two timings are alternatives, but the post-commit change tap stayed wired when the in-transaction recorder was registered, so both ran. One `bookmarks.create` → two history rows.
|
|
15
15
|
|
|
16
|
-
**
|
|
16
|
+
**And the trail was mis-ordered, which is worse.** Each path numbered independently: one insert plus one update produced versions `0, 0, 1, 2` across four rows. `selectAsOf`, `sortHistory` and `diffVersionRows` all read `version`, so `rowAsOf` returned the wrong snapshot and `diffVersions` found nothing. A duplicate can be deduped; a wrong order cannot be detected from the data.
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
The recorder wrote a constant `version: 0` on purpose, with a design note arguing that ordering could come from `changedAt` and that a read per covered write was too expensive. Both halves were wrong: `changedAt` is millisecond-resolution, so two writes to one row inside one transaction tie routinely, and the number is what every reader consults.
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
**BREAKING —** a `WriteRecorder` now receives a PORT (`{ append, maxOf }`) rather than a bare `append`. `maxOf` is one aggregate with an equality filter on the connection the write already holds; it is what lets an append-only trail number its own entries. A recorder still cannot UPDATE, DELETE or open a nested transaction, and a throw from either operation still rolls the caller's write back. Apps that merely ENABLE the timing need no change — only a hand-written recorder does, and `tsc` names every site.
|
|
21
21
|
|
|
22
|
-
**
|
|
22
|
+
**Cost, stated rather than avoided:** `'in-transaction'` now takes TWO round-trips per recorded write, roughly doubling this timing's published per-write overhead. Both timings number from 1, so switching `timing` no longer shifts version numbers.
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
- **@voltro/cli, @voltro/database** — **`.serverOnly()` now gates where it said it did.** A wire-reachable query that declares a `.serverOnly()` column of its source table in its `output` fails the boot under `voltro serve`, and makes `voltro doctor` exit non-zero. `voltro dev` still warns.
|
|
26
|
-
|
|
27
|
-
It shipped as one `log.warn` and nothing else — in every command — while `ColumnBuilder.serverOnly()`'s own doc comment and the seeded `AGENTS.md` marker table both said "**the boot audit — it FAILS the boot**, it does not warn". A team read the strong sentence, adopted the marker on four credential columns, injected a deliberate leak to check, and watched the server come up serving the leaking query. That is the register this repo keeps meeting from a new angle: *a check that prints instead of gating still reads as coverage* — here on the one marker whose entire job is the enforcement.
|
|
28
|
-
|
|
29
|
-
Two smaller things went with it, both of which had misled the reporter:
|
|
30
|
-
|
|
31
|
-
- **The message names the command.** It was emitted through a module-level logger scoped `voltro:dev`, so a warning from `voltro serve` announced itself as dev — which is why they concluded, and reported, that the audit does not run in production at all. It did; it just misattributed itself and stopped nothing. - **The audit is computed once, in `loadDiscovered`**, the discovery dev / serve / doctor / check all share — the same reasoning `validateRegisteredRelations()` lives there for. Dev and serve may disagree about what a leak COSTS; they must not disagree about what a leak IS.
|
|
32
|
-
|
|
33
|
-
`VOLTRO_SERVER_ONLY` moves the line both ways: `strict` fails `voltro dev` too, `warn` downgrades serve, `off` silences it. The downgrades are documented rather than hidden, because the alternative to a stated escape hatch is deleting the marker, and a check whose only way out is to disable it gets disabled.
|
|
34
|
-
|
|
35
|
-
`voltro check` deliberately does NOT run it: it has a live-api mode with no access to your table definitions, and a rule that fires in one of its two modes is worse than one that fires in neither.
|
|
36
|
-
|
|
37
|
-
**Migration** — `voltro update` prints it (`0.19.0/01_server-only-gates-the-boot`, `manual`, and it fires only for apps that actually use the marker). Run `voltro doctor` BEFORE you deploy: it reports exactly what `voltro serve` will now refuse, with no deploy involved. Each finding has two honest fixes and only its author can choose — the column is not wire-safe (drop it from the query's `output`, keep the marker), or the marker is wrong (drop the `.serverOnly()`). Do not substitute `.encrypted()`: that is the at-rest axis, the runtime decrypts for the handler, and reading it as "safe to expose" is the category error the three-marker table exists to prevent. To ship while triaging, `VOLTRO_SERVER_ONLY=warn` downgrades serve back to a warning — a bridge, not a setting to keep.
|
|
38
|
-
|
|
39
|
-
*Why this is `BREAKING` and not `Changed`: no signature moves and nothing that compiled stops compiling, so the literal type-level test does not catch it. It can still turn a booting production app into one that refuses — which is the point, the boot it refuses is the one shipping the column — and that failure lands at DEPLOY time. Filing it as `Changed` would have kept it out of the one section the stability contract names as the migration path, and out of `voltro update` entirely, because codemods hang off `BREAKING`. Both doc claims were corrected in the same change, so the `.d.ts`, the agent template and the docs site now describe the same behaviour.*
|
|
40
|
-
|
|
41
|
-
### Added
|
|
42
|
-
|
|
43
|
-
- **@voltro/plugin-audit** — **`auditByTrace` / `auditBySubject` — the read side of the correlation join.**
|
|
44
|
-
|
|
45
|
-
`byAuditTrace` and `byAuditSubjectStatus` shipped in the same release with **no caller**. That is the identical defect the versioning side had and that its own entry points were added to fix, repeated on the other half of the join one file away: an index nobody can enter is a query the app still hand-writes, and the docs then demonstrate a raw select over a framework-internal table.
|
|
46
|
-
|
|
47
|
-
It surfaced by checking an adopting team's design document against the code rather than from memory. Their §6 asks for *"a read-side composition joining `_voltro_row_history` × the audit sink (on `traceId`) × `actors`"* — which needs BOTH halves to have an entry point, or neither is usable.
|
|
48
|
-
|
|
49
|
-
```ts
|
|
50
|
-
const calls = await auditByTrace(ctx.store, traceId) // who called, and was it refused
|
|
51
|
-
const changed = await historyByTrace(ctx.store, traceId, tenantId) // what it changed
|
|
52
|
-
const denied = await auditBySubject(ctx.store, actorId, { status: 'error' })
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
`auditBySubject` takes `status` as a real argument rather than leaving the caller to filter in JS — the index is `(subjectId, status, at)`, so a filter applied after fetching would not use it. `limit` defaults to 100, because an actor's history is unbounded and an entry point that returns all of it is one you call once in production.
|
|
56
|
-
|
|
57
|
-
*They were also not exported from the package index when first written — built, tested, and unreachable. Caught before shipping; worth recording because "it has a test" and "a consumer can call it" are different claims.*
|
|
58
|
-
- **@voltro/database, @voltro/runtime, @voltro/protocol, @voltro/cli, @voltro/plugin-versioning, @voltro/plugin-audit, @voltro/voltro** — **`ChangeEvent` carries the calling `traceId` and `subjectId`** — the join key that lets `plugin-versioning` (what changed) and `auditPlugin` (who called, and whether they were refused) be read as one trail.
|
|
59
|
-
|
|
60
|
-
Both halves shipped and neither could be joined to the other. A consumer put it exactly right: *"we are not asking you to build our audit feature. We are asking for the join key that lets anyone build one on the three pieces you have already shipped."*
|
|
61
|
-
|
|
62
|
-
**The mechanism, and the part that was an empirical question rather than a design one.** Identity is known one layer up (`subject` in the middleware, `traceId` at the rpc boundary) and the event is created several layers down, inside each dialect store's private emit. Threading a context argument through every `DataStore` method to reach it would change the port every driver implements, for metadata that is ambient by nature — so it rides an `AsyncLocalStorage` (`@voltro/database`'s `writeAttribution.ts`), the same shape as the existing trace and replica-routing contexts.
|
|
63
|
-
|
|
64
|
-
Whether that survives a SQL store was *not* obvious: the write goes through `ManagedRuntime.runPromise`, so the read happens inside an Effect fiber, and a scheduler draining fibers from a shared loop would run them under the async context of whoever created the drain. If that were true the attribution would be silently ABSENT — a join key that is simply never there, on a trail nobody checks until an incident. Verified against a real sqlite store, including the transactional path (which queues events and flushes them post-commit, *outside* the scope — which is why the stamp goes at event CREATION, not delivery).
|
|
65
|
-
|
|
66
|
-
- **One call site, not one per method.** The scope is established at the rpc executor boundary, so it covers every write the handler makes — `ctx.store`, `EffectStore`, the crud helpers, a plugin interceptor's own writes. Wrapping the store middleware instead would have meant wrapping each mutating method and hoping the next one added remembers. - **`subjectId` is the same identity `audit()` stamps** (`actingUserId`, now exported so there is one source). Two answers to "who wrote this row" on one write would be worse than one; which API *key* was used is recoverable from the audit row sharing the `traceId`. - **Absent is a fact, not a gap** — no request behind the write (seed, startup hook, schedule, workflow step), or an event from another replica, where stamping the local ambient trace would attribute a remote write to a local call. The keys are omitted rather than set to `undefined`, so an unattributed event is byte-identical to one from before this existed. - **`dev.ts` and `serveCommand.ts` no longer hand-mirror the plugin fan-out.** They built that object literal separately in two files; a field added to one and not the other gives a plugin the data in dev and silence in production, and both files typecheck alone. Now `toPluginChangeEvent`.
|
|
67
|
-
|
|
68
|
-
`_voltro_row_history` gains `traceId` / `subjectId` plus `byTrace` and `bySubject` indices — "what did this call touch" and "what did this actor touch" were previously unanswerable at any speed, since `byRow` requires already knowing which row you are asking about. `changedBy` now prefers the caller over the row's `audit()` stamp, which fixes a reported case for free: the stamp is `null` for every write through the boot store, so a login route produced version rows with no actor at all.
|
|
69
|
-
|
|
70
|
-
*`apiSurface: compatible`: every golden line this touches is either a pure addition (the new fields and `actingUserId`) or api-extractor renumbering an import alias — `Subject_2` became `Subject` across `runtime` and the `voltro` re-export because a new import changed the ordering. No declaration changed shape and no call site is affected.*
|
|
71
|
-
- **@voltro/cli** — **`voltro doctor`'s hand-roll detector gains six rules and ranks its findings by file count.**
|
|
72
|
-
|
|
73
|
-
An adopting team audited four apps by hand against the framework's "reach for instead" table and produced twenty items, each with a count (`1631` files with a manual `rows[0]`, `500` queries with `input.offset`, `306` forms, `368` relations declared against `17` uses). **The detector already reported eleven of those twenty, with counts.** These are rules for what it missed — the findings that made a person do work a scan should have done:
|
|
74
|
-
|
|
75
|
-
- **`offset-pagination`** — `.offset(` / `input.offset` / `.skip(` on a list query. `hand-cursor` did not catch this: it wants `hasMore` AND `limit+1`, which is a hand-rolled *keyset* pager. Plain OFFSET is a different smell with a worse ending — O(n) in the page number, so it does not fail, it just stops loading once the table is big, on the tables (audit logs, time entries) that get big first. - **`oauth-token-table`** — an `accessToken`/`refreshToken` column on an app table → `defineConnection({ kind: 'oauth2' })`. The reporting team's version of this leaked: a `json()` column holding a Slack payload with the token inside, readable by every colleague in the tenant. - **`non-incremental-aggregate`** — a `count/sum/avg/min/max` aggregate with no `incremental:`, rescanned in full on every refresh. - **`external-state-library`** — jotai / zustand / mobx / redux → `defineStore`. A second runtime beside the reactive engine, and one that does not participate in the subscription graph. - **`hand-route-module`** — importing a hand-maintained `routes` / `urls` module instead of `.framework/routes.generated`, where a renamed page is a compile error rather than a 404 someone finds in production. - **`hand-permission-check`** — a component reading a permission bag by hand instead of `useCan` / `useResourceCan`.
|
|
76
|
-
|
|
77
|
-
**And the findings are now ordered by file count rather than by the order the rules happen to be declared in.** A reader facing twenty findings acts on the top of the list, so an arbitrary order silently decides what gets fixed. The reporting team ranked their own work by exactly that number and had to count it by hand, because this printed the same facts unranked. Effort we cannot know; magnitude we can.
|
|
78
|
-
|
|
79
|
-
*Two of these rules were caught being wrong by their own tests before shipping: `offset-pagination`'s first version checked only `input.offset` and did not match its own fixture (a narrow rule reports nothing and reads like a clean codebase), and the ranking test passed with the sort deleted until the fixture was rebuilt so declaration order and count order disagree.*
|
|
80
|
-
- **@voltro/plugin-versioning** — **`historyByTrace` / `historyBySubject` — the entry points the new indices existed for.** Plus their Effect twins.
|
|
81
|
-
|
|
82
|
-
The correlation bridge added `byTrace` and `bySubject` to `_voltro_row_history` in the same release, and shipped them with no caller: `rowHistory` requires a `rowId` you already have, which is the wrong way round during an incident, when what you have is a trace or an actor. The docs demonstrated a raw `ctx.store.select('_voltro_row_history')`, which is the shape an index is supposed to save you from writing.
|
|
83
|
-
|
|
84
|
-
Both are tenant-scoped exactly like `rowHistory` (own-tenant rows plus null-tenant rows from untenanted source tables; `undefined` skips the filter, for system paths only). `historyBySubject` takes a `limit`, default 100, because an actor's history is unbounded and an entry point that returns all of it is one you call once in production and never again.
|
|
85
|
-
|
|
86
|
-
**And a bug this surfaced.** The stored-row → `VersionRow` decoder was written inline inside `versionsOf`, before the bridge existed, and silently dropped `traceId` / `subjectId` — so `rowHistory()` returned rows missing the very field the feature exists to carry. Extracted to one `rowToVersion` now shared by all three readers, which is why the drift was possible in the first place.
|
|
87
|
-
- **@voltro/cli, @voltro/database** — **`setRowFilter` is now named in the always-loaded agent core**, with the distinction that makes it findable.
|
|
88
|
-
|
|
89
|
-
A team migrating eleven hand-written row filters reported missing it **twice** — once in a full framework audit, and once while telling a colleague in writing that the framework has no row-filter primitive. The depth doc covers it well; a depth doc is opened by someone who already suspects the topic. The core's SERVER rubric asked *"a permission check?"* and answered with two BOOLEAN questions, so a reader holding a row-VISIBILITY question took the nearest-fitting answer and wrote the filter by hand. Eleven times.
|
|
24
|
+
### Fixed
|
|
90
25
|
|
|
91
|
-
|
|
26
|
+
- **@voltro/database, @voltro/sql-postgres, @voltro/sql-mysql, @voltro/sql-sqlite, @voltro/sql-mssql** — The correlation bridge did not survive a transaction, and did not survive CDC. Both are fixed, and both were found by measuring against live databases after a consumer isolated the symptom in a scratch app.
|
|
92
27
|
|
|
93
|
-
|
|
28
|
+
**Every write a framework mutation makes was unattributed.** `transactional()` is entered from the request's async-local scope, but its callback runs from inside the Effect the store builds — and measured against live postgres AND live mariadb, the scope is active at the call site and EMPTY inside the callback. Framework mutations are auto-transactional, so this was every handler write. Same class as the `bindMutation` defect fixed alongside it: a scope covering the construction of an Effect and not its execution. The caller's attribution is now captured at `transactional()` entry and re-established around the callback, in all four dialect stores.
|
|
94
29
|
|
|
95
|
-
|
|
30
|
+
**And the CDC transports could not carry it at all.** Under `changeStrategy: 'cdc'` — the DEFAULT — the event a subscriber receives is rebuilt from a postgres NOTIFY payload or a mysql binlog row image, neither of which can hold a request context. `registerPendingAttribution` / `claimPendingAttribution` (`@voltro/database`) let the write path hand its identity to the echo, keyed by `(table, op, id)` and claimed once. A write made on ANOTHER replica has nothing pending and stays unattributed, which is the correct answer rather than a gap.
|
|
96
31
|
|
|
97
|
-
|
|
32
|
+
**Plus one nobody had reported, found on the way:** on postgres under CDC the write path skipped `routeEvent` entirely, and `runWriteRecorders` lives inside it — so `versioningPlugin({ timing: 'in-transaction' })` with the default `CDC=1` recorded NOTHING. The mode whose entire promise is "if the change committed, the entry is there" wrote an empty trail, silently. `routeEvent` now runs in both modes; only the DELIVERY decision is strategy-dependent.
|
|
98
33
|
|
|
99
|
-
|
|
34
|
+
New live-dialect suites (`cdcAttribution.integration.test.ts` in `sql-postgres` and `sql-mysql`) pin all of it, and were verified red against the previous code.
|
|
35
|
+
- **@voltro/plugin-versioning, @voltro/runtime, @voltro/cli** — `_voltro_row_history.traceId` and `.subjectId` were NULL on every write. Three independent causes, all found from one consumer report whose evidence pinned the diagnosis before we looked: `subjectId` was NULL while `changedBy` on the SAME row carried the acting user — so the identity was known and was not travelling.
|
|
100
36
|
|
|
101
|
-
|
|
102
|
-
- **@voltro/database, @voltro/plugin-versioning, @voltro/sql-postgres, @voltro/sql-mysql, @voltro/sql-mssql, @voltro/sql-sqlite** — **`versioningPlugin({ timing: 'in-transaction' })` — the history row commits or rolls back WITH the change it records.** Default stays `'post-commit'`.
|
|
37
|
+
- **The adapter dropped them.** `dataStoreHistoryStore.append` hand-wrote its insert object and listed `changedBy` but not `traceId` / `subjectId`. This is the second time that shape has bitten in this file — the READ side (`rowToVersion`) had drifted identically. A row built by hand in one place and read by hand in another disagree exactly when a field is ADDED, because nothing fails. Both now spread the row. - **An Effect-returning handler was unattributed.** `bindMutation` established the scope around the CALL, which covers an async executor for its whole run — but an Effect-returning one is only CONSTRUCTED there and runs later. It is now forked inside the scope, with interruption and typed failures preserved (both pinned by tests). Verified by measurement, not assumption: an Effect forked inside an ALS scope keeps seeing it across `sleep`, `yieldNow` and a `setTimeout` promise, while the same effect merely constructed inside sees nothing. - **The devtools `/invoke` path never entered the scope at all.** It bypasses the rpc stack by design, and that also bypassed everything `bindMutation` sets up. The audit plugin recorded a traceId (it reads `requestContext.traceId`, which this path does build) while every write underneath carried none — three consumers of one call disagreeing about its trace. It also synthesised `inspect-<8 random chars>`, which no trace consumer can parse; the reporter's framing is the rule worth keeping — *a synthesised id produces a column that looks joinable and is not; NULL at least fails honestly.* It is a real 32-hex id now, and it reaches all three sinks.
|
|
103
38
|
|
|
104
|
-
|
|
39
|
+
`actingUserId` is imported at the new call site rather than re-derived — one answer to "who is writing", shared with what `audit()` stamps.
|
|
40
|
+
- **@voltro/cli** — Three ways a check reported nothing while checking nothing, all found by a consumer verifying the silence instead of trusting it.
|
|
105
41
|
|
|
106
|
-
|
|
42
|
+
- **`unexercised-row-filter` never fired on a typed registration.** The match was `/\bsetRowFilter\s*\(/`, which demands the paren directly after the name, so `setRowFilter<Ctx>({…})` — the spelling our own generic signature invites — broke it. The rule was blind for exactly the teams that had wired `load`/`predicate` carefully. It counts CALLS now. - **…and its test-side condition was satisfiable by a COMMENT.** It matched `rowFilter:` in raw text. Comments and string literals are stripped, and the suite must both call `makeTestContext` and bind `rowFilter` in real code. - **The same paren-adjacent shape sat in two shipped codemod gates.** `0.7.0/01` (row filter) and `0.7.0/02` (`invoke`) both gate on a generic export, so a typed call made `voltro update` print nothing at all: the upgrade reads as clean and the behaviour change lands unread. Both now use the shared `callPattern`, which allows type arguments including nested ones.
|
|
107
43
|
|
|
108
|
-
|
|
44
|
+
Two false-positive fixes in the hand-roll detector, from the same report:
|
|
109
45
|
|
|
110
|
-
**
|
|
46
|
+
- **A file that WIRES a plugin is no longer told to adopt it.** The `presence` rule reported `app.config.ts` (which calls `presencePlugin()`) to an app that had just deleted its hand-rolled table. Rules that recommend a package now declare it, and a file referencing that package is skipped. - **Generated files and `.d.ts` are out of the scan.** A recommendation aimed at a file the next boot overwrites is never actionable.
|
|
111
47
|
|
|
112
|
-
|
|
48
|
+
And one more of the first kind, found while checking why a withdrawn report's probe had stayed silent: `raw-fetch` counted only a BARE `fetch(…)` callee, so `globalThis.fetch(url)` / `self.fetch(url)` in a server file read as clean.
|
|
49
|
+
- **@voltro/cli** — `voltro doctor`'s `serverOnly: NOT CHECKED` line now names the failure, and the field exists in `--json`.
|
|
113
50
|
|
|
114
|
-
|
|
51
|
+
The refusal to claim a pass was right. What shipped with it was nothing to act on: the `catch` discarded the error entirely, so there was no reason, no failing module, and — because the field was absent from `--json` — no way for CI to assert "still unchecked" rather than reading silence as a pass.
|
|
115
52
|
|
|
116
|
-
|
|
53
|
+
A consumer's verdict, which is the useful part: *"The message is honest and that is the problem."* They had already verified that every descriptor, `app.config.ts` and the generated rpc group imported cleanly under `tsx` on their own, so the difference had to be in what `loadDiscovered` does BEYOND importing — and none of that was visible from outside. It matters more than its size because `.serverOnly()` is what guards their `sessions.tokenHash` and `apiKeys.keyHash`, markers they added after finding a query whose output schema shipped a hash over the wire.
|
|
117
54
|
|
|
118
|
-
|
|
55
|
+
`--json` now carries `serverOnly: { checked, reason?, leaks? }`. Gate CI on `checked === false`.
|
|
56
|
+
- **@voltro/plugin-versioning** — A version snapshot no longer copies `.serverOnly()` columns into `_voltro_row_history`. `.encrypted()` columns are KEPT, and that distinction is the whole finding.
|
|
119
57
|
|
|
120
|
-
|
|
58
|
+
Reported by a team choosing which tables to version: `sessions` holds `.encrypted()` PATs and a `tokenHash`, `apiKeys` holds a `keyHash`, and they could not determine from outside what the snapshot would contain. They excluded both tables — then went and measured it, which corrected their own report:
|
|
121
59
|
|
|
122
60
|
```
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
but it is missing or failed to load. Run `voltro build` before serving …
|
|
61
|
+
probeItems.secret enc:v1:a56iziEV9THLhzmJ:Vk0ux+0bECleTLBJkCa0Rg==:3AtMwP…
|
|
62
|
+
_voltro_row_history {"secret":"enc:v1:a56iziEV9THLhzmJ:Vk0ux+0bECleTLBJkCa0Rg==:…"}
|
|
126
63
|
```
|
|
127
64
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
The cause is a catch that has to exist: `bin/voltro.mjs` imports the precompiled serve bundle inside a `try`, because an unusable bundle must degrade to the tsx path rather than kill the boot. It could not tell "this artefact is broken" from "this app decided not to start". Refusals now carry a marker (`bootRefusal.ts`) and the launcher prints them and exits 1.
|
|
65
|
+
**`.encrypted()` lands as ciphertext, byte-identical to the source column**, so versioning such a table widens nothing — the history is exactly as readable as the row it came from. Withholding it would have cost real audit data to prevent an exposure that does not exist.
|
|
131
66
|
|
|
132
|
-
|
|
67
|
+
**`.serverOnly()` is withheld**, and the reason is not "a second copy under different retention" — that argument is weak on its own, since the hash already sits in the source table. The decisive one: `crud.*` STRIPS `.serverOnly()` columns from every row it returns, and a snapshot would smuggle the same value back past that stripping inside a `json()` blob, where no column-level rule applies.
|
|
133
68
|
|
|
134
|
-
|
|
135
|
-
- **@voltro/
|
|
69
|
+
Withheld names are listed under `data._omitted`, so a reader can tell "this column was withheld" from "this column did not exist then". Both timings apply the same policy. `.sensitive()` is not involved: it is an export-masking marker for values that are legitimately readable in the app.
|
|
70
|
+
- **@voltro/cli** — A page that RE-EXPORTS its component (`export { default, renderMode } from '../page'`) no longer fails the codegen gate with "exports no default". The check required the literal words `as default`, so the one spelling that lets two routes share a screen without copying it was the one spelling it refused — and it refused in `voltro build`, while dev and tests stayed green because nothing prerenders there. `export { default as Screen }` is still correctly rejected: it renames the default away.
|
|
136
71
|
|
|
137
|
-
|
|
72
|
+
Two follow-ons from the same shape:
|
|
138
73
|
|
|
139
|
-
The message
|
|
140
|
-
- **@voltro/
|
|
74
|
+
- The refusal message said the file "ends in `.page.tsx`" and offered "drop the `.page` suffix" as a fix. That is the 0.15.0 convention, replaced by directory routing in 0.17.0 — it named a convention that no longer exists and a fix that could not work. It now names `page.tsx` and both real fixes. - `scanRenderProfile` read a forwarded `renderMode` as absent and fell back to `'static'`, so `staticSafe` and the deploy-target classification could call an app CDN-deployable with an `ssr` route in it. The forward is now followed (relative specifiers, depth-capped); an unresolvable one still falls back rather than failing the scan.
|
|
75
|
+
- **@voltro/database** — `VOLTRO_SOFT_DROP=1` could never converge. The applier renames the object to `<name>__dropped_<ts>` instead of dropping it, which leaves it undeclared — and the differ read that as one more forgotten table, planning the drop again. The re-plan inside `applyPlan` then found an operation still outstanding and aborted with "the DDL for these operations is a no-op — this is a framework bug", which was a wrong diagnosis of a real defect: the DDL had worked. No fingerprint was recorded, so the migration counted as unapplied and every later `db apply` / boot hit the same wall. The only exit was a hard drop of the snapshot — exactly the recoverability the flag is chosen for.
|
|
141
76
|
|
|
142
|
-
|
|
77
|
+
The planner now treats `<name>__dropped_<YYYYMMDDHHMMSS>` as framework-managed, alongside `_voltro_*` / `cluster_*`. Deliberately not retention-aware: a planner whose output depends on the clock would produce different plans before and after midnight, and `db gc-snapshots` already owns expiry.
|
|
143
78
|
|
|
144
|
-
|
|
79
|
+
Reported against tables; the same defect existed one level down for soft-dropped COLUMNS, where it was worse — a re-planned `drop-column` carries no `dropped()` marker and so refuses to plan at all. Both are fixed.
|
|
145
80
|
|
|
146
|
-
The
|
|
81
|
+
The convergence message itself no longer asserts a cause it cannot know. It said "the DDL for these operations is a no-op", which was flatly wrong here and sent the reporter looking for dead DDL. It now names both causes — no-op DDL, and a planner that cannot see what the DDL did — and says which one an operation naming a just-renamed object usually is.
|
|
147
82
|
|
|
148
83
|
### Internal (no consumer-facing effect)
|
|
149
84
|
|
|
150
|
-
-
|
|
85
|
+
- **The `0.20.0/01_write-recorder-port` codemod gains the gate test its two predecessors have.**
|
|
151
86
|
|
|
152
|
-
|
|
87
|
+
`codemodRegistry.test.ts` asserts that every `*.codemod.ts` on disk is registered and that ids are unique — registration, not behaviour. What it cannot see is the one way a `manual` codemod fails in practice: an `appliesTo` that is too broad, so the note prints for projects that have nothing to do. That is not a cosmetic problem. A note which fires on every app is how readers learn to skip notes, and the next one carries a boot refusal.
|
|
153
88
|
|
|
154
|
-
|
|
155
|
-
- **`ci.yml` gains a `workflow_dispatch` trigger.** The full matrix — 11 database services plus the SQL Server AG init containers — is not reachable from a push to `main`: `paths-ignore` plus the job-level `if: github.event_name != 'push'` mean a main push runs static checks only.
|
|
89
|
+
This codemod is the case where the silent direction matters most. The break is a TYPE error, so `tsc` already names every affected site; the note exists only to explain `maxOf`, which the compiler cannot. Apps that merely ENABLE `timing: 'in-transaction'` need to do nothing — `plugin-versioning` ships the recorder and it is already updated — and they are the large majority.
|
|
156
90
|
|
|
157
|
-
|
|
91
|
+
Four cases, covering both directions: a project registering its own recorder (note prints, and names `{ append }`, `maxOf`, and the `null`-is-not-zero distinction that a hand-written sequence gets wrong), an app that only enables the timing (silent), the identifier in a comment or a string (silent), and the generic call form `registerWriteRecorder<Row>(…)`, which `callPattern` admits and a naive match would miss.
|
|
@@ -11,16 +11,16 @@
|
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@effect/platform": "^0.96.1",
|
|
13
13
|
"@effect/rpc": "^0.75.1",
|
|
14
|
-
"@voltro/ai": "0.
|
|
15
|
-
"@voltro/cli": "0.
|
|
16
|
-
"@voltro/database": "0.
|
|
17
|
-
"@voltro/env": "0.
|
|
18
|
-
"@voltro/protocol": "0.
|
|
19
|
-
"@voltro/runtime": "0.
|
|
14
|
+
"@voltro/ai": "0.20.0",
|
|
15
|
+
"@voltro/cli": "0.20.0",
|
|
16
|
+
"@voltro/database": "0.20.0",
|
|
17
|
+
"@voltro/env": "0.20.0",
|
|
18
|
+
"@voltro/protocol": "0.20.0",
|
|
19
|
+
"@voltro/runtime": "0.20.0",
|
|
20
20
|
"effect": "^3.21.2"
|
|
21
21
|
},
|
|
22
22
|
"devDependencies": {
|
|
23
|
-
"@voltro/testing": "0.
|
|
23
|
+
"@voltro/testing": "0.20.0",
|
|
24
24
|
"typescript": "^5.7.0",
|
|
25
25
|
"vitest": "^3.0.0"
|
|
26
26
|
}
|
|
@@ -12,17 +12,17 @@
|
|
|
12
12
|
"dependencies": {
|
|
13
13
|
"@effect/platform": "^0.96.1",
|
|
14
14
|
"@effect/rpc": "^0.75.1",
|
|
15
|
-
"@voltro/cli": "0.
|
|
16
|
-
"@voltro/database": "0.
|
|
17
|
-
"@voltro/env": "0.
|
|
18
|
-
"@voltro/plugin-auth": "0.
|
|
19
|
-
"@voltro/protocol": "0.
|
|
20
|
-
"@voltro/runtime": "0.
|
|
21
|
-
"@voltro/sql-postgres": "0.
|
|
15
|
+
"@voltro/cli": "0.20.0",
|
|
16
|
+
"@voltro/database": "0.20.0",
|
|
17
|
+
"@voltro/env": "0.20.0",
|
|
18
|
+
"@voltro/plugin-auth": "0.20.0",
|
|
19
|
+
"@voltro/protocol": "0.20.0",
|
|
20
|
+
"@voltro/runtime": "0.20.0",
|
|
21
|
+
"@voltro/sql-postgres": "0.20.0",
|
|
22
22
|
"effect": "^3.21.2"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
|
-
"@voltro/testing": "0.
|
|
25
|
+
"@voltro/testing": "0.20.0",
|
|
26
26
|
"typescript": "^5.7.0",
|
|
27
27
|
"vitest": "^3.0.0"
|
|
28
28
|
}
|
|
@@ -12,16 +12,16 @@
|
|
|
12
12
|
"dependencies": {
|
|
13
13
|
"@effect/platform": "^0.96.1",
|
|
14
14
|
"@effect/rpc": "^0.75.1",
|
|
15
|
-
"@voltro/cli": "0.
|
|
16
|
-
"@voltro/database": "0.
|
|
17
|
-
"@voltro/env": "0.
|
|
18
|
-
"@voltro/plugin-multitenancy": "0.
|
|
19
|
-
"@voltro/protocol": "0.
|
|
20
|
-
"@voltro/runtime": "0.
|
|
15
|
+
"@voltro/cli": "0.20.0",
|
|
16
|
+
"@voltro/database": "0.20.0",
|
|
17
|
+
"@voltro/env": "0.20.0",
|
|
18
|
+
"@voltro/plugin-multitenancy": "0.20.0",
|
|
19
|
+
"@voltro/protocol": "0.20.0",
|
|
20
|
+
"@voltro/runtime": "0.20.0",
|
|
21
21
|
"effect": "^3.21.2"
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
24
|
-
"@voltro/testing": "0.
|
|
24
|
+
"@voltro/testing": "0.20.0",
|
|
25
25
|
"typescript": "^5.7.0",
|
|
26
26
|
"vitest": "^3.0.0"
|
|
27
27
|
}
|
|
@@ -12,16 +12,16 @@
|
|
|
12
12
|
"dependencies": {
|
|
13
13
|
"@effect/platform": "^0.96.1",
|
|
14
14
|
"@effect/rpc": "^0.75.1",
|
|
15
|
-
"@voltro/cli": "0.
|
|
16
|
-
"@voltro/database": "0.
|
|
17
|
-
"@voltro/env": "0.
|
|
18
|
-
"@voltro/plugin-deactivation": "0.
|
|
19
|
-
"@voltro/protocol": "0.
|
|
20
|
-
"@voltro/runtime": "0.
|
|
15
|
+
"@voltro/cli": "0.20.0",
|
|
16
|
+
"@voltro/database": "0.20.0",
|
|
17
|
+
"@voltro/env": "0.20.0",
|
|
18
|
+
"@voltro/plugin-deactivation": "0.20.0",
|
|
19
|
+
"@voltro/protocol": "0.20.0",
|
|
20
|
+
"@voltro/runtime": "0.20.0",
|
|
21
21
|
"effect": "^3.21.2"
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
24
|
-
"@voltro/testing": "0.
|
|
24
|
+
"@voltro/testing": "0.20.0",
|
|
25
25
|
"typescript": "^5.7.0",
|
|
26
26
|
"vitest": "^3.0.0"
|
|
27
27
|
}
|