1import * as Effect from "effect/Effect";
2import * as Option from "effect/Option";
3import { HttpServer, type HttpEffect } from "../../Http.ts";
4import * as Output from "../../Output.ts";
5import { serveRpc } from "../../Rpc.ts";
6import {
7 packEnvValueKeepRedacted,
8 unpackEnvValue,
9} from "../../RuntimeContext.ts";
10import * as Server from "../../Server/index.ts";
11
12export const MicrovmImageTypeId = "AWS.Lambda.MicrovmImage" as const;
13
14/**
15 * Runtime context for the in-VM process: an HTTP server (the MicroVM endpoint)
16 * that exposes the impl's `fetch` handler plus any RPC shape methods. Mirrors
17 * the Cloudflare `ContainerPlatform` process context.
18 */
19export const makeMicrovmRuntimeContext = (
20 id: string,
21): Server.ProcessContext => {
22 const runners: Effect.Effect<void, never, any>[] = [];
23 const env: Record<string, any> = {};
24
25 const serve = <Req = never>(
26 handler: HttpEffect<Req>,
27 options?: { shape?: Record<string, unknown> },
28 ) =>
29 Effect.sync(() => {
30 const finalHandler = options?.shape
31 ? serveRpc(options.shape, handler)
32 : handler;
33 runners.push(
34 Effect.gen(function* () {
35 const httpServer = yield* Effect.serviceOption(HttpServer).pipe(
36 Effect.map(Option.getOrUndefined),
37 );
38 if (httpServer) {
39 yield* httpServer.serve(finalHandler);
40 yield* Effect.never;
41 }
42 }).pipe(Effect.orDie),
43 );
44 });
45
46 return {
47 Type: MicrovmImageTypeId,
48 LogicalId: id,
49 id,
50 env,
51 set: (bindingId: string, output: Output.Output) =>
52 Effect.sync(() => {
53 const key = bindingId.replaceAll(/[^a-zA-Z0-9]/g, "_");
54 // `packEnvValueKeepRedacted` keeps the Redacted wrapper on the
55 // outside so deploy-time code can route secrets while the inner
56 // marker lets the runtime `get` accessor rebuild the wrapper.
57 env[key] = output.pipe(Output.map(packEnvValueKeepRedacted));
58 return key;
59 }),
60 get: <T>(key: string) =>
61 // Read straight from `process.env` — see `unpackEnvValue` for why
62 // this must never resolve through `Config.String`.
63 Effect.sync(() => unpackEnvValue<T>(process.env[key]) as T),
64 run: ((effect: Effect.Effect<void, never, any>) =>
65 Effect.sync(() => {
66 runners.push(effect);
67 })) as unknown as Server.ProcessContext["run"],
68 serve,
69 exports: Effect.sync(() => ({
70 default: Effect.all(
71 runners.map((eff) =>
72 Effect.forever(
73 eff.pipe(
74 Effect.tapError((err) => Effect.logError(err)),
75 Effect.ignore,
76 ),
77 ),
78 ),
79 { concurrency: "unbounded" },
80 ),
81 })),
82 } as Server.ProcessContext;
83};