A tagged Effect-native resource declared as a bare tag gets both its props and its implementation from .make(props, impl). Yielding the class without that Layer in scope hands the provider news === undefined, so the failure surfaces as an opaque TypeError deep inside the provider instead of an error naming the class and the missing Layer.
For a Cloudflare Worker:
export class ApiWorker extends Cloudflare.Worker<ApiWorker, {}>()("ApiWorker") {}
export const ApiWorkerLive = ApiWorker.make({ main: import.meta.url }, Effect.succeed({}));
export default Alchemy.Stack(
"WorkerMissingImplStack",
{ providers: Cloudflare.providers(), state: Cloudflare.state() },
Effect.gen(function* () {
const api = yield* ApiWorker; // ApiWorkerLive never provided
return { url: api.url.as<string>() };
}),
);
[ApiWorker] pre-creating
[ApiWorker] fail
Cause([Die(TypeError: undefined is not an object (evaluating 'news.name'))])
That line is const name = yield* createWorkerName(id, news.name) in WorkerProvider.ts's precreate. Nothing points at the real mistake, which is the missing Effect.provide([ApiWorkerLive]).
Mechanism
packages/alchemy/src/Platform.ts — the tagged branch, when the class's Self tag is absent from the context:
onNone: () =>
resource(
id,
props === undefined
? applyTransformProps(id, props) // <- stays `undefined`
: externalProps(),
),
A tagged class WITH props and no impl is the external (non-Effect-native) form, and externalProps() spreads to at least { isExternal: true }. A bare tag has neither props nor impl — both live on the un-provided .make(...) Layer — so applyTransformProps(id, undefined) returns undefined verbatim (no transformProps hook on most platforms) and the resource is registered with no props at all. cls.make normalizes the same value with transformed ?? {}; this path does not.
This is not Worker-specific: every Platform(...) consumer (Workers, Durable Objects, Containers, Workflows, AWS.Lambda.Function, AWS.ECS.Service/Task, Kubernetes.Deployment/Job, Prisma.Compute, …) reaches its provider with news === undefined in this situation. A provider tolerant of missing props deploys a silently misconfigured resource rather than crashing.
Reproduction
Hermetic, no cloud credentials, plain bun test against repo source. A synthetic Platform resource whose provider records what it receives:
const Widget: any = Platform<Widget>("Test.PlatformWidget", {
createRuntimeContext: () => ({}) as any,
});
const providers = Provider.succeed(Widget, {
reconcile: Effect.fn(function* ({ id, news }: any) {
observed = { ran: true, news };
return { name: news?.name ?? id };
}),
// list/diff/delete elided
});
class BareWidget extends Widget()("BareWidget") {} // no props, no impl, no layer
const Stack = Alchemy.Stack("PlatformMissingImplStack", { providers, state },
Effect.gen(function* () {
const widget = yield* BareWidget;
return { name: widget.name };
}),
);
provider ran: true | news: undefined
deploy SUCCEEDED
The Cloudflare Worker repro above is equally credential-free — the crash happens before any API call.
Expected
Fail fast at the yield site with an error naming the class and telling the user to provide its .make(...) Layer, e.g.
MissingImplementationError: Cloudflare.Worker<ApiWorker> was yielded without its implementation.
`ApiWorker` is declared as a bare tag — no props, no inline implementation — so both come from its `.make(...)` Layer:
export class ApiWorker extends Cloudflare.Worker<ApiWorker>()("ApiWorker") {}
export const ApiWorkerLive = ApiWorker.make({ /* props */ }, Effect.gen(function* () { /* ... */ }));
That Layer is not in scope where `ApiWorker` is yielded. Provide it:
Effect.gen(function* () {
const instance = yield* ApiWorker;
}).pipe(Effect.provide([ApiWorkerLive]))
The correct user action is documented by examples/cloudflare-worker/alchemy.run.ts, which pipes Effect.provide([WorkerTagLive, SecondaryApiLive, SandboxLive]). Only the diagnostics are wrong.
Guarding news?.name in WorkerProvider is the wrong fix — undefined props are legitimate elsewhere (the assets-only Worker has neither main nor script), and it would only move the crash to the next prop read in each of the other platforms.
Reproduced on 42625a4 (upstream main). Related but distinct: #1049 / #1050 cover a different pre-create crash (an Output-valued main in isPythonMain).
A tagged Effect-native resource declared as a bare tag gets both its props and its implementation from
.make(props, impl). Yielding the class without that Layer in scope hands the providernews === undefined, so the failure surfaces as an opaqueTypeErrordeep inside the provider instead of an error naming the class and the missing Layer.For a Cloudflare Worker:
That line is
const name = yield* createWorkerName(id, news.name)inWorkerProvider.ts'sprecreate. Nothing points at the real mistake, which is the missingEffect.provide([ApiWorkerLive]).Mechanism
packages/alchemy/src/Platform.ts— the tagged branch, when the class'sSelftag is absent from the context:A tagged class WITH props and no impl is the external (non-Effect-native) form, and
externalProps()spreads to at least{ isExternal: true }. A bare tag has neither props nor impl — both live on the un-provided.make(...)Layer — soapplyTransformProps(id, undefined)returnsundefinedverbatim (notransformPropshook on most platforms) and the resource is registered with no props at all.cls.makenormalizes the same value withtransformed ?? {}; this path does not.This is not Worker-specific: every
Platform(...)consumer (Workers, Durable Objects, Containers, Workflows,AWS.Lambda.Function,AWS.ECS.Service/Task,Kubernetes.Deployment/Job,Prisma.Compute, …) reaches its provider withnews === undefinedin this situation. A provider tolerant of missing props deploys a silently misconfigured resource rather than crashing.Reproduction
Hermetic, no cloud credentials, plain
bun testagainst repo source. A syntheticPlatformresource whose provider records what it receives:The Cloudflare Worker repro above is equally credential-free — the crash happens before any API call.
Expected
Fail fast at the yield site with an error naming the class and telling the user to provide its
.make(...)Layer, e.g.The correct user action is documented by
examples/cloudflare-worker/alchemy.run.ts, which pipesEffect.provide([WorkerTagLive, SecondaryApiLive, SandboxLive]). Only the diagnostics are wrong.Guarding
news?.nameinWorkerProvideris the wrong fix —undefinedprops are legitimate elsewhere (the assets-only Worker has neithermainnorscript), and it would only move the crash to the next prop read in each of the other platforms.Reproduced on
42625a4(upstreammain). Related but distinct: #1049 / #1050 cover a different pre-create crash (an Output-valuedmaininisPythonMain).