1import * as Cloudflare from "alchemy/Cloudflare";
2import * as Effect from "effect/Effect";
3import { HttpServerRequest } from "effect/http/HttpServerRequest";
4import * as HttpServerResponse from "effect/http/HttpServerResponse";
5import { PORTS } from "./ports.ts";
6
7/**
8 * Effect-native Cloudflare Container. Under `alchemy dev` the local
9 * provider builds/pulls the image and runs it in Docker; `SandboxDO`
10 * fronts it and `EchoWorker` proxies `GET /sandbox` into it.
11 *
12 * It exists in this stack so the stress suite covers a local resource
13 * whose restart is EXPENSIVE: the point of the container assertions is
14 * that unrelated churn (worker-source edits, stack reloads, broken
15 * intermediate states) must NOT bounce the container.
16 */
17export class SandboxContainer extends Cloudflare.Container<
18 SandboxContainer,
19 {}
20>()("SandboxContainer") {}
21
22export const SandboxLive = /* @__PURE__ */ SandboxContainer.make(
23 {
24 main: import.meta.url,
25 image: "oven/bun:latest",
26 env: {
27 SANDBOX_GREETING: "hello-from-container",
28 // A LOCALHOST url on purpose: it points at the AWS StaticSite dev
29 // server running on the HOST machine. Inside the container,
30 // `localhost` is the container itself — the dev runtime must rewrite
31 // the host to the `host.docker.localhost` alias for this to resolve —
32 // exactly what breaks database URLs from local containers
33 // (alchemy-run/alchemy#1334). `GET /host-fetch` proves it.
34 HOST_SERVICE_URL: `http://localhost:${PORTS.awsSite}`,
35 },
36 },
37 Effect.gen(function* () {
38 return SandboxContainer.of({
39 fetch: Effect.gen(function* () {
40 const request = yield* HttpServerRequest;
41 const url = new URL(request.url, "http://container");
42 if (url.pathname.endsWith("/host-fetch")) {
43 // Reach a service on the DEVELOPER'S machine through an env var
44 // that was written as `http://localhost:…` — the database shape
45 // of #1334, minus the database.
46 const target = process.env.HOST_SERVICE_URL;
47 const body = yield* Effect.tryPromise(() =>
48 fetch(`${target}/__dev-env`).then((res) => res.text()),
49 ).pipe(Effect.orElseSucceed(() => "UNREACHABLE"));
50 return yield* HttpServerResponse.json({
51 target: target ?? null,
52 body,
53 });
54 }
55 return yield* HttpServerResponse.json({
56 greeting: process.env.SANDBOX_GREETING ?? null,
57 // <<SANDBOX_MARKER>>
58 marker: "sandbox-v1",
59 // <</SANDBOX_MARKER>>
60 path: url.pathname,
61 });
62 }),
63 });
64 }),
65);
66
67export default SandboxLive;