fix(core): resolve Config values in input props - #566
Merged
Conversation
john-royal
force-pushed
the
john/handle-dev-config
branch
from
June 7, 2026 00:27
ee5d81d to
1ccab5c
Compare
Contributor
|
Install the packages built from this commit: alchemy bun add alchemy@https://pkg.ing/alchemy/32e9ed0@alchemy.run/better-auth bun add @alchemy.run/better-auth@https://pkg.ing/@alchemy.run/better-auth/32e9ed0@alchemy.run/pr-package bun add @alchemy.run/pr-package@https://pkg.ing/@alchemy.run/pr-package/32e9ed0 |
john-royal
marked this pull request as ready for review
June 7, 2026 00:50
john-royal
commented
Jun 7, 2026
Comment on lines
+85
to
86
| } else if (Redacted.isRedacted(binding)) { | ||
| const val = Redacted.value(binding); |
Contributor
Author
There was a problem hiding this comment.
This now type checks without needing a separate helper 🎉
val evaluates to type Json.
sam-goodwin
reviewed
Jun 9, 2026
| typeof Redacted.value(value) === "string" | ||
| ? Redacted.value(value) | ||
| : Config.isConfig(value) || Effect.isEffect(value) | ||
| : Effect.isEffect(value) |
sam-goodwin
approved these changes
Jun 9, 2026
agcty
added a commit
to agcty/alchemy-effect
that referenced
this pull request
Jun 11, 2026
Input<T> admits Effect<T> for any prop, but neither resolveInput
(Plan.ts) nor Output.evaluate (Output.ts) had a branch for raw
Effects: plan-time diffing saw the unresolved Effect object (tripping
providers' isResolved guards into conservative updates) and apply-time
evaluation shredded it via the generic object walk into a plain
`{"~effect/Effect/args": ...}` object that reached provider APIs —
e.g. the Worker provider's domain reconciliation, where a
stage-conditional `domain: Stack.useSync(...)` crashed the Cloudflare
client's schema validation, and a value that should have resolved to
`undefined` was truthy and entered reconciliation anyway.
Resolve raw Effects in both walkers, mirroring how Config values are
resolved (alchemy-run#566). Function-form Effects (resource classes, effectClass
constructors, Binding.Policy tags) stay opaque: they are class
references carried in props (e.g. Worker `exports`), not per-field
values.
Fixes alchemy-run#588
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
agcty
added a commit
to agcty/alchemy-effect
that referenced
this pull request
Jun 11, 2026
Input<T> admits Effect<T> for any prop, but neither resolveInput
(Plan.ts) nor Output.evaluate (Output.ts) had a branch for raw
Effects: plan-time diffing saw the unresolved Effect object (tripping
providers' isResolved guards into conservative updates) and apply-time
evaluation shredded it via the generic object walk into a plain
`{"~effect/Effect/args": ...}` object that reached provider APIs —
e.g. the Worker provider's domain reconciliation, where a
stage-conditional `domain: Stack.useSync(...)` crashed the Cloudflare
client's schema validation, and a value that should have resolved to
`undefined` was truthy and entered reconciliation anyway.
Resolve raw Effects in both walkers, mirroring how Config values are
resolved (alchemy-run#566), with two carve-outs for Effects that are resource
references rather than per-field values:
- Function-form Effects (resource classes, effectClass constructors,
Binding.Policy tags) stay opaque: they are class references carried
in props (e.g. Worker `exports`).
- Non-class resource references (`const db = Hyperdrive("db", ...)`)
are object-form Effects. The Resource constructor now brands them
(`alchemy/ResourceEffect`) and the walkers leave them opaque:
executing one outside the construction phase re-derives its FQN from
the ambient namespace (none at plan/apply time) and mints a phantom
resource — `MissingSourceError: Source db not found` when a Worker's
env holds `ADMIN_DB: db`. Branded references also count as resolved
in Diff.isResolved so providers' custom diffs keep running, and the
Worker vite env mapping skips them.
Plain Effects that wrap a resource reference (`AI_GATEWAY_ID:
Effect.map(aiGateway, g => g.gatewayId)`) cannot be detected without
executing them, so the Resource constructor additionally memoizes its
result per stack: re-execution during input resolution returns the
resource registered during construction instead of re-deriving the FQN,
and the resulting attribute expression resolves through the normal
Output machinery.
Fixes alchemy-run#588
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
DavidJFelix
pushed a commit
to DavidJFelix/alchemy-effect
that referenced
this pull request
Aug 7, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Configvalues are validInputs, but generic input resolution never handled them.resolveInputwalked theConfigobject withObject.entries, stripping its prototype down to its enumerableparseproperty — so providers received{ parse }instead of the configured value (andConfig.isConfigreturnedfalseon it downstream).This went unnoticed for a Worker's
envbecause those are resolved out of band by the bindings mechanism (yieldable values are resolved before binding metadata is derived). It surfaced when a user setdev.portto aConfig— theInputtype says that's allowed, but nothing actually resolved it.Resolve
Configagainst the deploy environment during input resolution instead of walking it:Resolving in plan (rather than per-provider) also means the concrete value participates in diffing/hashing — an opaque
Confighashes the same regardless of the underlying value, so env changes never triggered an update.Config.redactedresolves to aRedacted, which stays wrapped end-to-end.Notes
RedactedandDurationare unchanged (still opaque).Effectinputs are intentionally left unresolved for now.Confighandling in the Cloudflare Worker provider (Worker.tsvitedefine,WorkerAsyncBindings.tstoBinding).Output.evaluate/resolveInputnow surfaceConfig.ConfigError, which the deploy pipeline already threads.Tests
Output.test.ts: newConfigblock — top-level / nested / array resolution, resolution against aConfigProvider, andConfig→Redactedstaying wrapped.plan.test.ts: new block driving theresolveInputpath viaplan.resources.*.props.