1import type * as cf from "@cloudflare/workers-types";
2import * as Config from "effect/Config";
3import * as Context from "effect/Context";
4import * as Data from "effect/Data";
5import * as Effect from "effect/Effect";
6import * as Layer from "effect/Layer";
7import type * as HttpClientRequest from "effect/http/HttpClientRequest";
8import type * as HttpClientResponse from "effect/http/HttpClientResponse";
9import type * as HttpServerRequest from "effect/http/HttpServerRequest";
10import type * as HttpServerResponse from "effect/http/HttpServerResponse";
11import type { InlineDockerfile } from "../../Docker/Dockerfile.ts";
12import type { InputProps } from "../../Input.ts";
13import type { Named } from "../../Named.ts";
14import type { ResourceClass, ResourceClassLike } from "../../Resource.ts";
15import type { Rpc } from "../../Rpc.ts";
16import type { RuntimeContext } from "../../RuntimeContext.ts";
17import { effectClass } from "../../Util/effect.ts";
18import type { Fetcher } from "../Fetcher.ts";
19import type { Providers } from "../Providers.ts";
20import { type WorkerShape } from "../Workers/Worker.ts";
21import type {
22 ContainerApplication,
23 ContainerApplicationProps,
24} from "./ContainerApplication.ts";
25import { ContainerPlatform } from "./ContainerPlatform.ts";
26
27export const ContainerTypeId = "Cloudflare.Container";
28export type ContainerTypeId = typeof ContainerTypeId;
29
30export const ContainerTag = (
31 id: string,
32): Context.Key<Container.Instance, Container.Instance> =>
33 Context.Service<Container.Instance>(`Container<${id}>`);
34
35export const isContainer = <T>(value: T): value is T & Container =>
36 typeof value === "object" &&
37 value !== null &&
38 "Type" in value &&
39 value.Type === ContainerTypeId;
40
41export class ContainerError extends Data.TaggedError("ContainerError")<{
42 readonly message: string;
43 readonly cause?: unknown;
44}> {}
45
46/**
47 * No container instance could be allocated within the start budget — the
48 * account is at its concurrent-instance cap (`maxInstances`) or the platform
49 * is still provisioning. Mirrors `@cloudflare/containers`'
50 * `NO_CONTAINER_INSTANCE_ERROR` (surfaced as HTTP 503 by native).
51 */
52export class NoContainerInstanceError extends Data.TaggedError(
53 "NoContainerInstanceError",
54)<{
55 readonly message: string;
56 readonly cause?: unknown;
57}> {}
58
59/**
60 * Cloudflare is rate limiting container starts ("too many containers per
61 * second"). Mirrors `@cloudflare/containers`' `RATE_LIMITED_ERROR` (HTTP 429).
62 * Hammering `start()` while rate limited only prolongs it, so callers should
63 * back off rather than retry tightly.
64 */
65export class ContainerRateLimitedError extends Data.TaggedError(
66 "ContainerRateLimitedError",
67)<{
68 readonly message: string;
69 readonly cause?: unknown;
70}> {}
71
72/**
73 * The container instance exited/crashed while we were waiting for its port —
74 * the entrypoint failed to bind or died. Mirrors native's "container exited"
75 * detection (`!this.container.running` mid-wait); not curable by continuing to
76 * poll the same instance.
77 */
78export class ContainerCrashedError extends Data.TaggedError(
79 "ContainerCrashedError",
80)<{
81 readonly message: string;
82 readonly cause?: unknown;
83}> {}
84
85// Upstream changed ContainerStartupOptions to an intersection with an
86// image/containerSnapshot union, which an interface cannot extend.
87export type ContainerStartupOptions = cf.ContainerStartupOptions;
88
89import type {
90 EffectfulContainerProps,
91 ExternalContainerProps,
92 RemoteContainerProps,
93} from "./ContainerApplication.ts";
94
95export type {
96 EffectfulContainerProps,
97 ExternalContainerProps,
98 RemoteContainerProps,
99};
100
101/**
102 * Props for an image-backed container declaration — either the plain props
103 * object, or an Effect that produces it.
104 *
105 * The Effect form is how a container reaches another resource's outputs. A
106 * class body is module scope, so there is nowhere to `yield*` a database,
107 * queue, or bucket; wrapping the props in `Effect.gen` moves the declaration
108 * into an Effect where sibling resources resolve normally and their
109 * attributes can be threaded into `env`:
110 *
111 * ```typescript
112 * export class Api extends Cloudflare.Container<Api>()(
113 * "Api",
114 * Effect.gen(function* () {
115 * const { connection } = yield* Db;
116 * return {
117 * context: `${import.meta.dirname}/api`,
118 * env: { DATABASE_URL: connection.databaseUrl },
119 * };
120 * }),
121 * ) {}
122 * ```
123 *
124 * A plain props object cannot do this: a module-scope resource declaration
125 * is an Effect, not a resolved handle, so `Db.connectionString` is
126 * `undefined` until something yields it.
127 */
128export type ImageContainerProps<Req = never> =
129 | InputProps<ExternalContainerProps>
130 | InputProps<RemoteContainerProps>
131 | Effect.Effect<
132 InputProps<ExternalContainerProps> | InputProps<RemoteContainerProps>,
133 Config.ConfigError,
134 Req
135 >;
136
137export type Container<Id extends string = string> = Named<Id> & {
138 get running(): Effect.Effect<boolean, never, RuntimeContext>;
139 start(
140 options?: ContainerStartupOptions,
141 ): Effect.Effect<void, never, RuntimeContext>;
142 monitor(): Effect.Effect<void, ContainerError, RuntimeContext>;
143 destroy(error?: any): Effect.Effect<void, never, RuntimeContext>;
144 signal(signo: number): Effect.Effect<void, never, RuntimeContext>;
145 getTcpPort(port: number): Effect.Effect<Fetcher, never, RuntimeContext>;
146 setInactivityTimeout(
147 durationMs: number | bigint,
148 ): Effect.Effect<void, never, RuntimeContext>;
149 interceptOutboundHttp(
150 addr: string,
151 binding: Fetcher,
152 ): Effect.Effect<void, never, RuntimeContext>;
153 interceptAllOutboundHttp(
154 binding: Fetcher,
155 ): Effect.Effect<void, never, RuntimeContext>;
156};
157
158/**
159 * A Cloudflare Container that runs a long-lived process alongside a
160 * Durable Object.
161 *
162 * Containers always use the **Container Layer** pattern — the class
163 * and `.make()` must live in separate files. A Container must be
164 * bound to a Durable Object, and the DO imports the class to get a
165 * typed handle. If the class and `.make()` lived in the same file,
166 * the DO's bundle would pull in all of the container's runtime
167 * dependencies (process spawners, Node APIs, SDKs, etc.), which
168 * would bloat the bundle and likely break the Cloudflare Workers
169 * runtime. Keeping them separate ensures the bundler only includes
170 * the tiny class in the DO's output.
171 *
172 * See the [Runtime concept](/infrastructure-as-effects/runtime)
173 * page for how this fits into the async / effect / layer
174 * progression.
175 * ### Container Layer
176 * Define the class and `.make()` in separate files. The class
177 * declares the container's identity, configuration, and typed
178 * shape. `.make()` provides the runtime implementation as a
179 * default export. Use `Container.of` to construct the typed
180 * shape — it ensures your implementation matches the methods
181 * declared on the class.
182 *
183 * **Example:** Container class
184 * ```typescript
185 * // src/Sandbox.ts — the tag carries only the name + typed shape;
186 * // configuration lives on `.make()`.
187 * export class Sandbox extends Cloudflare.Container<
188 * Sandbox,
189 * {
190 * exec: (cmd: string) => Effect.Effect<{
191 * exitCode: number;
192 * stdout: string;
193 * stderr: string;
194 * }>;
195 * }
196 * >()("Sandbox") {}
197 * ```
198 *
199 * **Example:** Container .make()
200 * ```typescript
201 * // src/Sandbox.runtime.ts — props are the first argument to `.make()`
202 * export default Sandbox.make(
203 * { main: import.meta.url },
204 * Effect.gen(function* () {
205 * const cp = yield* ChildProcessSpawner;
206 *
207 * return Sandbox.of({
208 * exec: (command) =>
209 * cp.spawn(ChildProcess.make(command, { shell: true })).pipe(
210 * Effect.flatMap(({ exitCode, stdout, stderr }) =>
211 * Effect.all({
212 * exitCode,
213 * stdout: stdout.pipe(Stream.decodeText, Stream.mkString),
214 * stderr: stderr.pipe(Stream.decodeText, Stream.mkString),
215 * }),
216 * ),
217 * Effect.scoped,
218 * ),
219 * fetch: Effect.succeed(
220 * HttpServerResponse.text("Hello from container!"),
221 * ),
222 * });
223 * }),
224 * );
225 * ```
226 *
227 * ### Async Workers
228 * An async Worker can host a container-backed Durable Object class that
229 * ships as plain JavaScript — `@cloudflare/sandbox`'s `Sandbox`, or your
230 * own class extending `@cloudflare/containers`' `Container`. The class
231 * lives in the worker script; `Container` (the npm one) handles the
232 * lifecycle and forwards `fetch` to the port inside the container.
233 *
234 * **Example:** The worker script exports the container-backed class
235 * ```typescript
236 * // src/worker.ts
237 * import { Container } from "@cloudflare/containers";
238 *
239 * export class Sandbox extends Container {
240 * defaultPort = 8080;
241 * }
242 * ```
243 *
244 * Declare it in the stack by binding a `Cloudflare.Container` in the
245 * Worker's `env` — the Container **is** the Durable Object binding and its
246 * ContainerApplication together. Alchemy emits the
247 * `durable_object_namespace` binding, marks the class as container-backed
248 * in the script metadata, provisions the ContainerApplication, and attaches
249 * it to the class's namespace. The Durable Object class name defaults to
250 * the binding name (the `env` key); set `className` when the exported class
251 * is named differently.
252 *
253 * **Example:** Binding the container-backed class in the stack
254 * ```typescript
255 * // alchemy.run.ts
256 * import type { Sandbox } from "./src/worker.ts";
257 *
258 * export const Worker = Cloudflare.Worker("Worker", {
259 * main: "./src/worker.ts",
260 * env: {
261 * Sandbox: Cloudflare.Container<Sandbox>("Sandbox", {
262 * image: "docker.io/cloudflare/sandbox:0.1.3",
263 * }),
264 * },
265 * });
266 * ```
267 *
268 * The type parameter (`Container<Sandbox>`) is the class from `worker.ts` —
269 * it types `env.Sandbox` as `DurableObjectNamespace<Sandbox>` via
270 * `Cloudflare.InferEnv`, so the handler reaches the container with full
271 * types.
272 *
273 * **Example:** Reaching the container from the async handler
274 * ```typescript
275 * // src/worker.ts
276 * import { getContainer } from "@cloudflare/containers";
277 * import type * as Cloudflare from "alchemy/Cloudflare";
278 * import type { Worker } from "../alchemy.run.ts";
279 *
280 * export default {
281 * async fetch(request: Request, env: Cloudflare.InferEnv<typeof Worker>) {
282 * return getContainer(env.Sandbox, "default").fetch(request);
283 * },
284 * };
285 * ```
286 *
287 * ### Image Sources
288 * A container's image comes from one of three sources, picked by which
289 * prop you set:
290 *
291 * - `main` — bundle your Effect program into a generated image.
292 * - `context` (+ optional `dockerfile`) — build your own Dockerfile.
293 * - `image` — pull a pre-built remote image and re-push it.
294 *
295 * Only the `main` source bundles and injects an Effect runtime — so it
296 * has a typed shape and a `.make(props, impl)` runtime. The other two
297 * ship an arbitrary image as-is: they have no runtime to provide, so
298 * you declare the class with its props inline and register it purely
299 * via `Cloudflare.Containers.layer` from the hosting Durable Object.
300 *
301 * **Example:** Effect-native image (`main`)
302 * ```typescript
303 * // Alchemy bundles this file's Effect program and bakes it into a
304 * // generated image as the entrypoint.
305 * export class Sandbox extends Cloudflare.Container<
306 * Sandbox,
307 * { ping: () => Effect.Effect<string> }
308 * >()("Sandbox") {}
309 *
310 * export default Sandbox.make(
311 * { main: import.meta.url },
312 * Effect.gen(function* () {
313 * return Sandbox.of({
314 * ping: () => Effect.succeed("pong"),
315 * fetch: Effect.succeed(HttpServerResponse.text("hello")),
316 * });
317 * }),
318 * );
319 * ```
320 *
321 * **Example:** Build your own Dockerfile (`context` / `dockerfile`)
322 * ```typescript
323 * // Alchemy builds the Dockerfile against the context directory — no
324 * // Effect bundling, no `.make()`. `dockerfile` defaults to
325 * // `<context>/Dockerfile`. The props are declared inline on the tag.
326 * export class Web extends Cloudflare.Container<Web>()("Web", {
327 * context: `${import.meta.dirname}/context`,
328 * }) {}
329 * ```
330 *
331 * Builds are cached by default at
332 * `registry.cloudflare.com/<account-id>/<application-physical-name>`.
333 * The generated physical name includes the stage and resource instance, so
334 * subsequent updates in that stage can reuse matching images. Replacement or
335 * destroy/recreate can change the name and start a new cache. This does not
336 * automatically share one repository across every container in the stage.
337 * The same defaults apply to inline Dockerfiles and Effect-native `main` builds.
338 *
339 * **Example:** Reuse builds across stages
340 * ```typescript
341 * export class Web extends Cloudflare.Container<Web>()("Web", {
342 * context: `${import.meta.dirname}/context`,
343 * publish: { repository: "web" },
344 * }) {}
345 * ```
346 *
347 * `repository: "web"` names the destination repository, not a source image or
348 * the container application. With the default registry host, Alchemy lowercases
349 * the name and expands it to `registry.cloudflare.com/<account-id>/web`.
350 * Supply only the repository name, without a registry host, account ID, tag,
351 * or digest. A build produces these references:
352 *
353 * ```text
354 * Published build: registry.cloudflare.com/<account-id>/web:<build-hash>
355 * Build cache: registry.cloudflare.com/<account-id>/web:buildcache
356 * Deployed image: registry.cloudflare.com/<account-id>/web@sha256:<manifest-digest>
357 * ```
358 *
359 * The build hash identifies the inputs; the manifest digest identifies the
360 * published artifact. Applications and stages in the same account can use
361 * `publish: { repository: "web" }` to reuse matching published builds. Changed
362 * inputs produce another hash tag in the same repository. Builds targeting
363 * that repository import reusable layers from its shared `:buildcache` tag,
364 * including when full input hashes differ. This mutable tag points to the
365 * latest exported inline cache, not a combined cache of every historical
366 * image. Reusing a finished image does not move the layer-cache tag.
367 *
368 * Pin base images and downloaded dependencies: changes outside the build
369 * context cannot invalidate the input hash. Each stage still has its own
370 * Container application, runtime settings, and instances; only images and
371 * build layers are shared.
372 *
373 * **Example:** Publish an existing image into a named repository
374 * ```typescript
375 * export class Proxy extends Cloudflare.Container<Proxy>()("Proxy", {
376 * image: "nginx:alpine",
377 * publish: { repository: "web-proxy" },
378 * }) {}
379 * // Re-publishes nginx into registry.cloudflare.com/<account-id>/web-proxy
380 * // and deploys registry.cloudflare.com/<account-id>/web-proxy@sha256:<manifest-digest>.
381 * ```
382 *
383 * Remote images are re-published without building them. An image already in
384 * the target registry keeps its existing repository; `publish.repository`
385 * does not copy it into another one.
386 *
387 * **Example:** Remote image (`image`)
388 * ```typescript
389 * // Alchemy pulls the public image and re-pushes it to Cloudflare's
390 * // registry — no build, no bundling, no `.make()`.
391 * export class Echo extends Cloudflare.Container<Echo>()("Echo", {
392 * image: "mendhak/http-https-echo:latest",
393 * }) {}
394 * ```
395 *
396 * **Example:** Reaching an arbitrary image's port from a Durable Object
397 * ```typescript
398 * // `external` and `remote` images expose no RPC methods, so the DO
399 * // talks to them purely over their TCP port via `getTcpPort`.
400 * export class WebObject extends Cloudflare.DurableObject<WebObject>()(
401 * "WebObject",
402 * Effect.gen(function* () {
403 * const web = yield* Web;
404 * return Effect.gen(function* () {
405 * return {
406 * hello: () =>
407 * Effect.gen(function* () {
408 * const { fetch } = yield* web.getTcpPort(8080);
409 * const res = yield* fetch(HttpClientRequest.get("http://container/"));
410 * return yield* res.text;
411 * }),
412 * };
413 * });
414 * }).pipe(Effect.provide(Cloudflare.Containers.layer(Web))),
415 * ) {}
416 * ```
417 *
418 * ### Bundling & Tree-shaking
419 * `main` is bundled with rolldown at deploy time. Unused code is
420 * tree-shaken. `effect`, alchemy, and `@distilled.cloud` are marked
421 * pure so unused parts prune more aggressively. Your app is not
422 * marked pure.
423 *
424 * **Example:** Mark additional packages as pure
425 * Only list packages with no top-level side effects.
426 * ```typescript
427 * {
428 * main: import.meta.url,
429 * build: {
430 * pure: { packages: ["my-lib", "@my-scope/*"] },
431 * },
432 * }
433 * ```
434 *
435 * **Example:** Turn it off
436 * ```typescript
437 * {
438 * main: import.meta.url,
439 * build: { pure: false },
440 * }
441 * ```
442 *
443 * ### Configuration
444 * The props object — the first argument to `.make()` — accepts `main`
445 * (entrypoint file), `instanceType` (compute size), `runtime`
446 * (`"bun"` or `"node"`), and `observability` settings. Use
447 * `Stack.useSync` to read the surrounding stack and pick a beefier
448 * `instanceType` in prod while keeping the cheap `dev` instance for
449 * preview environments.
450 *
451 * **Example:** Stage-dependent configuration
452 * ```typescript
453 * export const SandboxLive = Sandbox.make(
454 * Stack.useSync((stack) => ({
455 * main: import.meta.url,
456 * instanceType: stack.stage === "prod" ? "standard-1" : "dev",
457 * observability: { logs: { enabled: true } },
458 * })),
459 * Effect.gen(function* () {
460 * return Sandbox.of({ exec: (cmd) => ... });
461 * }),
462 * );
463 * ```
464 *
465 * ### Environment Variables
466 * A container is a process, not a Worker: it has no bindings, so every
467 * piece of configuration reaches it through `env`. Each entry lands on
468 * the deployment and shows up in `process.env` inside the image —
469 * generated (`main`), built (`context`), or pre-built (`image`) alike.
470 * Wrap a secret in `Redacted` to keep it encrypted in state and out of
471 * plan output; the container still reads a plain string.
472 *
473 * **Example:** Plain and secret env values
474 * ```typescript
475 * export class Api extends Cloudflare.Container<Api>()("Api", {
476 * context: `${import.meta.dirname}/api`,
477 * ports: [{ name: "http", port: 8080 }],
478 * env: {
479 * PORT: "8080",
480 * SESSION_KEY: Redacted.make(process.env.SESSION_KEY!),
481 * },
482 * }) {}
483 * ```
484 *
485 * ### Props from Other Resources
486 * A class body is module scope, so there is nowhere to `yield*` the
487 * database, queue, or bucket whose output you need — and a bare
488 * declaration is an Effect, not a resolved handle, so
489 * `Uploads.bucketName` reads as `undefined`. Pass the props as an
490 * `Effect.gen` instead: inside it sibling resources resolve normally,
491 * and the reference orders the deploy.
492 *
493 * **Example:** Threading a sibling resource's output into `env`
494 * ```typescript
495 * export const Uploads = Cloudflare.R2.Bucket("Uploads");
496 *
497 * export class Api extends Cloudflare.Container<Api>()(
498 * "Api",
499 * Effect.gen(function* () {
500 * const uploads = yield* Uploads;
501 * return {
502 * context: `${import.meta.dirname}/api`,
503 * env: { BUCKET_NAME: uploads.bucketName },
504 * };
505 * }),
506 * ) {}
507 * ```
508 *
509 * ### Database Connections
510 * An effectful (`main`) container runs your Effect program, so it
511 * resolves a database capability the same way a Worker does — you never
512 * name `DATABASE_URL`. The container has no bindings (it is a process,
513 * not a Worker), so `Prisma.Connect` writes the connection's outputs
514 * onto the deployment as environment variables and reads them back at
515 * runtime; the capability owns both ends.
516 *
517 * **Example:** Binding a Prisma connection inside the container runtime
518 * ```typescript
519 * export default Api.make(
520 * { main: import.meta.url },
521 * Effect.gen(function* () {
522 * const db = yield* Prisma.Connect(Connection);
523 * const sql = yield* SQL.Postgres({ url: db.databaseUrl });
524 *
525 * return Api.of({
526 * fetch: Effect.gen(function* () {
527 * const users = yield* sql`SELECT * FROM users`;
528 * return yield* HttpServerResponse.json(users);
529 * }),
530 * });
531 * }).pipe(Effect.provide(Prisma.ConnectBinding)),
532 * );
533 * ```
534 *
535 * An image you brought yourself knows nothing about alchemy, so there is
536 * no capability to bind — name the variable and hand it the provider's
537 * **pooled** connection string.
538 *
539 * **Example:** Passing a pooled database URL to an arbitrary image
540 * ```typescript
541 * export class Web extends Cloudflare.Container<Web>()(
542 * "Web",
543 * Effect.gen(function* () {
544 * const connection = yield* Connection;
545 * return {
546 * context: `${import.meta.dirname}/web`,
547 * env: { DATABASE_URL: connection.databaseUrl },
548 * };
549 * }),
550 * ) {}
551 * ```
552 *
553 * Either way, start it with
554 * `Cloudflare.Containers.layer(Api, { enableInternet: true })` — without
555 * outbound networking the container never reaches the database.
556 * `Cloudflare.Hyperdrive.Connect` is the one that cannot work here: it
557 * *is* a workerd binding, so no container process can resolve it.
558 *
559 * ### Stack-level wiring
560 * The `.make()` `export default` is the side-effect that registers
561 * the container's runtime. It must be reachable from your
562 * `alchemy.run.ts` so the bundler emits the runtime entrypoint.
563 * Provide it on the Stack's generator with `Effect.provide`.
564 *
565 * **Example:** Wiring SandboxLive into the Stack
566 * ```typescript
567 * // alchemy.run.ts
568 * import SandboxLive from "./src/Sandbox.runtime.ts";
569 *
570 * export default Alchemy.Stack(
571 * "MyApp",
572 * { providers: Cloudflare.providers(), state: Cloudflare.state() },
573 * Effect.gen(function* () {
574 * const worker = yield* Worker;
575 * return { url: worker.url };
576 * }).pipe(Effect.provide(SandboxLive)),
577 * );
578 * ```
579 *
580 * ### Calling from a Durable Object
581 * `yield* Sandbox` resolves a **running** container instance — every
582 * method declared on the container's shape **plus** a `getTcpPort`
583 * helper. Provide `Cloudflare.Containers.layer(Sandbox, …)` on the
584 * DO's init to configure how the container runs; that layer binds,
585 * starts, and monitors it and satisfies the `Sandbox` tag. Because
586 * only the class is imported, the runtime implementation in
587 * `Sandbox.runtime.ts` is tree-shaken out of the DO's bundle.
588 *
589 * **Example:** Running a container from a DO
590 * ```typescript
591 * export default class Agent extends Cloudflare.DurableObject<Agent>()(
592 * "Agents",
593 * Effect.gen(function* () {
594 * const sandbox = yield* Sandbox;
595 *
596 * return Effect.gen(function* () {
597 * return {
598 * exec: (cmd: string) => sandbox.exec(cmd),
599 * };
600 * });
601 * }).pipe(
602 * Effect.provide(
603 * Cloudflare.Containers.layer(Sandbox, { enableInternet: true }),
604 * ),
605 * ),
606 * ) {}
607 * ```
608 *
609 * ### HTTP Requests to Container Ports
610 * Use `getTcpPort` on the running container instance to get a `fetch`
611 * handle for a specific port. This lets you make HTTP requests to
612 * servers running inside the container process.
613 *
614 * **Example:** Fetching from a container port
615 * ```typescript
616 * export default class Agent extends Cloudflare.DurableObject<Agent>()(
617 * "Agents",
618 * Effect.gen(function* () {
619 * const sandbox = yield* Sandbox;
620 *
621 * return Effect.gen(function* () {
622 * const { fetch } = yield* sandbox.getTcpPort(3000);
623 *
624 * return {
625 * health: () =>
626 * Effect.gen(function* () {
627 * const response = yield* fetch(
628 * HttpClientRequest.get("http://container/health"),
629 * );
630 * return yield* response.text;
631 * }),
632 * };
633 * });
634 * }).pipe(
635 * Effect.provide(
636 * Cloudflare.Containers.layer(Sandbox, { enableInternet: true }),
637 * ),
638 * ),
639 * ) {}
640 * ```
641 *
642 * @resource
643 * @product Containers
644 * @category Workers & Compute
645 */
646export const Container: ResourceClassLike<ContainerApplication> &
647 Pick<ResourceClass<ContainerApplication>, "ref"> & {
648 <DOShape = unknown, const Id extends string = string, PropsReq = never>(
649 id: Id,
650 props: ImageContainerProps<PropsReq>,
651 ): Container.Decl<Container<Id>, {}, Id, PropsReq, DOShape>;
652 <Self>(): {
653 <const Id extends string, PropsReq = never>(
654 id: Id,
655 props: ImageContainerProps<PropsReq>,
656 ): Container.Decl<Self, {}, Id, PropsReq>;
657 };
658 <Self, Shape>(): {
659 <const Id extends string>(
660 id: Id,
661 ): Container.Decl<Self, Shape, Id, Container.Application<Self>>;
662 };
663 } = Object.assign(
664 (...args: any[]) => {
665 if (args.length === 0) {
666 return (...args: any[]) => {
667 if (args.length === 1) {
668 const [id] = args as [string];
669 const tag = ContainerPlatform()(id);
670 // `yield* MyContainer` resolves the *started* instance tag, which is
671 // provided by `layer(MyContainer)`. The bind effect (which
672 // registers the DO + Worker bindings and produces the runtime
673 // handle) is stashed so `startContainer` can run it from inside that
674 // layer — see ContainerPlatform.bind / StartContainer.ts.
675 // NOTE: no `~alchemy/Container/ClassName` marker here — an
676 // effectful (`main`) container is not bindable on an async
677 // Worker's `env` (its application is created by the `.make()`
678 // Layer inside an Effect-native Durable Object host), so it must
679 // not be picked up by bindWorkerAsyncBindings' container branch.
680 return Object.assign(effectClass(ContainerTag(id)), {
681 "~alchemy/Id": id,
682 "~alchemy/Container/Binding": ContainerPlatform.bind(tag),
683 make: (props: any, impl: any) => tag.make(props, impl),
684 // yield* MyContainer.Application to get the ContainerApplication Resource Outputs
685 Application: tag,
686 of: (shape: any) => shape,
687 });
688 } else {
689 return Container(...(args as [string, any]));
690 }
691 };
692 } else {
693 const [id, props] = args as [string, any];
694 const resource = ContainerPlatform(id, props);
695 return Object.assign(effectClass(ContainerTag(id)), {
696 "~alchemy/Id": id,
697 "~alchemy/Container/Binding": ContainerPlatform.bind(resource),
698 // The Durable Object class name this container backs when bound on
699 // an async Worker's `env` (see bindWorkerAsyncBindings). Defaults to
700 // the binding name at bind time when no explicit `className` is set.
701 // Effect-valued props cannot be read synchronously here, so carry
702 // the unresolved lookup and let `bindContainerClass` await it.
703 "~alchemy/Container/ClassName": Effect.isEffect(props)
704 ? Effect.map(
705 props as Effect.Effect<{ className?: string } | undefined>,
706 (resolved) => resolved?.className,
707 )
708 : (props as { className?: string })?.className,
709 // yield* MyContainer.Application to get the ContainerApplication Resource Outputs
710 Application: resource,
711 of: (shape: any) => shape,
712 });
713 }
714 },
715 {
716 Type: ContainerTypeId,
717 // Read `ContainerPlatform` lazily: this module and ContainerPlatform.ts
718 // import each other, so it may not be initialized at module load.
719 ref: (id: string, options?: { stage?: string; stack?: string }) =>
720 (
721 ContainerPlatform as unknown as Pick<
722 ResourceClass<ContainerApplication>,
723 "ref"
724 >
725 ).ref(id, options),
726 },
727) as any;
728
729export declare namespace Container {
730 export interface Decl<
731 Self = any,
732 Shape = any,
733 Id extends string = string,
734 Req = never,
735 DOShape = unknown,
736 >
737 extends Effect.Effect<Self, never, Providers | Req>, Rpc<Shape>, Named<Id> {
738 new (): Container<Id> & Shape;
739 /**
740 * @internal phantom — the Durable Object class type backing this
741 * container when it is bound on an async Worker's `env`. Drives
742 * `InferEnv` (`env.NAME` becomes `DurableObjectNamespace<DOShape>`).
743 */
744 readonly "~alchemy/Container/Shape": DOShape;
745 /**
746 * @internal — the explicit `className` from props (`undefined` defaults
747 * to the binding name at bind time). Doubles as the runtime marker that
748 * identifies an async-bindable Container declaration in a Worker's `env`
749 * (see `bindWorkerAsyncBindings`).
750 */
751 readonly "~alchemy/Container/ClassName":
752 | string
753 | undefined
754 | Effect.Effect<string | undefined>;
755 /**
756 * The underlying {@link ContainerApplication} resource declaration —
757 * `yield*` it to get the application's Output attributes.
758 */
759 Application: Effect.Effect<ContainerApplication<Self>, never, Providers>;
760 make: <InitReq = never, WorkerReq = never, PropsReq = never>(
761 props:
762 | InputProps<EffectfulContainerProps>
763 | Effect.Effect<
764 InputProps<EffectfulContainerProps>,
765 Config.ConfigError,
766 PropsReq
767 >,
768 impl: Effect.Effect<
769 Shape & WorkerShape<WorkerReq>,
770 Config.ConfigError,
771 InitReq
772 >,
773 ) => Layer.Layer<Application<Self>, never, Providers>;
774 of(shape: Shape & WorkerShape): Shape;
775 }
776 export namespace Decl {
777 export type Any = Decl<any, any, string, any, any>;
778 }
779
780 export interface Application<Self> {
781 "~alchemy/Kind": "ContainerApplication";
782 "~alchemy/Self": Self;
783 }
784
785 export type Instance<Shape = any> = Container &
786 Shape & {
787 getTcpPort: (portNumber: number) => Effect.Effect<{
788 fetch: {
789 (
790 request: HttpClientRequest.HttpClientRequest,
791 ): Effect.Effect<HttpClientResponse.HttpClientResponse>;
792 (
793 request: HttpServerRequest.HttpServerRequest,
794 ): Effect.Effect<HttpServerResponse.HttpServerResponse>;
795 };
796 }>;
797 };
798}