Repro (2.0.0-beta.55)
The class-form Worker/StaticSite pattern relies on a plain props literal so InferEnv<typeof Website> can infer the binding map. Stage-dependent values are therefore expressed as per-field Effect inputs (the InputProps machinery), not by branching the whole props object:
export class Website extends Cloudflare.StaticSite<Website>()("website", {
// ...
url: Alchemy.Stack.useSync(stack => stack.stage !== "prod"),
domain: Alchemy.Stack.useSync(stack => (stack.stage === "prod" ? "example.com" : undefined)),
env: { /* bindings — per-field Effects here resolve fine */ },
})
Deploying any stage fails at the final step:
[Worker] Reconciling custom domains (1) ...
[Worker] fail
ERROR: SchemaError: Expected string | undefined, got {"~effect/Effect/args":(fiber) => ...}
at ["hostname"]
at GET /accounts/{account_id}/workers/domains
Cause
Worker.ts feeds the raw prop into normalizeDomains(news.domain) without resolving Input effects in that path. Env bindings and other props resolve fine — this domain path reads news directly. Two consequences:
- The unresolved Effect object is neither
undefined nor an array, so it normalizes to a one-element domain list — reconciliation runs even on stages where the value would resolve to undefined.
- The object reaches the API client as
hostname and fails schema validation.
news.url !== false a few lines down looks like the same unresolved read — an Effect object is truthy, so a url that resolves to false on some stages would still enable the workers.dev subdomain there.
Why per-field inputs (not in-generator construction)
The obvious alternative — construct the Worker inside the stack generator with different props per stage — breaks InferEnv: once the props object is produced by a generator rather than a literal, the binding-map type is lost, so Cloudflare.InferEnv<typeof Website> (used to type env.d.ts) no longer works. Per-field Effect inputs are the only shape that keeps the class-form inference and lets values vary by stage. That makes consistent input resolution across props a requirement of the class form you ship, not a request for a new pattern.
Expected
domain and url resolve per-field Effect inputs on the provider path the same way env bindings already do.
Happy to PR the fix if you point me at the intended resolution point for that path (same fork as #586/#587).
Repro (2.0.0-beta.55)
The class-form Worker/StaticSite pattern relies on a plain props literal so
InferEnv<typeof Website>can infer the binding map. Stage-dependent values are therefore expressed as per-field Effect inputs (theInputPropsmachinery), not by branching the whole props object:Deploying any stage fails at the final step:
Cause
Worker.tsfeeds the raw prop intonormalizeDomains(news.domain)without resolving Input effects in that path. Env bindings and other props resolve fine — this domain path readsnewsdirectly. Two consequences:undefinednor an array, so it normalizes to a one-element domain list — reconciliation runs even on stages where the value would resolve toundefined.hostnameand fails schema validation.news.url !== falsea few lines down looks like the same unresolved read — an Effect object is truthy, so aurlthat resolves tofalseon some stages would still enable the workers.dev subdomain there.Why per-field inputs (not in-generator construction)
The obvious alternative — construct the Worker inside the stack generator with different props per stage — breaks
InferEnv: once the props object is produced by a generator rather than a literal, the binding-map type is lost, soCloudflare.InferEnv<typeof Website>(used to typeenv.d.ts) no longer works. Per-field Effect inputs are the only shape that keeps the class-form inference and lets values vary by stage. That makes consistent input resolution across props a requirement of the class form you ship, not a request for a new pattern.Expected
domainandurlresolve per-field Effect inputs on the provider path the same wayenvbindings already do.Happy to PR the fix if you point me at the intended resolution point for that path (same fork as #586/#587).