@voltro/cli 0.58.0 → 0.60.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 +113 -0
- package/dist/apiBuild-B9OvhITV.js +2 -0
- package/dist/{apiBuild-CkpsCqIu.js → apiBuild-BU5cd-Fn.js} +50 -48
- package/dist/bin.js +1 -1
- package/dist/{build-51qBWfYY.js → build-BxAsLzyU.js} +347 -258
- package/dist/{checkCommand-C1EbNmwl.js → checkCommand-C1CQc8lD.js} +1 -1
- package/dist/{checkCommand-C30J2F18.js → checkCommand-DC7k1_hP.js} +1 -1
- package/dist/{codemodRunner-BKjCwGiN.js → codemodRunner-ClUqkhvR.js} +523 -390
- package/dist/{commands-DowWkYtd.js → commands-Ro4hYBHD.js} +12 -12
- package/dist/{dashboardCommand-gG1y4PFG.js → dashboardCommand-t8Qsebjr.js} +1 -1
- package/dist/{doctorCommand-5ULMpL10.js → doctorCommand-CGjefPmk.js} +2 -2
- package/dist/doctorCommand-DXnNpKQT.js +2 -0
- package/dist/{evolveCommand-DTawtTxf.js → evolveCommand-CThb0EZZ.js} +2 -2
- package/dist/index.js +1 -1
- package/dist/{infoCommand-BFksHunl.js → infoCommand-a0WkKcUY.js} +1 -1
- package/dist/{runtimeTrace-BUk5zMXY.js → runtimeTrace-DDuKJgVi.js} +1 -1
- package/dist/{sdkgen-CAhmmbjx.js → sdkgen-vq1fF9D_.js} +1 -1
- package/dist/serveCommand-1R52mo6F.js +2 -0
- package/dist/{serveCommand-DhtC7WBq.js → serveCommand-Dr4KXmTS.js} +1 -1
- package/dist/serveEntry.js +1 -1
- package/dist/{start-C75w6ftA.js → start-BhoDQ25C.js} +1 -1
- package/dist/{start-DNtIlBcn.js → start-CcdPRG8L.js} +418 -367
- package/dist/startEntry.js +1 -1
- package/dist/{updateCommand-CtuD5r1t.js → updateCommand-D9avpQFT.js} +1 -1
- package/dist/updateCommand-QMOirvXC.js +2 -0
- package/package.json +37 -19
- package/templates/AGENTS.md +1 -1
- package/templates/agent-docs/_index.md +1 -1
- package/templates/agent-docs/ai.md +50 -1
- package/templates/agent-docs/cli.md +7 -6
- package/templates/agent-docs/plugins/sentry.md +142 -16
- package/templates/agent-docs/whats-new.md +25 -168
- package/templates/apps/api-ai/package.json +6 -6
- 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-sqlite/package.json +8 -8
- package/templates/apps/api-backend-storage/package.json +8 -8
- package/templates/apps/api-cms/package.json +9 -9
- package/templates/apps/api-collab/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-row-history/package.json +8 -8
- package/templates/apps/api-saas/package.json +11 -11
- package/templates/apps/api-saas-starter/package.json +10 -10
- package/templates/apps/api-search/package.json +8 -8
- package/templates/apps/api-status/package.json +8 -8
- package/templates/apps/api-webhooks/package.json +9 -9
- package/templates/apps/changelog/package.json +7 -7
- package/templates/apps/edge-functions/package.json +2 -2
- package/templates/apps/frontend-admin/package.json +7 -7
- package/templates/apps/frontend-app/package.json +8 -8
- package/templates/apps/frontend-auth/package.json +7 -7
- package/templates/apps/frontend-blank/package.json +6 -6
- package/templates/apps/frontend-cms/package.json +8 -8
- package/templates/apps/frontend-collab/package.json +9 -9
- package/templates/apps/frontend-contact/package.json +7 -7
- package/templates/apps/frontend-dashboard/package.json +6 -6
- package/templates/apps/frontend-docs/package.json +8 -8
- package/templates/apps/frontend-i18n/package.json +6 -6
- package/templates/apps/frontend-landing/package.json +7 -7
- package/templates/apps/frontend-portal/package.json +7 -7
- package/templates/apps/frontend-saas/package.json +7 -7
- package/templates/apps/frontend-spa/package.json +6 -6
- package/templates/apps/frontend-ssr/package.json +6 -6
- package/templates/apps/frontend-ssr-api/package.json +7 -7
- package/templates/apps/frontend-static-blog/package.json +8 -8
- package/templates/apps/frontend-status/package.json +7 -7
- package/templates/apps/mobile-app/package.json +4 -4
- package/dist/apiBuild-CDE4NXNi.js +0 -2
- package/dist/doctorCommand-B8H2MYCE.js +0 -2
- package/dist/serveCommand-BUXUtSF5.js +0 -2
- package/dist/updateCommand-eVKVHj3N.js +0 -2
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# What's new in 0.
|
|
1
|
+
# What's new in 0.60.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,197 +9,54 @@ BREAKING entries name a codemod; run `voltro update` to apply it.
|
|
|
9
9
|
|
|
10
10
|
### ⚠ BREAKING
|
|
11
11
|
|
|
12
|
-
- **@voltro/
|
|
12
|
+
- **@voltro/plugin-sentry** — **A declared failure no longer reaches Sentry by default, and the source-map upload now injects debug ids.** Two separate defects, both found by the same deployment on its first day of real server-side events.
|
|
13
13
|
|
|
14
|
-
**The
|
|
14
|
+
**1. The contract was being reported as an incident.** The rpc interceptor skipped only clean interrupts; everything else went to `captureException`. So a failure declared in a procedure's `error:` union — the thing the client gets typed and branches on — arrived as `level: error`, `handled: yes`. The first server-side issue a deployment ever received was a person clicking a team they are not a member of.
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
Effect separates a failure from a defect, this framework leans on that split deliberately (a store refusal was made typed so an app could branch on it; an unlookupable conflict key was deliberately left a defect, because it is a broken call rather than a condition in the data), and a descriptor carries it in `error:`. Reporting both as an incident discarded that one layer up.
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
`shouldCapture` now skips a cause that is failures-ONLY. A defect is reported as before, including a defect that travelled beside a failure — the rule is failures-only rather than "any failure present" precisely so one cannot hide the other. `captureFailures: true` restores the old behaviour; a predicate keeps the ones that are signal.
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
**The browser half moved with it**, or the option would have been half-wired: a rejected call is an rpc error on the client too, published to the client error bus and captured by the browser bridge. `initSentryBrowser` takes the same option and applies it to `rpc.*` events carrying a `_tag`. Route render failures and `reportClientError` calls are never filtered — nobody declared those.
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
**2. `sentry-cli sourcemaps upload` does NOT write debug ids.** `inject` is a separate subcommand; `upload` only uses ids that are already present, and falls back to matching on the artifact NAME when they are not. That fallback cannot work for a server bundle: the artifact is named from `--url-prefix` (`~/chunk- ABC.js`) while the frame carries the absolute path the node process loaded, and nothing rewrites either side.
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
Measured downstream: 4300 artifacts uploaded, release finalised, every frame still minified. Nothing was red — the exact shape this code's own header warns about, an upload that matched nothing looking like one that worked. The comment above the uploader asserted the injection happened, which made it a description standing where a check belonged.
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
`inject` now runs first, over the same directories, and `sourcemapDebugIds.test.ts` drives the real binary to assert an id lands in both the JS and the map. `--url-prefix` stays as the fallback for the browser bundle, whose frames really are URLs.
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
The boot line names the new setting (`sentry active … captureFailures=false`), because a default the framework picks for you is one nobody finds again.
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
**`voltro update` carries you across this** — codemod `0.58.0/07_rerank_takes_a_provider_config`. If you pin versions by hand and never run it, print the notes without changing anything: `voltro update --codemods-only --from <your current version> --dry-run` (this one ships in 0.58.0).
|
|
33
|
-
- **@voltro/protocol, @voltro/plugin-auth-supabase, @voltro/cli, @voltro/voltro** — An auth strategy's shared secret may be a function, so `voltro build` no longer needs the app's production credentials.
|
|
34
|
-
|
|
35
|
-
`voltro build` imports `app.config.ts`, so anything the config constructs runs at build time. `supabaseStrategy({ jwtSecret: process.env.SUPABASE_JWT_SECRET })` therefore made an image build need a production secret for a bundle that will not contain one. A placeholder is not available — this framework ships no secret values anywhere, and an `ARG SUPABASE_JWT_SECRET` in a Dockerfile is exactly that — so a deployment stayed on the slower tsx path instead.
|
|
36
|
-
|
|
37
|
-
`jwtSecret` now accepts `string | (() => string | undefined)`, on `jwtBearerStrategy` and on the Supabase strategy that specialises it.
|
|
38
|
-
|
|
39
|
-
**The deferral is paired, and that pairing is the design.** A thunk alone would trade an image-build inconvenience for a runtime surprise: the failure of a MISSING secret would move from boot to the first request that happens to carry a token, and an auth strategy that cannot verify one accepts nobody — a process that started cleanly, serving a login that can never succeed. "Fail at boot" is what the env gate, the plugin env contracts and the migration guard all promise, and this is not the place to sell it.
|
|
40
|
-
|
|
41
|
-
So `AuthStrategy` gained an optional `verifyConfig()`, and the pass over the strategies that `voltro dev` and `voltro serve` already share calls it. A build never reaches that builder. Three moments, three answers: construction is silent, boot decides, verification uses the settled value — resolved once, because a credential re-read per request is a `process.env` hit on the hot path and a value that could change under a running process.
|
|
42
|
-
|
|
43
|
-
A thunk counts as PRESENT at construction: whether it resolves is a boot question, and calling it early to find out would undo the whole point. Passing no credential at all still refuses at construction, unchanged.
|
|
44
|
-
|
|
45
|
-
Migration: passing a value is unchanged; what can stop compiling is READING `jwtSecret` off a typed config, which is now the union. That is the mirror of "more precise is still breaking" — less precise breaks readers — and the population is small, because the interface is one users construct and a literal's own inferred type never widened. `@voltro/voltro` is listed because it re-exports the type.
|
|
46
|
-
|
|
47
|
-
**`voltro update` carries you across this** — codemod `0.58.0/06_jwt_secret_may_be_a_function`. If you pin versions by hand and never run it, print the notes without changing anything: `voltro update --codemods-only --from <your current version> --dry-run` (this one ships in 0.58.0).
|
|
48
|
-
- **@voltro/runtime, @voltro/cli** — A fleet view no longer counts rows left behind by processes that are gone, and the rows of live replicas actually refresh on MariaDB.
|
|
49
|
-
|
|
50
|
-
Three symptoms, one root cause and two that outlived it.
|
|
51
|
-
|
|
52
|
-
**The rows were frozen.** `writeReplicaObservation` upserts on `UNIQUE (replicaId, kind)` — a conflict key that is not the primary key, with a fresh `id` on every write — which is exactly the shape the MySQL/MariaDB upsert guard was rejecting. Every republish after the first was rolled back, so a publisher writing every 30s produced a row whose age grew second for second.
|
|
53
|
-
|
|
54
|
-
**The failure said nothing.** It was logged at `debug`, on the reasoning that a failed write leaves the previous row and the reader reports it as stale, so the failure is visible in the answer. It is not: a stale row is what a slow, busy or departed replica looks like, and the answer never says a write was REFUSED. It was also invisible where it mattered — `voltro dev` prints debug and `voltro serve` does not, so the unattended deployment got no line at all. The first failure per kind now warns.
|
|
55
|
-
|
|
56
|
-
**Nothing ever removed a row.** The table is keyed `(replicaId, kind)`, so a restart overwrites its own row and a replica that never returns leaves one forever — counted as a responder, counted in the version tally, and old enough to hold `complete` at false for the life of the deployment. Membership is the roster of who is alive, so a row whose replica it no longer lists is now reported as `departed`: separately from `stale`, because a stale row belongs to a process that is running and has stopped refreshing (a fault) and a departed one belongs to a process that is gone (a scale-down). It is reported rather than dropped — a reader who cannot see them cannot tell a fleet that scaled down from a table being written by something nobody is tracking. The publisher also forgets its own rows on shutdown, so the ordinary case leaves nothing to classify.
|
|
57
|
-
|
|
58
|
-
The sharpest consequence of the old behaviour was not the incompleteness: `versions` counted the framework version of a process that had been dead for hours and reported a version SPLIT across a fleet that had none — a line an operator acts on.
|
|
59
|
-
|
|
60
|
-
Migration: `FleetMergeResult.departed` is required, so code that CONSTRUCTS one — a test double for `fleetObservations` — needs the field. Reading one is unaffected. See the codemod note.
|
|
61
|
-
|
|
62
|
-
**`voltro update` carries you across this** — codemod `0.58.0/03_fleet_view_reports_departed_replicas`. If you pin versions by hand and never run it, print the notes without changing anything: `voltro update --codemods-only --from <your current version> --dry-run` (this one ships in 0.58.0).
|
|
63
|
-
- **@voltro/client, @voltro/web** — An rpc call that fails in the client now reaches the app's error REPORTERS, not only its rpc listeners.
|
|
64
|
-
|
|
65
|
-
There were two buses. `clientErrorBus` is the module-global seam a reporter subscribes to — the route ErrorBoundary publishes render and loader failures there, `reportClientError` publishes manual captures, and the Sentry browser integration listens. `RpcErrorBus` is a per-runtime bus with a different subscriber set, for cross-cutting POLICY: redirect on `Unauthenticated`, toast on a network failure. Nothing connected them, so a call that failed in the client reached policy and never reached reporting.
|
|
66
|
-
|
|
67
|
-
The subscription case is the one that costs. A rejected subscription leaves `data: undefined`, `loading: false` and an empty list — which is also what "nothing matched" looks like — so there is nothing in the UI to notice and nothing in any reporter to find.
|
|
68
|
-
|
|
69
|
-
The bridge is in `RpcErrorBus.emit`, not at the six call sites that emit rpc errors, so a seventh is covered by existing. `ClientErrorEvent.source` gained `rpc.mutation` / `rpc.action` / `rpc.subscription` as three separate labels rather than one `rpc`: a failed mutation is a write the user watched fail, and a failed subscription is a screen that silently never filled. The rpc tag and the `traceId` — the same id the server logged — travel in `context`.
|
|
70
|
-
|
|
71
|
-
Everything is published. A reporter that must not be flooded by a reconnect storm applies its own ceiling: the bus's job is to make the failure observable, and which failures are worth an event is a policy it cannot hold for every subscriber.
|
|
72
|
-
|
|
73
|
-
Migration: only an EXHAUSTIVE switch over `event.source` stops compiling. If you bridged the two buses by hand, delete that bridge or you will report twice — and keep whatever throttling you put around it.
|
|
74
|
-
|
|
75
|
-
`@voltro/web` is listed because it re-exports the type: the union widens in its golden too, and a consumer importing `ClientErrorEvent` from there is affected identically.
|
|
76
|
-
|
|
77
|
-
**`voltro update` carries you across this** — codemod `0.58.0/04_rpc_errors_reach_reporters`. If you pin versions by hand and never run it, print the notes without changing anything: `voltro update --codemods-only --from <your current version> --dry-run` (this one ships in 0.58.0).
|
|
78
|
-
- **@voltro/plugin-sentry, @voltro/cli** — Sentry performance traces are on by default, and the web entry no longer needs a config block to initialise them.
|
|
79
|
-
|
|
80
|
-
`sentryPlugin({ traces })` defaulted to `false` while the browser half defaulted `tracesSampleRate: 1.0` with `browserTracing` on. That pairing is the worst of the three available defaults: every page load, navigation and rpc call became a browser span that was emitted, paid for, and belonged to a trace with no server half — a browser hop hanging off nothing. Both-off would at least be coherent; both-on is what the two halves were built for, and the docs already said to pass `traces: true` to get it, which made that sentence a workaround note rather than a configuration option.
|
|
81
|
-
|
|
82
|
-
The rate stays 1.0 rather than being quietly lowered — a number the framework picks on your behalf is one nobody can find again — and the boot line names it: `sentry active traces=true tracesSampleRate=1`. Lower it with `tracesSampleRate`, or `traces: false` for errors and breadcrumbs only.
|
|
83
|
-
|
|
84
|
-
The browser half is reached the same way. The generated entry called `initSentryBrowser` only when `app.config.ts` carried a `sentry:` block, which made that block load-bearing for something it does not decide: the api half needs no config object (`sentryPlugin()` reads `SENTRY_DSN`) and the browser half reads `VOLTRO_PUBLIC_SENTRY_DSN`, so an app that had set its variables still had to add an empty `sentry: {}`. The entry now initialises whenever `@voltro/plugin-sentry` is a dependency of the web app — the dependency is the intent — and the block is for overrides. Without a DSN from either source the init returns immediately, so this changes nothing for an app that set none.
|
|
85
|
-
|
|
86
|
-
Migration: decide once whether you want the transactions. See the codemod note.
|
|
87
|
-
|
|
88
|
-
**`voltro update` carries you across this** — codemod `0.58.0/05_sentry_traces_on_by_default`. If you pin versions by hand and never run it, print the notes without changing anything: `voltro update --codemods-only --from <your current version> --dry-run` (this one ships in 0.58.0).
|
|
89
|
-
- **@voltro/runtime, @voltro/cli** — `SYSTEM_SUBJECT` — what a schedule, a subscriber, a resumed workflow and the webhook trigger context run as — is now a `system` subject. It was `{ type: 'anonymous', id: null }`, and every exemption for "the framework acting as itself" is keyed on `type === 'system'`: the row-filter bypass in `resolveRowFilterScopeFor`, the unfiltered-read refusal in `wrapStoreWithMixinBehaviour`, and `isSystemSubject`. It matched none of them.
|
|
90
|
-
|
|
91
|
-
An app that calls `setRowFilter` therefore had every schedule fire refused at context-build time, before its handler ran — on both boot paths, from the cron timer and from `voltro schedule run` alike. The quieter half is the one that produced no error: a path that RESOLVED a scope for this subject asked the app's own filter what an anonymous caller may see, and applied that answer to a cross-tenant sweep.
|
|
92
|
-
|
|
93
|
-
`ctx.storeForTenant(id)` is fixed alongside it, and needed saying separately. It narrows to a `serviceAccount` subject by design, so a subject whose null tenant means "every tenant" cannot widen back out of the tenant it was just confined to — which also means the exemption stops at the derivation. The row-filter bypass now travels across that narrowing; the tenant confinement does not.
|
|
94
|
-
|
|
95
|
-
Migration: `actingUserId` answers `null` for a system subject, so a `runAsSystem` write stamps no `createdBy` / `updatedBy` instead of `'system'`. `createdBy` is a reference into the app's own `actors` table and the framework seeds no row there, so an id it stamps is one nothing points at — an outright insert failure where that reference is declared. Schedules, subscribers and workflows already stamped nothing and are unchanged. See the codemod note.
|
|
96
|
-
|
|
97
|
-
**`voltro update` carries you across this** — codemod `0.58.0/01_system_writes_stamp_no_actor`. If you pin versions by hand and never run it, print the notes without changing anything: `voltro update --codemods-only --from <your current version> --dry-run` (this one ships in 0.58.0).
|
|
98
|
-
- **@voltro/runtime, @voltro/cli** — A workflow step now reads through its caller's row filter, resolved at execution time — and until now a workflow started by a user could not read at all.
|
|
99
|
-
|
|
100
|
-
The store refuses a context that carries neither a resolved row-filter scope nor a system subject. A BOOTSTRAP run has the system subject and passed; a run started by a user has neither, so in any app that calls `setRowFilter` every workflow step threw before its executor ran. The gap was invisible from the framework's own suites because the arm that works and the arm that does not differ only in who started the run.
|
|
101
|
-
|
|
102
|
-
Visibility is resolved in the same seam as authority, at the same moment, for the same reason: `resolveAuthority` re-reads what a three-day-old run may DO, and the scope resolved beside it re-reads what that run may SEE. Restoring either from the start-context row would be a fact frozen at start time and made durable. A failure to resolve fails the attempt rather than downgrading it — a run that silently reads fewer rows than it should is indistinguishable from one whose data is not there. An app that registered no filter pays nothing; the resolution short-circuits without touching a store.
|
|
103
|
-
|
|
104
|
-
`BuildAppContext`'s request parameter now declares `rowFilter`. It was read by the implementation and absent from the type, so the request arms were correct by the accident of passing a value the signature did not mention, while a non-request caller had no way to see there was anything to supply.
|
|
105
|
-
|
|
106
|
-
`ResolvedWorkflowCallerContext.rowFilter` is required, so code that CONSTRUCTS one — in practice a test double for a context builder — needs the field. Implementing `buildContext` is unaffected: it receives one more property it may ignore. Required rather than optional is deliberate: optional is what it effectively was, and it left a boot path able to omit the one value without which a user's workflow cannot run.
|
|
107
|
-
|
|
108
|
-
**`voltro update` carries you across this** — codemod `0.58.0/02_workflow_caller_context_carries_a_scope`. If you pin versions by hand and never run it, print the notes without changing anything: `voltro update --codemods-only --from <your current version> --dry-run` (this one ships in 0.58.0).
|
|
30
|
+
**`voltro update` carries you across this** — codemod `0.60.0/01_declared_failures_are_not_incidents`. If you pin versions by hand and never run it, print the notes without changing anything: `voltro update --codemods-only --from <your current version> --dry-run` (this one ships in 0.60.0).
|
|
109
31
|
|
|
110
32
|
### Added
|
|
111
33
|
|
|
112
|
-
- **@voltro/cli** —
|
|
113
|
-
|
|
114
|
-
The rule asks "is this column nullable?" by finding the `table('<name>', { … })` declaration among the sources it was handed — the app's. That is right for the app's own tables and cannot work for ours: `_voltro_webhook_targets` is declared inside `@voltro/plugin-webhooks`, so a mutation writing to it resolved to "I could not look". The rule keeps that answer distinct from "this table has no nullable columns", because the two lead to opposite conclusions — but for our tables it was a permanent unknown, leaving anyone auditing their mutations to carry them as a standing exception and count them by hand.
|
|
115
|
-
|
|
116
|
-
Doctor is a static analyser: it parses files and boots nothing, so it cannot import a plugin to ask a table about itself. The nullability of our own tables is therefore generated from our declarations — with the same extraction rule the doctor applies, so a manifest entry cannot mean something different from what the rule would have concluded — and shipped as data. `scripts/gen-framework-table-nullability.mjs --check` runs in CI, because a stale entry would answer with a value that is no longer declared, which is worse than the honest unknown it replaces.
|
|
117
|
-
|
|
118
|
-
An app's own declaration still wins where both have one: it is the more specific authority and the one a developer can actually edit.
|
|
119
|
-
- **@voltro/plugin-sentry, @voltro/cli** — The browser half of the Sentry integration can now resolve its own configuration — from the public env, and from a module that runs at boot.
|
|
120
|
-
|
|
121
|
-
**It reads `VOLTRO_PUBLIC_SENTRY_DSN` / `_ENVIRONMENT` / `_RELEASE`.** The api half already resolved `SENTRY_DSN`, `SENTRY_ENVIRONMENT` and `SENTRY_RELEASE` from the environment and declared all three, so `sentryPlugin()` takes no arguments. `initSentryBrowser` resolved nothing: without an explicit `dsn` it returned, and the only way to supply one was `sentry:` in `app.config.ts`. An explicit option still wins — `app.config.ts` is the more specific authority.
|
|
34
|
+
- **@voltro/cli, @voltro/plugin-sentry** — **The web server process now initialises Sentry and reports its own errors.**
|
|
122
35
|
|
|
123
|
-
|
|
36
|
+
A framework app runs two server processes. `voltro serve` is the api, where `sentryPlugin()` initialises the SDK through the plugin lifecycle. `voltro start` is the web server — SSR, loaders, ISR, the revalidation legs — and it has no plugin lifecycle, so it had none.
|
|
124
37
|
|
|
125
|
-
|
|
38
|
+
That was not a cosmetic difference in boot output. A web pod legitimately logs less than an api pod, because it has no store, no rpc, no scheduler, no workflows and no plugins. What it also had was **no error reporting**: an SSR shell throw is caught, logged and answered with a 500, so no browser ever renders it and the client-side ErrorBoundary bridge cannot see it either. Both ends of the integration worked and the middle was dark — while the docs' "React render error ✅ auto" row, true of the client path, read as covering all of them.
|
|
126
39
|
|
|
127
|
-
|
|
128
|
-
- **@voltro/cli, @voltro/plugin-sentry** — `voltro build` uploads source maps to Sentry and then removes them from the output.
|
|
40
|
+
Set `SENTRY_DSN` on the web deployment and the boot says `sentry active` with the same message and the same field names as the api half, from the same function — `initSentryServer` in `@voltro/plugin-sentry/server`, which the api plugin now calls too. One init, because a copy of `skipOpenTelemetrySetup`, the traces default, the overrides-first spread and the degrade-on-missing-SDK path would have drifted the moment either half gained a case.
|
|
129
41
|
|
|
130
|
-
|
|
42
|
+
Three deliberate asymmetries, each because the two processes are not the same thing:
|
|
131
43
|
|
|
132
|
-
-
|
|
44
|
+
- **`traces: false` on the web side.** The api contributes a span processor to the framework's tracer; the web server has no tracer at all, so tracing on would put `traces: true` in a boot line while nothing produces a span. - **No DSN is SILENT here.** On the api side `sentryPlugin()` is a declaration and an inert one contradicts it. There is no declaration here. - **`@voltro/plugin-sentry` must be installed in the WEB app.** With a DSN set and the package missing, the boot names the command rather than failing — monitoring must never be what stops a deploy.
|
|
133
45
|
|
|
134
|
-
|
|
135
|
-
web: { sourcemaps: { mode: 'hidden', upload: { org: 'acme', project: 'web' } } }
|
|
136
|
-
```
|
|
137
|
-
|
|
138
|
-
Selective by ordinary means: `app.config.ts` is TypeScript, so `sourcemaps: process.env.CI ? { … } : 'hidden'` emits-and-keeps locally and uploads in the pipeline. `keep` defaults to "keep them only if nothing consumed them" — false with an upload, true without.
|
|
139
|
-
|
|
140
|
-
**There is no `authToken` option**, on purpose. It is read from `SENTRY_AUTH_TOKEN`: `app.config.ts` is a committed file, and a token with `project:releases` scope can write to every project in the org. It belongs to the build, not the deployment.
|
|
141
|
-
|
|
142
|
-
The upload runs through `@sentry/cli` rather than a hand-written HTTP client, because Sentry's artifact model is debug-ID based — the ids are injected into the built JS and the map, and the matching happens on them. An uploader that skipped that injection would produce artifacts that upload cleanly and resolve nothing. **You install it: `pnpm add -D @sentry/cli` in the web app.** The framework does not depend on it anywhere, and that is a licence decision rather than a packaging one. `@sentry/cli` is FSL-1.1-MIT, which restricts competing commercial use — so unlike the MPL and LGPL dependencies we do carry, "the consumer's own install fetches it, and we neither distribute nor modify it" does not answer the question: FSL binds a user, not a distributor. An optional dependency of a package we publish is part of that package's licence surface, so it is not one.
|
|
143
|
-
|
|
144
|
-
A configured upload with the package absent refuses the build and names the command, rather than skipping the upload and leaving you to discover months later that no frame resolves.
|
|
46
|
+
Measured against a real production `voltro start`, both branches. Note that production `voltro start` loads the app's precompiled start bundle, so a web deployment picks this up when that bundle is rebuilt — the framework version alone is not enough.
|
|
145
47
|
|
|
146
48
|
### Fixed
|
|
147
49
|
|
|
148
|
-
- **@voltro/
|
|
149
|
-
|
|
150
|
-
Every failure the DATABASE reports already had this: `storeMiddleware` reads the driver's error and maps it. A refusal the framework decides had nothing to map from — there is no driver code for our own decision — so it stayed a plain `Error`, which Effect classifies as a defect. It arrived at the client as a `Defect` wrapping a stringified `InternalError`, untagged, for a condition an app can genuinely branch on. A form shows a support id over an input that saved nothing.
|
|
151
|
-
|
|
152
|
-
The MySQL-family upsert raises one: `ON DUPLICATE KEY UPDATE` fires on whichever unique key the incoming row violates, so the store checks afterwards that it reached the row the caller's `conflictColumns` name, and rolls back when it did not. `unique` is the kind — a row did collide on some unique key of that table; what the engine will not say is which, and that silence is the reason the check exists, so `constraint` stays absent rather than guessed at.
|
|
153
|
-
|
|
154
|
-
`ConstraintViolation` gained an optional `detail`, populated only by these refusals and never from a driver message or a row value. The generated sentence exists because the driver's own text carries row data on several dialects; that is a rule about the DRIVER's sentence, not about length, and a sentence the framework authored carries exactly what we put in it.
|
|
155
|
-
|
|
156
|
-
The mark is read BEFORE the driver-parser and never folded into it: giving our own decision the fields `classifyConstraintViolation` reads would make the two indistinguishable inside the one function whose contract is "this is what the database said".
|
|
157
|
-
|
|
158
|
-
Not everything the store throws becomes a failure. An upsert whose conflict key cannot be looked up at all — every column database-GENERATED — stays a defect, because it is a call that cannot be served as written rather than a condition in the data, and typing it would invite an app to swallow a wiring mistake as though it were a busy row.
|
|
159
|
-
- **@voltro/sql-mysql, @voltro/testing** — An upsert whose `conflictColumns` are NOT the primary key now works on MySQL and MariaDB. It threw, rolled back, and reported "Nothing was changed" — accurately, which is the only reason it was not worse.
|
|
160
|
-
|
|
161
|
-
Postgres names its target (`ON CONFLICT (a, b) DO UPDATE`); MySQL and MariaDB have no such form, so `ON DUPLICATE KEY UPDATE` fires on whichever unique key the incoming row violates. `id` is excluded from the update — it identifies the row and must not be silently rewritten — so the store checked afterwards that the returned row was the one it aimed at, by comparing the returned `id` with the one passed in.
|
|
162
|
-
|
|
163
|
-
That is the shape of the dangerous case, and it is also the shape of every correct update: an upsert that legitimately matches on the caller's own columns returns THAT row's id, never the freshly minted one. So the check fired on the case it existed to permit. The `op` classification one line below it (`id unchanged → insert, else update`) is written for a branch the throw made unreachable.
|
|
164
|
-
|
|
165
|
-
What decides it is the conflict-column VALUES, which are excluded from the update too and therefore still carry the matched row's own: equal to the input means the named key is what matched, and no other row can carry those values because the column set is unique. A difference means the statement was sent somewhere the named key could not have sent it — still refused, and the message now names the differing COLUMN instead of asserting a constraint it cannot identify. Not its values: that sentence reaches the client, and a conflict column is as likely to hold an email address as a team id — the same line `ConstraintViolation` draws when it refuses to carry the driver's own sentence. The values go to the server log, where the row already is. A conflict column the database GENERATES cannot be compared; when none of them can, that is said rather than resolved to "fine".
|
|
166
|
-
|
|
167
|
-
The parity suite gained the case that was missing: every upsert scenario in it conflicted on `id`, so the returned id always equalled the one sent and a store could not be caught being wrong about this. It now upserts on a composite unique key from a fresh id on every dialect, with a second row that must NOT be overwritten.
|
|
168
|
-
|
|
169
|
-
The LOOKUP half of the same operation — taken by every MySQL upsert, and on MariaDB by a partial row or a function `update` — now refuses clearly when a conflict column is database-GENERATED, instead of failing inside the driver.
|
|
170
|
-
|
|
171
|
-
It matches by value, and the value of a generated column is not in the row the caller passed: the classic partial-unique index on this engine IS such a column (a STORED expression plus NULL-distinct semantics, so the key constrains only the rows the expression marks). Binding the absent value reached mysql2 as `Bind parameters must not contain undefined` — a TypeError naming no column, no table and no cause, for a write that did nothing. Binding SQL NULL would make the predicate never true, so the lookup finds nothing and falls through to an INSERT the very key rejects; dropping the column would widen the lookup and can match a row the write would not have collided with, which is the one outcome that overwrites somebody else's data.
|
|
172
|
-
|
|
173
|
-
So it refuses, names the column, and points at the path that CAN answer: the single-statement form, where the database evaluates the expression itself. That path is reached on MariaDB with a complete insert row and a column-list `update`.
|
|
174
|
-
- **@voltro/kv** — Closing a RESP client can no longer take the process down.
|
|
175
|
-
|
|
176
|
-
A previous round added a silent `error` listener so an ioredis client without one could not crash the process. That is a different failure, and a deployment on `voltro dev` measured the difference: after the fix, roughly one reload in three still ended in `Error: Connection is closed.` thrown from `ioredis/built/redis/event_handler.js`, unhandled, with no framework frame in the stack.
|
|
177
|
-
|
|
178
|
-
That is not an `error` event. `event_handler.js`'s `close` is where ioredis REJECTS the promises of commands that were in flight when the socket went — and `quit()` is one of those commands. Calling it on a connection that is already closing rejects, and `close: () => c.quit().then(() => undefined)` had no `catch`. A reload closes the client while the previous instance is still shutting down, which is the race.
|
|
179
|
-
|
|
180
|
-
`close()` now cannot reject, and falls back to `disconnect()` so giving up on the error does not mean giving up on the teardown — a `quit` that failed has not necessarily closed anything, and a socket leaked per reload is its own slow failure. Swallowing is right here and nowhere else in that file: a failed COMMAND reaches its caller as a `KvError`, because that is where it can be acted on, and a failed close has no such place.
|
|
181
|
-
- **@voltro/plugin-broadcast** — A subscription's teardown can no longer kill the process.
|
|
182
|
-
|
|
183
|
-
`void sub.unsubscribe(channel)` in the dispose function returned by the redis broadcast provider was fire-and-forget with no `catch`. ioredis rejects the promises of every command in flight when the socket closes — `Connection is closed.`, thrown from its own close handler — so a `voltro dev` reload, which disposes subscriptions while the previous connection is going away, produced an unhandled rejection and node killed the process.
|
|
184
|
-
|
|
185
|
-
This is the THIRD round on one symptom with three different causes: a client with no `error` listener, `quit()`'s unguarded promise, and now a discarded command. Each fix was correct and none of them was the next one, so this round adds a rule instead of a fourth fix: `scripts/check-resp-fire-and-forget.mjs` (CI + gate, with a `--selftest`) fails when a discarded RESP command has no `.catch`.
|
|
186
|
-
|
|
187
|
-
The rule is deliberately narrow. The general property is `no-floating-promises`, which this repo has no eslint to run — and measured before writing it, a blanket `void <call>` rule matches **194** sites in `packages/*/src`, nearly all legitimate. The RESP subset matches 3, all correct. Widening the command list to "any method" costs the 194; the header says so, next to the number.
|
|
188
|
-
|
|
189
|
-
The local half of the teardown still runs unconditionally: detaching the message handler is synchronous and cannot fail, and it is what stops a reloaded module receiving the previous one's messages.
|
|
190
|
-
- **@voltro/runtime** — A schedule firing that is rejected before its handler runs no longer outlives itself. `runHandler` wrote the `running` row, armed the watchdog, armed the heartbeat, and only then built the app context — with the `try` whose `finally` releases all three starting after them. The context build is the one step in that window that can throw on input the scheduler does not control, and when it did, each acquisition survived in its own way:
|
|
191
|
-
|
|
192
|
-
- the watchdog rejected a promise `Promise.race` never received, `maxRuntimeMs` after a firing that never started. Nothing was listening, so the rejection went unhandled and ended the process — with no recorded failure in between to connect the two events. Lowering `maxRuntimeMs`, the obvious response to a run that appears stuck, only widened the gap; - the heartbeat kept stamping `heartbeatAt` on a run that had not begun. That beat is what tells a live run from a corpse, so it certified one: a peer's overlap guard reads a beating row as alive for the life of the process, turning a bounded stale window into a permanent skip for that schedule; - the `catch` never ran either, so the row stayed `running` — the only artefact the firing left behind, saying the opposite of what happened.
|
|
50
|
+
- **@voltro/workflow, @voltro/runtime, @voltro/database, @voltro/cli, @voltro/plugin-sentry** — **Six raw writes removed from production log streams — and the guard that was supposed to catch them rewritten, because it was green for two independent reasons.**
|
|
193
51
|
|
|
194
|
-
|
|
195
|
-
- **@voltro/runtime, @voltro/cli** — A `defineStream`'s declared `guards:` are enforced — before the executor runs.
|
|
52
|
+
A pod tail showed `[voltro:workflow] shard-lock coordination: row-based (dialect=mariadb, mode=row)` sitting between JSON records. `@voltro/logger` is what makes a line JSON in a pod and pretty on a TTY; a `process.stderr.write` bypasses that decision at exactly the place nobody looks, because a dev terminal renders both the same.
|
|
196
53
|
|
|
197
|
-
|
|
54
|
+
Fixed at the source: the workflow cluster layer (2), `rpcServer`'s computed-cache warning, the subscription outbox's and the RYW store's `warn`/`onError` defaults — those two are not fallbacks, the callers pass nothing, so the default IS the production path — and the migration file discovery's skip notice, which lands in the migrate job's stream.
|
|
198
55
|
|
|
199
|
-
|
|
56
|
+
**The guard is the part worth reading.** `prodLogDiscipline.test.ts` existed for this exact class and reported clean, for two reasons that had to be fixed separately:
|
|
200
57
|
|
|
201
|
-
|
|
58
|
+
- Its file set was a hand-written list of five. A guard that opts files IN says nothing about any file added after it was written. - Its matcher was LINE-LOCAL, so `process.stderr.write(` on one line and the `` `[tag] `` on the next never matched — 4 of the 9 call sites in the repo are written that way, including one in a file that WAS on the list. The guard had been pointed straight at an offender and called it clean.
|
|
202
59
|
|
|
203
|
-
|
|
60
|
+
It is opt-OUT now: every server-side package's source is scanned, exceptions carry a reason, and a second test fails if an exception's call site disappears — an allowlist entry for code that is gone reads as a rule with a hole in it.
|
|
204
61
|
|
|
205
|
-
|
|
62
|
+
Verified against a real `voltro serve` under `NODE_ENV=production`: 35 records, zero framework lines that are not JSON.
|
|
@@ -12,12 +12,12 @@
|
|
|
12
12
|
"dependencies": {
|
|
13
13
|
"@effect/platform": "^0.97.0",
|
|
14
14
|
"@effect/rpc": "^0.76.0",
|
|
15
|
-
"@voltro/ai": "0.
|
|
16
|
-
"@voltro/cli": "0.
|
|
17
|
-
"@voltro/database": "0.
|
|
18
|
-
"@voltro/env": "0.
|
|
19
|
-
"@voltro/protocol": "0.
|
|
20
|
-
"@voltro/runtime": "0.
|
|
15
|
+
"@voltro/ai": "0.60.0",
|
|
16
|
+
"@voltro/cli": "0.60.0",
|
|
17
|
+
"@voltro/database": "0.60.0",
|
|
18
|
+
"@voltro/env": "0.60.0",
|
|
19
|
+
"@voltro/protocol": "0.60.0",
|
|
20
|
+
"@voltro/runtime": "0.60.0",
|
|
21
21
|
"effect": "^3.22.0"
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
@@ -13,17 +13,17 @@
|
|
|
13
13
|
"dependencies": {
|
|
14
14
|
"@effect/platform": "^0.97.0",
|
|
15
15
|
"@effect/rpc": "^0.76.0",
|
|
16
|
-
"@voltro/cli": "0.
|
|
17
|
-
"@voltro/database": "0.
|
|
18
|
-
"@voltro/env": "0.
|
|
19
|
-
"@voltro/plugin-auth": "0.
|
|
20
|
-
"@voltro/protocol": "0.
|
|
21
|
-
"@voltro/runtime": "0.
|
|
22
|
-
"@voltro/sql-postgres": "0.
|
|
16
|
+
"@voltro/cli": "0.60.0",
|
|
17
|
+
"@voltro/database": "0.60.0",
|
|
18
|
+
"@voltro/env": "0.60.0",
|
|
19
|
+
"@voltro/plugin-auth": "0.60.0",
|
|
20
|
+
"@voltro/protocol": "0.60.0",
|
|
21
|
+
"@voltro/runtime": "0.60.0",
|
|
22
|
+
"@voltro/sql-postgres": "0.60.0",
|
|
23
23
|
"effect": "^3.22.0"
|
|
24
24
|
},
|
|
25
25
|
"devDependencies": {
|
|
26
|
-
"@voltro/testing": "0.
|
|
26
|
+
"@voltro/testing": "0.60.0",
|
|
27
27
|
"typescript": "^6.0.3",
|
|
28
28
|
"@vitest/coverage-v8": "^4.1.10",
|
|
29
29
|
"vitest": "^4.1.10"
|
|
@@ -16,16 +16,16 @@
|
|
|
16
16
|
"dependencies": {
|
|
17
17
|
"@effect/platform": "^0.97.0",
|
|
18
18
|
"@effect/rpc": "^0.76.0",
|
|
19
|
-
"@voltro/cli": "0.
|
|
20
|
-
"@voltro/database": "0.
|
|
21
|
-
"@voltro/env": "0.
|
|
22
|
-
"@voltro/plugin-multitenancy": "0.
|
|
23
|
-
"@voltro/protocol": "0.
|
|
24
|
-
"@voltro/runtime": "0.
|
|
19
|
+
"@voltro/cli": "0.60.0",
|
|
20
|
+
"@voltro/database": "0.60.0",
|
|
21
|
+
"@voltro/env": "0.60.0",
|
|
22
|
+
"@voltro/plugin-multitenancy": "0.60.0",
|
|
23
|
+
"@voltro/protocol": "0.60.0",
|
|
24
|
+
"@voltro/runtime": "0.60.0",
|
|
25
25
|
"effect": "^3.22.0"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
|
-
"@voltro/testing": "0.
|
|
28
|
+
"@voltro/testing": "0.60.0",
|
|
29
29
|
"typescript": "^6.0.3",
|
|
30
30
|
"@vitest/coverage-v8": "^4.1.10",
|
|
31
31
|
"vitest": "^4.1.10"
|
|
@@ -13,16 +13,16 @@
|
|
|
13
13
|
"dependencies": {
|
|
14
14
|
"@effect/platform": "^0.97.0",
|
|
15
15
|
"@effect/rpc": "^0.76.0",
|
|
16
|
-
"@voltro/cli": "0.
|
|
17
|
-
"@voltro/database": "0.
|
|
18
|
-
"@voltro/env": "0.
|
|
19
|
-
"@voltro/plugin-deactivation": "0.
|
|
20
|
-
"@voltro/protocol": "0.
|
|
21
|
-
"@voltro/runtime": "0.
|
|
16
|
+
"@voltro/cli": "0.60.0",
|
|
17
|
+
"@voltro/database": "0.60.0",
|
|
18
|
+
"@voltro/env": "0.60.0",
|
|
19
|
+
"@voltro/plugin-deactivation": "0.60.0",
|
|
20
|
+
"@voltro/protocol": "0.60.0",
|
|
21
|
+
"@voltro/runtime": "0.60.0",
|
|
22
22
|
"effect": "^3.22.0"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
|
-
"@voltro/testing": "0.
|
|
25
|
+
"@voltro/testing": "0.60.0",
|
|
26
26
|
"typescript": "^6.0.3",
|
|
27
27
|
"@vitest/coverage-v8": "^4.1.10",
|
|
28
28
|
"vitest": "^4.1.10"
|
|
@@ -13,18 +13,18 @@
|
|
|
13
13
|
"dependencies": {
|
|
14
14
|
"@react-email/components": "^1.0.12",
|
|
15
15
|
"@react-email/render": "^1.4.0",
|
|
16
|
-
"@voltro/cli": "0.
|
|
17
|
-
"@voltro/database": "0.
|
|
18
|
-
"@voltro/env": "0.
|
|
19
|
-
"@voltro/plugin-mail": "0.
|
|
20
|
-
"@voltro/plugin-multitenancy": "0.
|
|
21
|
-
"@voltro/protocol": "0.
|
|
22
|
-
"@voltro/runtime": "0.
|
|
16
|
+
"@voltro/cli": "0.60.0",
|
|
17
|
+
"@voltro/database": "0.60.0",
|
|
18
|
+
"@voltro/env": "0.60.0",
|
|
19
|
+
"@voltro/plugin-mail": "0.60.0",
|
|
20
|
+
"@voltro/plugin-multitenancy": "0.60.0",
|
|
21
|
+
"@voltro/protocol": "0.60.0",
|
|
22
|
+
"@voltro/runtime": "0.60.0",
|
|
23
23
|
"effect": "^3.22.0",
|
|
24
24
|
"react": "^19.0.0"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|
|
27
|
-
"@voltro/testing": "0.
|
|
27
|
+
"@voltro/testing": "0.60.0",
|
|
28
28
|
"typescript": "^6.0.3",
|
|
29
29
|
"@vitest/coverage-v8": "^4.1.10",
|
|
30
30
|
"vitest": "^4.1.10"
|
|
@@ -13,18 +13,18 @@
|
|
|
13
13
|
"dependencies": {
|
|
14
14
|
"@effect/platform": "^0.97.0",
|
|
15
15
|
"@effect/rpc": "^0.76.0",
|
|
16
|
-
"@voltro/cli": "0.
|
|
17
|
-
"@voltro/database": "0.
|
|
18
|
-
"@voltro/env": "0.
|
|
19
|
-
"@voltro/plugin-multitenancy": "0.
|
|
20
|
-
"@voltro/plugin-storage": "0.
|
|
21
|
-
"@voltro/protocol": "0.
|
|
22
|
-
"@voltro/runtime": "0.
|
|
23
|
-
"@voltro/sql-mysql": "0.
|
|
16
|
+
"@voltro/cli": "0.60.0",
|
|
17
|
+
"@voltro/database": "0.60.0",
|
|
18
|
+
"@voltro/env": "0.60.0",
|
|
19
|
+
"@voltro/plugin-multitenancy": "0.60.0",
|
|
20
|
+
"@voltro/plugin-storage": "0.60.0",
|
|
21
|
+
"@voltro/protocol": "0.60.0",
|
|
22
|
+
"@voltro/runtime": "0.60.0",
|
|
23
|
+
"@voltro/sql-mysql": "0.60.0",
|
|
24
24
|
"effect": "^3.22.0"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|
|
27
|
-
"@voltro/testing": "0.
|
|
27
|
+
"@voltro/testing": "0.60.0",
|
|
28
28
|
"typescript": "^6.0.3",
|
|
29
29
|
"@vitest/coverage-v8": "^4.1.10",
|
|
30
30
|
"vitest": "^4.1.10"
|
|
@@ -13,17 +13,17 @@
|
|
|
13
13
|
"dependencies": {
|
|
14
14
|
"@effect/platform": "^0.97.0",
|
|
15
15
|
"@effect/rpc": "^0.76.0",
|
|
16
|
-
"@voltro/cli": "0.
|
|
17
|
-
"@voltro/database": "0.
|
|
18
|
-
"@voltro/env": "0.
|
|
19
|
-
"@voltro/plugin-multitenancy": "0.
|
|
20
|
-
"@voltro/protocol": "0.
|
|
21
|
-
"@voltro/runtime": "0.
|
|
22
|
-
"@voltro/sql-sqlite": "0.
|
|
16
|
+
"@voltro/cli": "0.60.0",
|
|
17
|
+
"@voltro/database": "0.60.0",
|
|
18
|
+
"@voltro/env": "0.60.0",
|
|
19
|
+
"@voltro/plugin-multitenancy": "0.60.0",
|
|
20
|
+
"@voltro/protocol": "0.60.0",
|
|
21
|
+
"@voltro/runtime": "0.60.0",
|
|
22
|
+
"@voltro/sql-sqlite": "0.60.0",
|
|
23
23
|
"effect": "^3.22.0"
|
|
24
24
|
},
|
|
25
25
|
"devDependencies": {
|
|
26
|
-
"@voltro/testing": "0.
|
|
26
|
+
"@voltro/testing": "0.60.0",
|
|
27
27
|
"typescript": "^6.0.3",
|
|
28
28
|
"@vitest/coverage-v8": "^4.1.10",
|
|
29
29
|
"vitest": "^4.1.10"
|
|
@@ -11,17 +11,17 @@
|
|
|
11
11
|
"test": "voltro test"
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"@voltro/cli": "0.
|
|
15
|
-
"@voltro/database": "0.
|
|
16
|
-
"@voltro/env": "0.
|
|
17
|
-
"@voltro/plugin-multitenancy": "0.
|
|
18
|
-
"@voltro/plugin-storage": "0.
|
|
19
|
-
"@voltro/protocol": "0.
|
|
20
|
-
"@voltro/runtime": "0.
|
|
14
|
+
"@voltro/cli": "0.60.0",
|
|
15
|
+
"@voltro/database": "0.60.0",
|
|
16
|
+
"@voltro/env": "0.60.0",
|
|
17
|
+
"@voltro/plugin-multitenancy": "0.60.0",
|
|
18
|
+
"@voltro/plugin-storage": "0.60.0",
|
|
19
|
+
"@voltro/protocol": "0.60.0",
|
|
20
|
+
"@voltro/runtime": "0.60.0",
|
|
21
21
|
"effect": "^3.22.0"
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
24
|
-
"@voltro/testing": "0.
|
|
24
|
+
"@voltro/testing": "0.60.0",
|
|
25
25
|
"typescript": "^6.0.3",
|
|
26
26
|
"@vitest/coverage-v8": "^4.1.10",
|
|
27
27
|
"vitest": "^4.1.10"
|
|
@@ -13,18 +13,18 @@
|
|
|
13
13
|
"dependencies": {
|
|
14
14
|
"@effect/platform": "^0.97.0",
|
|
15
15
|
"@effect/rpc": "^0.76.0",
|
|
16
|
-
"@voltro/cli": "0.
|
|
17
|
-
"@voltro/cms": "0.
|
|
18
|
-
"@voltro/database": "0.
|
|
19
|
-
"@voltro/env": "0.
|
|
20
|
-
"@voltro/plugin-auth": "0.
|
|
21
|
-
"@voltro/protocol": "0.
|
|
22
|
-
"@voltro/runtime": "0.
|
|
23
|
-
"@voltro/sql-postgres": "0.
|
|
16
|
+
"@voltro/cli": "0.60.0",
|
|
17
|
+
"@voltro/cms": "0.60.0",
|
|
18
|
+
"@voltro/database": "0.60.0",
|
|
19
|
+
"@voltro/env": "0.60.0",
|
|
20
|
+
"@voltro/plugin-auth": "0.60.0",
|
|
21
|
+
"@voltro/protocol": "0.60.0",
|
|
22
|
+
"@voltro/runtime": "0.60.0",
|
|
23
|
+
"@voltro/sql-postgres": "0.60.0",
|
|
24
24
|
"effect": "^3.22.0"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|
|
27
|
-
"@voltro/testing": "0.
|
|
27
|
+
"@voltro/testing": "0.60.0",
|
|
28
28
|
"typescript": "^6.0.3",
|
|
29
29
|
"@vitest/coverage-v8": "^4.1.10",
|
|
30
30
|
"vitest": "^4.1.10"
|
|
@@ -13,17 +13,17 @@
|
|
|
13
13
|
"dependencies": {
|
|
14
14
|
"@effect/platform": "^0.97.0",
|
|
15
15
|
"@effect/rpc": "^0.76.0",
|
|
16
|
-
"@voltro/cli": "0.
|
|
17
|
-
"@voltro/database": "0.
|
|
18
|
-
"@voltro/env": "0.
|
|
19
|
-
"@voltro/local-first": "0.
|
|
20
|
-
"@voltro/plugin-multitenancy": "0.
|
|
21
|
-
"@voltro/protocol": "0.
|
|
22
|
-
"@voltro/runtime": "0.
|
|
16
|
+
"@voltro/cli": "0.60.0",
|
|
17
|
+
"@voltro/database": "0.60.0",
|
|
18
|
+
"@voltro/env": "0.60.0",
|
|
19
|
+
"@voltro/local-first": "0.60.0",
|
|
20
|
+
"@voltro/plugin-multitenancy": "0.60.0",
|
|
21
|
+
"@voltro/protocol": "0.60.0",
|
|
22
|
+
"@voltro/runtime": "0.60.0",
|
|
23
23
|
"effect": "^3.22.0"
|
|
24
24
|
},
|
|
25
25
|
"devDependencies": {
|
|
26
|
-
"@voltro/testing": "0.
|
|
26
|
+
"@voltro/testing": "0.60.0",
|
|
27
27
|
"typescript": "^6.0.3",
|
|
28
28
|
"@vitest/coverage-v8": "^4.1.10",
|
|
29
29
|
"vitest": "^4.1.10"
|