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