Skip to content
Source
Sign inSign up
Open History to see the last commit

Platform.ts

679 lines30.0 KB

1/** @effect-diagnostics anyUnknownInErrorContext:off */
2​
3import type { NodeServices } from "@effect/platform-node/NodeServices";
4import * as ConfigError from "effect/Config";
5import * as ConfigProvider from "effect/ConfigProvider";
6import * as Context from "effect/Context";
7import * as Effect from "effect/Effect";
8import * as Effectable from "effect/Effectable";
9import type { HttpClient } from "effect/http/HttpClient";
10import type { HttpServerRequest } from "effect/http/HttpServerRequest";
11import * as HttpServerResponse from "effect/http/HttpServerResponse";
12import * as Layer from "effect/Layer";
13import * as Option from "effect/Option";
14import * as Redacted from "effect/Redacted";
15import { Scope } from "effect/Scope";
16import type * as Stream from "effect/Stream";
17import type { Dependencies } from "./Dependencies.ts";
18import { toFqn } from "./FQN.ts";
19import type { HttpEffect } from "./Http.ts";
20import type { InputProps } from "./Input.ts";
21import type { Named, Tag } from "./Named.ts";
22import { CurrentNamespace } from "./Namespace.ts";
23import * as Output from "./Output.ts";
24import { ALCHEMY_PHASE } from "./Phase.ts";
25import type { Provider, ProviderCollectionLike } from "./Provider.ts";
26import { Resource, type ResourceLike } from "./Resource.ts";
27import type { Rpc } from "./Rpc.ts";
28import { ServerHost, type ProcessContext } from "./Runtime/Process.ts";
29import {
30 CurrentRuntimeContext,
31 RuntimeContext,
32 sanitizeKey,
33 type BaseRuntimeContext,
34} from "./RuntimeContext.ts";
35import { Self } from "./Self.ts";
36import type { Stack, StackServices } from "./Stack.ts";
37import { StackContext } from "./StackContext.ts";
38import type { Stage } from "./Stage.ts";
39import { effectClass } from "./Util/effect.ts";
40​
41export interface PlatformProps {
42 /**
43 * @internal type used to signal when this is an effect-native implementation
44 * @default false
45 */
46 isExternal?: boolean;
48​
49/**
50 * Provide the platform class's layer (`cls.make(props, impl)`) with a
51 * lifetime that matches the phase.
52 *
53 * **At runtime** (`__ALCHEMY_RUNTIME__`, folded to `true` in every bundled
54 * artifact) the layer builds against the AMBIENT scope. `Effect.provide` is
55 * implemented with `scopedWith`, so its transient region scope would tear
56 * the layer down — firing init-level finalizers and releasing
57 * `Layer.scoped` services — the moment init completes. The runtime bridges
58 * evaluate the entrypoint under the instance-lifetime build scope (closed
59 * at instance shutdown where the platform offers one — Lambda's SIGTERM
60 * window — and never on workerd), so building against it keeps instance
61 * services alive for the instance.
62 *
63 * **At plan/deploy** it stays `Effect.provide`: the transient region evicts
64 * the layer's memo entry when each `yield*` of the class completes, so
65 * every deploy in a session re-evaluates the resource and re-registers its
66 * Output sources. Building on the session scope would keep the memo alive
67 * across deploys and a second `stack.deploy` would skip source
68 * registration (`MissingSourceError`).
69 */
70const provideClassLayer =
71 <ROut, E2, RIn>(layer: Layer.Layer<ROut, E2, RIn>) =>
72 <A, E, R>(
73 self: Effect.Effect<A, E, R>,
74 ): Effect.Effect<A, E | E2, RIn | Scope | Exclude<R, ROut>> =>
75 (globalThis.__ALCHEMY_RUNTIME__
76 ? Effect.flatMap(Effect.scope, (scope) =>
77 Effect.flatMap(Layer.buildWithScope(layer, scope), (context) =>
78 Effect.provideContext(self, context),
79 ),
80 )
81 : Effect.provide(self, layer)) as Effect.Effect<A, E | E2, RIn | Scope | Exclude<R, ROut>>;
82​
83export type Main<InitServices = never> = void | {
84 fetch?:
85 | HttpEffect<InitServices | PlatformServices | RuntimeContext | Scope>
86 | Effect.Effect<
87 HttpEffect<InitServices | PlatformServices | RuntimeContext | Scope>,
88 never,
89 InitServices | PlatformServices
90 >;
91};
92​
93export interface MainRpc<Req = never> {
94 [key: string]:
95 | Effect.Effect<any, any, PlatformServices | RuntimeContext | HttpServerRequest | Scope | Req>
96 | Stream.Stream<any, any, PlatformServices | RuntimeContext | HttpServerRequest | Scope | Req>
97 | ((
98 ...args: any[]
99 ) =>
100 | Effect.Effect<any, any, PlatformServices | RuntimeContext | Scope | Req>
101 | Stream.Stream<any, any, PlatformServices | RuntimeContext | Scope | Req>);
103​
104// Strip `void`/`undefined`/`never` from `Shape` before intersecting it with
105// `BaseShape`. This matters when `Shape` fails its `extends MainShape`
106// constraint (e.g. a `fetch` handler that leaks an error): TS clamps `Shape`
107// to the constraint union `void | { fetch: ... }`, and a *distributive*
108// conditional would split that into `BaseShape | ({ fetch } & BaseShape)` — a
109// union. Feeding a union into the `new (_: never): ...` construct signature
110// makes the base class a union type, which surfaces as the cryptic
111// ts(2509) "Base constructor return type ... is not an object type" instead of
112// the real assignability error on the `impl` argument. Excluding `void` here
113// keeps the construct-sig return a single object type, so only the actionable
114// error remains.
115export type MakeShape<Shape, BaseShape> = [Exclude<Shape, void | undefined>] extends [never]
116 ? Exclude<BaseShape, void | undefined>
117 : Exclude<Shape, void | undefined> & Exclude<BaseShape, void | undefined>;
118​
119// Services provided to the Resource's init/props effects. Deliberately does
120// NOT include `Scope`: init runs once per instance under a build scope that
121// closes at instance shutdown at best (Lambda's SIGTERM window; never on
122// workerd), so init code that needs a scope must not typecheck. Handlers get
123// a fresh per-event `Scope` from the bridge — note the explicit `| Scope` on
124// the handler positions in `Main` / `MainRpc` above.
125export type PlatformServices =
126 | NodeServices
127 | HttpClient
128 | Provider<any>
129 | ProviderCollectionLike
130 | Stack
131 | StackServices
132 | Stage
133 // Every platform provides its instance as the generic `Self` (see `make`),
134 // which host-agnostic bindings (e.g. Cloudflare `*Http`) name tokens after.
135 | Self;
136​
137/** A platform declaration's logical identity, readable without yielding it. */
138export interface PlatformIdentity<Id extends string = string> {
139 readonly LogicalId: Id;
141​
142export interface Platform<
143 Resource extends ResourceLike<string, PlatformProps>,
144 Services,
145 MainShape,
146 RuntimeContext extends BaseRuntimeContext,
147 BaseShape = {},
148 InlineProps extends Resource["Props"] = Resource["Props"],
149> extends Effect.Effect<Resource & RuntimeContext, never, Resource> {
150 Type: Resource["Type"];
151 Provider: Provider<Resource>;
152​
153 <Self, Shape, Deps = never>(): {
154 <const Id extends string>(
155 id: Id,
156 ): Effect.Effect<Resource & Rpc<Self> & Dependencies<Deps>, never, Resource["Providers"]> &
157 Named<Id> &
158 PlatformIdentity<Id> & {
159 make<PropsReq = never, InitReq = never>(
160 props:
161 | InputProps<InlineProps>
162 | Effect.Effect<InputProps<InlineProps>, ConfigError.ConfigError, PropsReq>,
163 impl: Effect.Effect<Shape, ConfigError.ConfigError, InitReq>,
164 ): Layer.Layer<
165 Self,
166 never,
167 | Resource["Providers"]
168 | Exclude<PropsReq | InitReq, Services | PlatformServices | Resource>
169 >;
170 new (_: never): MakeShape<Shape, BaseShape> & Named<Id> & Tag<Resource["Type"]>;
171 of(shape: Shape & MainShape): MakeShape<Shape, BaseShape>;
172 };
173 };
174 <Self>(): {
175 <
176 const Id extends string,
177 Shape extends MainShape,
178 PropsReq = never,
179 InitReq extends Services | PlatformServices | Resource = never,
180 >(
181 id: Id,
182 props:
183 | InputProps<InlineProps>
184 | Effect.Effect<InputProps<InlineProps>, ConfigError.ConfigError, PropsReq>,
185 impl: Effect.Effect<Shape, ConfigError.ConfigError, InitReq>,
186 ): Effect.Effect<
187 Resource & Rpc<Self>,
188 never,
189 | Resource["Providers"]
190 | Exclude<PropsReq, Services | PlatformServices | Resource>
191 | Exclude<InitReq, Services | PlatformServices | Resource>
192 > &
193 Named<Id> &
194 PlatformIdentity<Id> & {
195 new (_: never): MakeShape<Shape, BaseShape> & Named<Id> & Tag<Resource["Type"]>;
196 };
197​
198 <const Id extends string>(
199 id: Id,
200 ): Effect.Effect<Resource & Rpc<Self>, never, Resource["Providers"]> &
201 Named<Id> &
202 PlatformIdentity<Id> & {
203 make<PropsReq = never, InitReq extends Services | PlatformServices | Resource = never>(
204 props:
205 | InputProps<InlineProps>
206 | Effect.Effect<InputProps<InlineProps>, ConfigError.ConfigError, PropsReq>,
207 impl: Effect.Effect<MainShape, ConfigError.ConfigError, InitReq>,
208 ): Layer.Layer<
209 Self,
210 never,
211 | Resource["Providers"]
212 | Exclude<PropsReq | InitReq, Services | PlatformServices | Resource>
213 >;
214 new (_: never): BaseShape & Named<Id> & Tag<Resource["Type"]>;
215 };
216 };
217 <
218 PropsReq = never,
219 InitReq extends Services | PlatformServices = never,
220 const Id extends string = string,
221 >(
222 id: Id,
223 props:
224 | InputProps<Resource["Props"]>
225 | Effect.Effect<InputProps<Resource["Props"]>, never, PropsReq>,
226 ): Effect.Effect<
227 Resource,
228 never,
229 Resource["Providers"] | PropsReq | Exclude<InitReq, Services | PlatformServices>
230 > &
231 PlatformIdentity<Id>;
232 <
233 const Id extends string,
234 Shape extends MainShape,
235 PropsReq = never,
236 InitReq extends Services | PlatformServices = never,
237 >(
238 id: Id,
239 props: InputProps<InlineProps> | Effect.Effect<InputProps<InlineProps>, never, PropsReq>,
240 impl: Effect.Effect<Shape, ConfigError.ConfigError, InitReq>,
241 ): Effect.Effect<
242 Resource & Rpc<Shape> & Named<Id>,
243 never,
244 Resource["Providers"] | PropsReq | Exclude<InitReq, Services | PlatformServices>
245 > &
246 Named<Id> &
247 PlatformIdentity<Id>;
249​
250export const Platform = <
251 R extends ResourceLike<
252 string,
253 | {
254 env?: Record<string, any>;
255 exports?: string[] | Record<string, any>;
256 }
257 | undefined
258 >,
259>(
260 type: R["Type"],
261 hooks: {
262 createRuntimeContext: (id: string) => BaseRuntimeContext;
263 /**
264 * Legacy type names this platform's resource was previously registered
265 * under (see `ResourceOptions.aliases`) — threaded to the underlying
266 * `Resource` so state persisted under a pre-rename type keeps
267 * resolving.
268 */
269 aliases?: string[];
270 // `onCreate` runs inside the resource-construction context, which already
271 // carries the Stack's providers — so the hook may yield child resources
272 // (e.g. an async Worker registering a `WorkflowResource` for a bound
273 // Workflow). Allow an ambient requirement (`any`) rather than forcing
274 // `never`; it is discharged by the surrounding provider context.
275 onCreate?: (resource: R, props: any) => Effect.Effect<void, never, any>;
276 /**
277 * Transform the user-supplied props before the underlying resource is
278 * declared. Runs in the resource-construction context (Stack, Namespace,
279 * and providers are available), so the hook may compose REAL child
280 * resources (the `StaticSite` pattern) and return derived props that
281 * reference their Outputs. Implementations MUST be a no-op at runtime
282 * (guard on `globalThis.__ALCHEMY_RUNTIME__`) — the hook is evaluated
283 * wherever the props effect is, including inside deployed bundles.
284 */
285 transformProps?: (id: string, props: any) => Effect.Effect<any, any, any>;
286 },
287 methods?: { [key: string]: any },
288): any => {
289 type Props = any;
290 type Impl = Effect.Effect<any>;
291​
292 // Platform registrations must have resolved props by plan time: every
293 // legitimate construction (a `.make(props, impl)` Layer build, a tag
294 // declared with props, a plain call) produces them, so props still
295 // `undefined` at plan can only be a bare-tag forward reference whose
296 // `.make` Layer was never provided — `Plan.make` fails fast naming the
297 // class and its Layer (#1054).
298 const resource = Resource(type, {
299 aliases: hooks.aliases,
300 requiresImplementation: true,
301 });
302 const PlatformContext = RuntimeContext;
303​
304 // Apply the optional `transformProps` hook to a (possibly Effect-valued)
305 // props argument. Returns the props untouched when no hook is installed so
306 // the plain-object fast paths below keep working, and leaves `undefined`
307 // props (a bare-tag forward reference) for `Plan.make` to report.
308 const applyTransformProps = (id: string, props: any): any =>
309 hooks.transformProps === undefined || props === undefined
310 ? props
311 : Effect.flatMap(
312 // oxlint-disable-next-line effecttsgo/unsafe-effect-type-assertion -- props Effects are declarations resolved by the engine
313 Effect.isEffect(props) ? (props as Effect.Effect<any>) : Effect.succeed(props ?? {}),
314 (resolved) => hooks.transformProps!(id, resolved),
315 );
316​
317 const constructor = (id?: string, props?: any, impl?: Impl, isTag = false): any => {
318 if (!id) {
319 // impl was not provided inline, this is a tagged instance
320 // e.g.
321 // export class Sandbox extends Cloudflare.Container<Sandbox>()(..) {}
322 //
323 // export const SandboxLive = Sandbox.make(..)
324 return (id: string, props?: any, impl?: Impl) => constructor(id, props, impl, true);
325 } else if (!impl) {
326 const cls = makeClass(id);
327 // A resource declared without an inline impl is "external": there is
328 // no Effect-native entry to inject (an ordinary bundled worker, or an
329 // assets-only worker with no script at all).
330 const externalProps = () => {
331 const transformed = applyTransformProps(id, props);
332 return Effect.isEffect(transformed)
333 ? Effect.map(transformed, (p: any) => ({
334 ...p,
335 isExternal: true,
336 }))
337 : {
338 ...transformed,
339 isExternal: true,
340 };
341 };
342 const register = () =>
343 !isTag
344 ? // this is a non-tagged resource yielded without providing an implementation
345 // e.g.
346 // yield* Cloudflare.Worker("id", { main: "./src/worker.ts" })
347 //
348 // This is where we bridge to non-effect, e.g. bundling an ordinary worker
349 // export default {
350 // fetch: (request: Request) => {
351 // return new Response("Hello, world!");
352 // }
353 // }
354 resource(id, externalProps())
355 : Effect.flatMap(
356 // this is a tagged resource
357 Effect.serviceOption(cls.Self),
358 Option.match({
359 // we are likely running at runtime, so we create.
360 // A tagged class WITH props and no impl is the class form of
361 // the external resource above (e.g.
362 // `class Site extends Worker<Site>()("Site", { assets }) {}`)
363 // — same isExternal marking; without props, this is a bare
364 // tag whose props/impl arrive later via `.make`.
365 onNone: () =>
366 // Without props this is a bare-tag FORWARD REFERENCE: its
367 // `.make(props, impl)` Layer may build before or after
368 // this yield (e.g. a worker tag bound in another worker's
369 // `env` — the #874 circular-binding pattern — resolves
370 // during that worker's async-binding pass, outside the
371 // Layer's own context). Register with `undefined` props;
372 // the Layer's build repairs them, and `Plan.make` fails
373 // fast on any platform registration whose props are still
374 // `undefined` after the whole program evaluated (see
375 // `requiresImplementation` above).
376 resource(
377 id,
378 props === undefined ? applyTransformProps(id, props) : externalProps(),
379 ),
380 onSome: Effect.succeed,
381 }),
382 );
383 // Whether this `yield*` registers the resource. The stack holds one
384 // resource per FQN and every later `yield*` returns it. Outside a
385 // stack (at runtime) nothing registers, so the hook runs as before.
386 const isFirstRegistration = Effect.flatMap(
387 Effect.serviceOption(StackContext),
388 Option.match({
389 onNone: () => Effect.succeed(true),
390 onSome: (stack) =>
391 Effect.map(CurrentNamespace, (namespace) => !(toFqn(namespace, id) in stack.resources)),
392 }),
393 );
394 const evaluate = () =>
395 Effect.flatMap(isFirstRegistration, (firstRegistration) =>
396 register().pipe(
397 Effect.tap((resource) =>
398 // Run the hook only for the `yield*` that registered the
399 // resource. The hook yields the resource's `env`, so re-running
400 // it on every `yield*` lets two Workers whose `env` entries
401 // yield each other recurse forever (#2672). The resource is
402 // registered before the hook runs, so a cycle back to it stops.
403 hooks.onCreate && firstRegistration
404 ? Effect.flatMap(
405 // `props` may itself be an Effect (e.g. when wrapped by
406 // `Cloudflare.Website.Vite` via `Effect.map`); resolve it
407 // before handing it to the hook so `onCreate` always sees
408 // the plain props object — the second call site (in
409 // `cls.make`) already does this.
410 Effect.isEffect(props) ? props : Effect.succeed(props ?? {}),
411 (resolved) => hooks.onCreate!(resource as R, resolved),
412 )
413 : Effect.void,
414 ),
415 ),
416 );
417 return Object.assign(
418 function (props: Props, impl: Impl) {
419 return cls.Self.pipe(provideClassLayer(cls.make(props, impl)));
420 },
421 // we splice in the Effect so this can be yielded to indicate a non-Effect native instance
422 // e.g. here, we yield it - in this case we don't want to provide an implementation
423 // const worker = yield* Cloudflare.Worker("id", {
424 // main: "./src/worker.ts"
425 // });
426 cls,
427 // Spread the Effect prototype LAST so it overrides the evaluate copied
428 // from `cls`: yielding this no-impl form bridges to the (possibly
429 // non-Effect-native) resource rather than resolving the Self tag.
430 Effectable.Prototype({
431 label: `${type}<${id}>`,
432 evaluate,
433 }),
434 );
435 } else {
436 // impl was provided inline, this is a non-tagged eager instance
437 // e.g.
438 // export default Cloudflare.Worker("id", { main: "./src/worker.ts" }, Effect.gen(function* () { .. })
439 const cls = makeClass(id);
440 return Object.assign(
441 cls.Self.pipe(provideClassLayer(cls.make(props, impl)), effectClass),
442 // Expose the logical id statically (mirrors `makeClass`) so a class
443 // reference identifies its resource without being yielded.
444 { LogicalId: id },
445 );
446 }
447 };
448​
449 const makeClass = (id: string) => {
450 class Platform {
451 /**
452 * Logical id of the resource this class declares. Statically readable
453 * — e.g. `DurableObjectProps.transferredFrom` accepts a Worker class
454 * and takes its identity from here without yielding it (yielding
455 * would add a dependency edge on the referenced resource).
456 */
457 static readonly LogicalId = id;
458 static readonly Self = Self(`${type}<${id}>`);
459 static readonly Platform = Context.Service<Platform, Platform>(`Platform<${type}<${id}>>`);
460 static of = (shape: any) => shape;
461 static make = (props: Props, impl: Impl) => {
462 // build the Layer once for the root Self
463 const SelfLayer = Layer.effect(
464 Self,
465 Effect.flatMap(
466 Effect.all([
467 (() => {
468 const transformed = applyTransformProps(id, props);
469 return Effect.isEffect(transformed)
470 ? // oxlint-disable-next-line effecttsgo/unsafe-effect-type-assertion -- props Effects are declarations resolved by the engine
471 (transformed as Effect.Effect<any>)
472 : Effect.succeed(transformed ?? {});
473 })(),
474 Effect.sync(() => hooks.createRuntimeContext(id)),
475 Effect.context<never>(),
476 ]),
477 Effect.fn(function* ([props, runtimeContext, outerServices]) {
478 // The init effect (`impl`) is evaluated inside an
479 // `Effect.provide(...)` region below, whose implementation
480 // (`scopedWith`) would otherwise shadow the ambient `Scope`
481 // with a transient one that closes the moment init returns.
482 // Pin init's ambient scope to this layer's build scope
483 // instead: under the runtime bridges that scope belongs to
484 // the instance-lifetime build, so init-level finalizers run
485 // at instance shutdown or not at all — never per event
486 // (workerd never closes it; the Lambda entry closes it in the
487 // SIGTERM window). Request-coupled cleanup belongs in
488 // handlers, where the bridge provides a per-event scope.
489 const buildScope = yield* Effect.scope;
490 const instance = Object.assign(
491 yield* resource(id, props as any).pipe(
492 Effect.flatMap(
493 (resource) =>
494 hooks.onCreate?.(resource, props).pipe(Effect.map(() => resource)) ??
495 Effect.succeed(resource),
496 ),
497 ),
498 runtimeContext,
499 );
500​
501 yield* impl.pipe(
502 Effect.flatMap((impl) => {
503 if (!impl) return Effect.void;
504 const shape = impl as Record<string, unknown>;
505 // Serve when there's a `fetch` handler OR any RPC shape
506 // methods. A pure-RPC impl (methods, no `fetch`) still needs
507 // the server to boot — hand `serveRpc` a default 404 fallback
508 // so `/__rpc__/*` is dispatched to the shape methods and
509 // everything else 404s.
510 // May be an `HttpEffect` or an Effect resolving to one (the
511 // `Main.fetch` shape); `serve` accepts both.
512 const fetch = shape.fetch as any;
513 const hasRpcMethods = Object.keys(shape).some((key) => key !== "fetch");
514 if (!fetch && !hasRpcMethods) return Effect.void;
515 // Hand the full impl to `serve` so the runtime can expose any
516 // non-handler methods on the impl shape (RPC methods)
517 // alongside the standard `fetch` handler.
518 return (
519 runtimeContext.serve?.(
520 fetch ??
521 Effect.succeed(HttpServerResponse.text("Not Found", { status: 404 })),
522 { shape },
523 ) ?? Effect.die("No serve handler")
524 );
525 }),
526 Effect.provide(
527 Layer.effect(
528 ConfigProvider.ConfigProvider,
529 Effect.gen(function* () {
530 // a Config Provider that we use to intercept config lookups and bind them to the RuntimeContext
531 const configProvider = yield* ConfigProvider.ConfigProvider;
532 const phase = yield* ALCHEMY_PHASE;
533​
534 return ConfigProvider.make(
535 Effect.fnUntraced(function* (path) {
536 const ctx = yield* CurrentRuntimeContext;
537 // `set`/`get` store keys verbatim, so canonicalize the
538 // logical config path here (the caller's job) before
539 // handing it to the RuntimeContext.
540 const key = sanitizeKey(path.map((p) => p.toString()).join("_"));
541 const node = yield* configProvider.load(path);
542 if (phase === "plan" && node) {
543 // bind it to the RuntimeContext if running in plan phase
544 const output = Output.literal(Redacted.make(node.value));
545 yield* ctx?.set(key, output) ?? Effect.void;
546 return node;
547 } else if (phase === "runtime" && ctx) {
548 // retrieve from the RuntimeContext if running in runtime phase
549 const value = yield* ctx.get<unknown>(key);
550 if (value) {
551 // `get` JSON-decodes unbound env values (`"true"` →
552 // `true`), but a `Value` node holds the raw string the
553 // `Config` schema parses.
554 return ConfigProvider.makeValue(
555 Redacted.isRedacted(value)
556 ? String(Redacted.value(value))
557 : typeof value === "string"
558 ? value
559 : JSON.stringify(value),
560 );
561 }
562 }
563 // fallback to the config provider otherwise
564 return node;
565 }),
566 );
567 }),
568 ).pipe(
569 Layer.provideMerge(
570 // Services the host provides its init (e.g. a Worker's `WebhookHost`),
571 // built over the instance so they can `yield*` it.
572 (runtimeContext.hostServices ?? Layer.empty).pipe(
573 Layer.provideMerge(
574 Layer.mergeAll(
575 // Pin init's ambient `Scope` to this layer's build
576 // scope. `Effect.provide` (`scopedWith`) would
577 // otherwise shadow it with a transient scope that
578 // closes the moment init returns; the build scope
579 // lives for the instance under the runtime bridges,
580 // so init-level finalizers run at instance shutdown
581 // (Lambda's SIGTERM window) or never (workerd) —
582 // request-coupled cleanup belongs in handlers, where
583 // the bridge provides a per-event scope. It also
584 // wins over any `Scope` captured in `outerServices`
585 // below.
586 Layer.succeed(Scope, buildScope),
587 Layer.succeed(Platform.Platform, runtimeContext),
588 Layer.succeed(PlatformContext, runtimeContext),
589 Layer.succeed(RuntimeContext, runtimeContext),
590 // Host contexts (EC2 instances, ECS tasks, processes)
591 // carry a `run` for registering long-running loops.
592 // Expose it as `ServerHost` so an inline program can
593 // `yield* ServerHost` during plan/deploy without the
594 // caller providing the layer itself.
595 "run" in runtimeContext &&
596 typeof (runtimeContext as { run?: unknown }).run === "function"
597 ? Layer.succeed(ServerHost, {
598 run: (runtimeContext as ProcessContext).run,
599 })
600 : Layer.empty,
601 Layer.succeed(resource.Self, instance),
602 Layer.succeed(Platform.Self, instance),
603 Layer.succeed(Self, instance),
604 runtimeContext.planServices
605 ? Layer.unwrap(
606 ALCHEMY_PHASE.pipe(
607 Effect.map((phase) =>
608 phase === "plan" ? runtimeContext.planServices! : Layer.empty,
609 ),
610 ),
611 )
612 : Layer.empty,
613 ),
614 ),
615 ),
616 ),
617 Layer.provideMerge(Layer.succeedContext(outerServices)),
618 ),
619 ),
620 );
621​
622 instance.Props = {
623 ...props,
624 env: {
625 ...props?.env,
626 ...runtimeContext.env,
627 },
628 exports: runtimeContext.exports ? yield* runtimeContext.exports : undefined,
629 };
630​
631 return Object.assign(instance, {
632 RuntimeContext: runtimeContext,
633 }) as R;
634 }),
635 ),
636 );
637 const self = Self as any; // TODO(sam): why do we need to cast?
638​
639 return Layer.provideMerge(
640 Layer.mergeAll(
641 // sets the Context for all self-hierarchies
642 // Self
643 // Self<Cloudflare.Worker>
644 // Self<Cloudflare.Worker<Api>>
645 Layer.effect(Self<R>(type), self),
646 Layer.effect(Self<R>(`${type}<${id}>`), self),
647 ),
648 // provide here so we build once and just mirror
649 SelfLayer,
650 );
651 };
652 }
653 // Make the platform class itself a real Effect: `yield* MyWorker` resolves
654 // the Self tag. Replaces the hand-rolled asEffect/pipe/[Symbol.iterator].
655 return Object.assign(
656 Platform,
657 Effectable.Prototype({
658 label: `${type}<${id}>`,
659 evaluate: () => Platform.Self,
660 }),
661 );
662 };
663​
664 const instance = Object.assign(
665 constructor,
666 resource,
667 // Spread the Effect prototype LAST so it overrides any evaluate inherited
668 // from `resource`; `yield* Cloudflare.Worker` resolves the resource Self.
669 Effectable.Prototype({
670 label: `${type}`,
671 evaluate: () => resource.Self,
672 }),
673 {
674 Platform: Platform,
675 ...methods,
676 },
677 ) as any;
678 return instance;
679};