alchemy 2.0.0-beta.82 → 2.0.0-beta.83

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/lib/Cloudflare/Containers/Container.d.ts +128 -40
  2. package/lib/Cloudflare/Containers/Container.d.ts.map +1 -1
  3. package/lib/Cloudflare/Containers/Container.js +124 -24
  4. package/lib/Cloudflare/Containers/Container.js.map +1 -1
  5. package/lib/Cloudflare/Containers/ContainerApplication.d.ts +59 -50
  6. package/lib/Cloudflare/Containers/ContainerApplication.d.ts.map +1 -1
  7. package/lib/Cloudflare/Containers/ContainerApplication.js.map +1 -1
  8. package/lib/Cloudflare/Containers/ContainerClient.d.ts +20 -7
  9. package/lib/Cloudflare/Containers/ContainerClient.d.ts.map +1 -1
  10. package/lib/Cloudflare/Containers/ContainerClient.js +22 -5
  11. package/lib/Cloudflare/Containers/ContainerClient.js.map +1 -1
  12. package/lib/Cloudflare/Containers/ContainerConfiguration.d.ts +132 -13
  13. package/lib/Cloudflare/Containers/ContainerConfiguration.d.ts.map +1 -1
  14. package/lib/Cloudflare/Containers/ContainerConfiguration.js +219 -35
  15. package/lib/Cloudflare/Containers/ContainerConfiguration.js.map +1 -1
  16. package/lib/Cloudflare/Containers/ContainerPlatform.d.ts +1 -1
  17. package/lib/Cloudflare/Containers/ContainerPlatform.d.ts.map +1 -1
  18. package/lib/Cloudflare/Containers/ContainerPlatform.js +21 -2
  19. package/lib/Cloudflare/Containers/ContainerPlatform.js.map +1 -1
  20. package/lib/Cloudflare/Containers/ContainerProvider.d.ts.map +1 -1
  21. package/lib/Cloudflare/Containers/ContainerProvider.js +74 -177
  22. package/lib/Cloudflare/Containers/ContainerProvider.js.map +1 -1
  23. package/lib/Cloudflare/Containers/DurableObjectApplication.d.ts +84 -0
  24. package/lib/Cloudflare/Containers/DurableObjectApplication.d.ts.map +1 -0
  25. package/lib/Cloudflare/Containers/DurableObjectApplication.js +249 -0
  26. package/lib/Cloudflare/Containers/DurableObjectApplication.js.map +1 -0
  27. package/lib/Cloudflare/Containers/LocalContainerProvider.d.ts.map +1 -1
  28. package/lib/Cloudflare/Containers/LocalContainerProvider.js +20 -41
  29. package/lib/Cloudflare/Containers/LocalContainerProvider.js.map +1 -1
  30. package/lib/Cloudflare/Containers/index.d.ts +2 -1
  31. package/lib/Cloudflare/Containers/index.d.ts.map +1 -1
  32. package/lib/Cloudflare/Containers/index.js +2 -1
  33. package/lib/Cloudflare/Containers/index.js.map +1 -1
  34. package/lib/Cloudflare/Providers.d.ts.map +1 -1
  35. package/lib/Cloudflare/Providers.js +2 -1
  36. package/lib/Cloudflare/Providers.js.map +1 -1
  37. package/lib/Cloudflare/StateStore/Api.d.ts +3 -0
  38. package/lib/Cloudflare/Workers/DurableObject.d.ts +75 -1
  39. package/lib/Cloudflare/Workers/DurableObject.d.ts.map +1 -1
  40. package/lib/Cloudflare/Workers/DurableObject.js +50 -0
  41. package/lib/Cloudflare/Workers/DurableObject.js.map +1 -1
  42. package/lib/Cloudflare/Workers/LocalWorkerProvider.d.ts.map +1 -1
  43. package/lib/Cloudflare/Workers/LocalWorkerProvider.js +29 -23
  44. package/lib/Cloudflare/Workers/LocalWorkerProvider.js.map +1 -1
  45. package/lib/Cloudflare/Workers/Worker.d.ts +2 -0
  46. package/lib/Cloudflare/Workers/Worker.d.ts.map +1 -1
  47. package/lib/Cloudflare/Workers/Worker.js.map +1 -1
  48. package/lib/Cloudflare/Workers/WorkerAsyncBindings.d.ts.map +1 -1
  49. package/lib/Cloudflare/Workers/WorkerAsyncBindings.js +67 -4
  50. package/lib/Cloudflare/Workers/WorkerAsyncBindings.js.map +1 -1
  51. package/lib/Cloudflare/Workers/WorkerBinding.d.ts +6 -0
  52. package/lib/Cloudflare/Workers/WorkerBinding.d.ts.map +1 -1
  53. package/lib/Cloudflare/Workers/WorkerProvider.d.ts.map +1 -1
  54. package/lib/Cloudflare/Workers/WorkerProvider.js +54 -8
  55. package/lib/Cloudflare/Workers/WorkerProvider.js.map +1 -1
  56. package/lib/State/HttpStateApi.d.ts +13 -13
  57. package/package.json +6 -6
  58. package/src/Cloudflare/Containers/Container.ts +126 -56
  59. package/src/Cloudflare/Containers/ContainerApplication.ts +60 -53
  60. package/src/Cloudflare/Containers/ContainerClient.ts +47 -13
  61. package/src/Cloudflare/Containers/ContainerConfiguration.ts +316 -44
  62. package/src/Cloudflare/Containers/ContainerPlatform.ts +27 -3
  63. package/src/Cloudflare/Containers/ContainerProvider.ts +96 -256
  64. package/src/Cloudflare/Containers/DurableObjectApplication.ts +382 -0
  65. package/src/Cloudflare/Containers/LocalContainerProvider.ts +20 -48
  66. package/src/Cloudflare/Containers/index.ts +10 -0
  67. package/src/Cloudflare/Providers.ts +2 -0
  68. package/src/Cloudflare/Workers/DurableObject.ts +77 -1
  69. package/src/Cloudflare/Workers/LocalWorkerProvider.ts +32 -21
  70. package/src/Cloudflare/Workers/Worker.ts +2 -0
  71. package/src/Cloudflare/Workers/WorkerAsyncBindings.ts +95 -4
  72. package/src/Cloudflare/Workers/WorkerProvider.ts +78 -13
@@ -107,37 +107,37 @@ export declare const VersionResponse: Schema.Struct<{
107
107
  export declare const GetVersion: HttpApiEndpoint.HttpApiEndpoint<"getVersion", "GET", "/version", never, never, never, never, Schema.toCodecJson<Schema.Struct<{
108
108
  readonly version: Schema.Number;
109
109
  }>>, never, never, never>;
110
- declare const StateGroup_base: HttpApiGroup.HttpApiGroup<"state", HttpApiEndpoint.HttpApiEndpoint<"listStacks", "GET", "/state/stacks", never, never, never, never, Schema.toCodecJson<Schema.$Array<Schema.String>>, never, StateAuth, BearerTokenValidator> | HttpApiEndpoint.HttpApiEndpoint<"listStages", "GET", "/state/stacks/:stack/stages", Schema.toCodecStringTree<Schema.Struct<{
111
- readonly stack: Schema.String;
112
- }>>, never, never, never, Schema.toCodecJson<Schema.$Array<Schema.String>>, never, StateAuth, BearerTokenValidator> | HttpApiEndpoint.HttpApiEndpoint<"deleteStack", "DELETE", "/state/stacks/:stack", Schema.toCodecStringTree<Schema.Struct<{
110
+ declare const StateGroup_base: HttpApiGroup.HttpApiGroup<"state", HttpApiEndpoint.HttpApiEndpoint<"deleteStack", "DELETE", "/state/stacks/:stack", Schema.toCodecStringTree<Schema.Struct<{
113
111
  readonly stack: Schema.String;
114
112
  }>>, Schema.toCodecStringTree<Schema.Struct<{
115
113
  readonly stage: Schema.optional<Schema.String>;
116
- }>>, never, never, Schema.toCodecJson<HttpApiSchema.NoContent>, never, StateAuth, BearerTokenValidator> | HttpApiEndpoint.HttpApiEndpoint<"getReplacedResources", "GET", "/state/stacks/:stack/stages/:stage/replaced-resources", Schema.toCodecStringTree<Schema.Struct<{
114
+ }>>, never, never, Schema.toCodecJson<HttpApiSchema.NoContent>, never, StateAuth, BearerTokenValidator> | HttpApiEndpoint.HttpApiEndpoint<"deleteState", "DELETE", "/state/stacks/:stack/stages/:stage/resources/:fqn", Schema.toCodecStringTree<Schema.Struct<{
117
115
  readonly stack: Schema.String;
118
116
  readonly stage: Schema.String;
119
- }>>, never, never, never, Schema.toCodecJson<Schema.$Array<Schema.Any>>, never, StateAuth, BearerTokenValidator> | HttpApiEndpoint.HttpApiEndpoint<"listResources", "GET", "/state/stacks/:stack/stages/:stage/resources", Schema.toCodecStringTree<Schema.Struct<{
117
+ readonly fqn: Schema.String;
118
+ }>>, never, never, never, Schema.toCodecJson<HttpApiSchema.NoContent>, never, StateAuth, BearerTokenValidator> | HttpApiEndpoint.HttpApiEndpoint<"getReplacedResources", "GET", "/state/stacks/:stack/stages/:stage/replaced-resources", Schema.toCodecStringTree<Schema.Struct<{
120
119
  readonly stack: Schema.String;
121
120
  readonly stage: Schema.String;
122
- }>>, never, never, never, Schema.toCodecJson<Schema.$Array<Schema.String>>, never, StateAuth, BearerTokenValidator> | HttpApiEndpoint.HttpApiEndpoint<"setStackOutput", "PUT", "/state/stacks/:stack/stages/:stage/output", Schema.toCodecStringTree<Schema.Struct<{
121
+ }>>, never, never, never, Schema.toCodecJson<Schema.$Array<Schema.Any>>, never, StateAuth, BearerTokenValidator> | HttpApiEndpoint.HttpApiEndpoint<"getStackOutput", "GET", "/state/stacks/:stack/stages/:stage/output", Schema.toCodecStringTree<Schema.Struct<{
123
122
  readonly stack: Schema.String;
124
123
  readonly stage: Schema.String;
125
- }>>, never, Schema.toCodecJson<Schema.Any>, never, Schema.toCodecJson<Schema.Any>, never, StateAuth, BearerTokenValidator> | HttpApiEndpoint.HttpApiEndpoint<"getStackOutput", "GET", "/state/stacks/:stack/stages/:stage/output", Schema.toCodecStringTree<Schema.Struct<{
124
+ }>>, never, never, never, Schema.toCodecJson<Schema.UndefinedOr<Schema.Any>>, never, StateAuth, BearerTokenValidator> | HttpApiEndpoint.HttpApiEndpoint<"getState", "GET", "/state/stacks/:stack/stages/:stage/resources/:fqn", Schema.toCodecStringTree<Schema.Struct<{
126
125
  readonly stack: Schema.String;
127
126
  readonly stage: Schema.String;
128
- }>>, never, never, never, Schema.toCodecJson<Schema.UndefinedOr<Schema.Any>>, never, StateAuth, BearerTokenValidator> | HttpApiEndpoint.HttpApiEndpoint<"setState", "PUT", "/state/stacks/:stack/stages/:stage/resources/:fqn", Schema.toCodecStringTree<Schema.Struct<{
127
+ readonly fqn: Schema.String;
128
+ }>>, never, never, never, Schema.toCodecJson<Schema.UndefinedOr<Schema.Any>>, never, StateAuth, BearerTokenValidator> | HttpApiEndpoint.HttpApiEndpoint<"listResources", "GET", "/state/stacks/:stack/stages/:stage/resources", Schema.toCodecStringTree<Schema.Struct<{
129
129
  readonly stack: Schema.String;
130
130
  readonly stage: Schema.String;
131
- readonly fqn: Schema.String;
132
- }>>, never, Schema.toCodecJson<Schema.Any>, never, Schema.toCodecJson<Schema.Any>, never, StateAuth, BearerTokenValidator> | HttpApiEndpoint.HttpApiEndpoint<"deleteState", "DELETE", "/state/stacks/:stack/stages/:stage/resources/:fqn", Schema.toCodecStringTree<Schema.Struct<{
131
+ }>>, never, never, never, Schema.toCodecJson<Schema.$Array<Schema.String>>, never, StateAuth, BearerTokenValidator> | HttpApiEndpoint.HttpApiEndpoint<"listStacks", "GET", "/state/stacks", never, never, never, never, Schema.toCodecJson<Schema.$Array<Schema.String>>, never, StateAuth, BearerTokenValidator> | HttpApiEndpoint.HttpApiEndpoint<"listStages", "GET", "/state/stacks/:stack/stages", Schema.toCodecStringTree<Schema.Struct<{
132
+ readonly stack: Schema.String;
133
+ }>>, never, never, never, Schema.toCodecJson<Schema.$Array<Schema.String>>, never, StateAuth, BearerTokenValidator> | HttpApiEndpoint.HttpApiEndpoint<"setStackOutput", "PUT", "/state/stacks/:stack/stages/:stage/output", Schema.toCodecStringTree<Schema.Struct<{
133
134
  readonly stack: Schema.String;
134
135
  readonly stage: Schema.String;
135
- readonly fqn: Schema.String;
136
- }>>, never, never, never, Schema.toCodecJson<HttpApiSchema.NoContent>, never, StateAuth, BearerTokenValidator> | HttpApiEndpoint.HttpApiEndpoint<"getState", "GET", "/state/stacks/:stack/stages/:stage/resources/:fqn", Schema.toCodecStringTree<Schema.Struct<{
136
+ }>>, never, Schema.toCodecJson<Schema.Any>, never, Schema.toCodecJson<Schema.Any>, never, StateAuth, BearerTokenValidator> | HttpApiEndpoint.HttpApiEndpoint<"setState", "PUT", "/state/stacks/:stack/stages/:stage/resources/:fqn", Schema.toCodecStringTree<Schema.Struct<{
137
137
  readonly stack: Schema.String;
138
138
  readonly stage: Schema.String;
139
139
  readonly fqn: Schema.String;
140
- }>>, never, never, never, Schema.toCodecJson<Schema.UndefinedOr<Schema.Any>>, never, StateAuth, BearerTokenValidator>, false>;
140
+ }>>, never, Schema.toCodecJson<Schema.Any>, never, Schema.toCodecJson<Schema.Any>, never, StateAuth, BearerTokenValidator>, false>;
141
141
  export declare class StateGroup extends StateGroup_base {
142
142
  }
143
143
  declare const VersionGroup_base: HttpApiGroup.HttpApiGroup<"version", HttpApiEndpoint.HttpApiEndpoint<"getVersion", "GET", "/version", never, never, never, never, Schema.toCodecJson<Schema.Struct<{
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "alchemy",
3
- "version": "2.0.0-beta.82",
3
+ "version": "2.0.0-beta.83",
4
4
  "description": "Infrastructure as Effects for TypeScript.",
5
5
  "keywords": [
6
6
  "alchemy",
@@ -333,9 +333,9 @@
333
333
  "access": "public"
334
334
  },
335
335
  "dependencies": {
336
- "@alchemy.run/cloudflare-runtime": "2.0.0-beta.82",
337
- "@alchemy.run/floci": "2.0.0-beta.82",
338
- "@alchemy.run/node-utils": "2.0.0-beta.82",
336
+ "@alchemy.run/cloudflare-runtime": "2.0.0-beta.83",
337
+ "@alchemy.run/floci": "2.0.0-beta.83",
338
+ "@alchemy.run/node-utils": "2.0.0-beta.83",
339
339
  "@alchemy.run/sigil": "0.1.0-alpha.1",
340
340
  "@distilled.cloud/acme": "1.0.0-rc.15",
341
341
  "@distilled.cloud/aws": "1.0.0-rc.15",
@@ -373,7 +373,7 @@
373
373
  "yaml": "^2.9.0"
374
374
  },
375
375
  "devDependencies": {
376
- "@alchemy.run/frontend-frameworks": "2.0.0-beta.82",
376
+ "@alchemy.run/frontend-frameworks": "2.0.0-beta.83",
377
377
  "@aws/durable-execution-sdk-js": "^2.1.0",
378
378
  "@cloudflare/containers": "^0.3.7",
379
379
  "@cloudflare/puppeteer": "^1.1.0",
@@ -447,7 +447,7 @@
447
447
  "ws": "^8.21.0"
448
448
  },
449
449
  "peerDependencies": {
450
- "@alchemy.run/frontend-frameworks": "2.0.0-beta.82",
450
+ "@alchemy.run/frontend-frameworks": "2.0.0-beta.83",
451
451
  "@aws/durable-execution-sdk-js": "^2.1.0",
452
452
  "@effect/platform-bun": "^4.0.0",
453
453
  "@effect/platform-node": "^4.0.0",
@@ -76,7 +76,6 @@ export class ContainerNotRunningError extends Data.TaggedError("ContainerNotRunn
76
76
  export type ContainerStartupOptions = cf.ContainerStartupOptions;
77
77
 
78
78
  import type {
79
- ContainerImageProps,
80
79
  DurableObjectContainerProps,
81
80
  EffectfulContainerProps,
82
81
  ExternalContainerProps,
@@ -129,24 +128,11 @@ export type ImageContainerProps<Req = never> =
129
128
  Req
130
129
  >;
131
130
 
132
- /**
133
- * Durable Object container props with statically known image names, so the
134
- * native client can type `images.<name>` as present.
135
- */
136
- type NamedImageProps<ImageName extends string> = Omit<DurableObjectContainerProps, "images"> & {
137
- images: Record<ImageName, ContainerImageProps>;
138
- };
139
-
140
- type NamedImageContainerProps<ImageName extends string, Req> =
141
- | InputProps<NamedImageProps<ImageName>>
142
- | Effect.Effect<InputProps<NamedImageProps<ImageName>>, Config.ConfigError, Req>;
143
-
144
131
  /**
145
132
  * The handle `yield* MyContainer` returns inside a Durable Object. See
146
133
  * {@link ContainerClient}.
147
134
  */
148
- export type Container<Id extends string = string, ImageName extends string = string> = Named<Id> &
149
- ContainerClient<ImageName>;
135
+ export type Container<Id extends string = string> = Named<Id> & ContainerClient;
150
136
 
151
137
  /** An RPC shape may not redeclare the container handle's own methods. */
152
138
  type ContainerShape<Shape> = {
@@ -283,39 +269,37 @@ type ContainerShape<Shape> = {
283
269
  * ```
284
270
  *
285
271
  * ### Native Durable Object Containers
286
- * Use `schedulingPolicy: "durable_object"` to choose an image and instance
287
- * size for each container at runtime. Named images are built or copied into
288
- * Cloudflare's registry, pinned to a digest, and prepared before the Worker
289
- * is uploaded. Omit `images` to use `cloudflare/debian-trixie` or restore a
290
- * snapshot without publishing an image.
272
+ * Use `schedulingPolicy: "durable_object"` to let a Durable Object pick its
273
+ * image and instance size at runtime. One Container is one image: `image`
274
+ * copies a registry image, `context`/`dockerfile` builds one. Alchemy pins the
275
+ * image to a digest and prepares it before the Worker is uploaded.
291
276
  *
292
- * `yield* Sandbox` returns the container's handle without starting it. Its
293
- * `exec` takes an argument vector and returns a scoped process with streams,
294
- * an exit code, and an `output()` collector.
277
+ * `yield* Node` returns the container's handle without starting it.
278
+ * `start()` starts the Container's own image. Its `exec` takes an argument
279
+ * vector and returns a scoped process with streams, an exit code, and an
280
+ * `output()` collector.
295
281
  *
296
- * **Example:** Execute a command in a named image
282
+ * **Example:** Execute a command in a Container's image
297
283
  * ```typescript
298
- * export class Sandbox extends Cloudflare.Container<Sandbox>()("Sandbox", {
284
+ * export class Node extends Cloudflare.Container<Node>()("Node", {
299
285
  * schedulingPolicy: "durable_object",
300
- * images: { node: { image: "node:24-slim" } },
286
+ * image: "node:24-slim",
301
287
  * }) {}
302
288
  *
303
289
  * export class Agent extends Cloudflare.DurableObject<Agent>()(
304
290
  * "Agent",
305
291
  * Effect.gen(function* () {
306
- * const sandbox = yield* Sandbox;
292
+ * const node = yield* Node;
307
293
  * return Effect.succeed({
308
294
  * exec: (args: string[]) =>
309
295
  * Effect.gen(function* () {
310
- * const images = yield* sandbox.images;
311
- * // A no-op when the container is already running.
312
- * yield* sandbox.start({
313
- * image: images.node,
296
+ * // Starts the Node image; a no-op when the container is running.
297
+ * yield* node.start({
314
298
  * instance: "lite",
315
299
  * entrypoint: ["sleep", "infinity"],
316
300
  * enableInternet: false,
317
301
  * });
318
- * const process = yield* sandbox.exec(args);
302
+ * const process = yield* node.exec(args);
319
303
  * const result = yield* process.output();
320
304
  * return { text: new TextDecoder().decode(result.stdout), exitCode: result.exitCode };
321
305
  * }).pipe(Effect.scoped),
@@ -324,13 +308,62 @@ type ContainerShape<Shape> = {
324
308
  * ) {}
325
309
  * ```
326
310
  *
311
+ * ### Several Images in One Durable Object
312
+ * Cloudflare gives each Durable Object class one container application and
313
+ * each object one container instance. Bind several Containers to choose
314
+ * between their images. Every binding adds its image to the class's
315
+ * application under the Container's id. Each handle's `start()` starts its
316
+ * own image; start one at a time and `destroy()` before switching.
317
+ *
318
+ * **Example:** A Durable Object with Node and Python images
319
+ * ```typescript
320
+ * export class Node extends Cloudflare.Container<Node>()("Node", {
321
+ * schedulingPolicy: "durable_object",
322
+ * image: "node:24-slim",
323
+ * }) {}
324
+ *
325
+ * export class Python extends Cloudflare.Container<Python>()("Python", {
326
+ * schedulingPolicy: "durable_object",
327
+ * context: "./python",
328
+ * }) {}
329
+ *
330
+ * export class Agent extends Cloudflare.DurableObject<Agent>()(
331
+ * "Agent",
332
+ * Effect.gen(function* () {
333
+ * const node = yield* Node;
334
+ * const python = yield* Python;
335
+ * const run = (cmd: string[]) =>
336
+ * Effect.scoped(Effect.flatMap(node.exec(cmd), (process) => process.output()));
337
+ * return Effect.succeed({
338
+ * node: (cmd: string[]) =>
339
+ * node
340
+ * .start({ entrypoint: ["sleep", "infinity"], enableInternet: false })
341
+ * .pipe(Effect.andThen(run(cmd))),
342
+ * python: (cmd: string[]) =>
343
+ * Effect.gen(function* () {
344
+ * // Every handle controls the same instance: switch images explicitly.
345
+ * if (yield* node.running) yield* node.destroy();
346
+ * yield* python.start({ entrypoint: ["sleep", "infinity"], enableInternet: false });
347
+ * return yield* Effect.scoped(
348
+ * Effect.flatMap(python.exec(cmd), (process) => process.output()),
349
+ * );
350
+ * }),
351
+ * });
352
+ * }),
353
+ * ) {}
354
+ * ```
355
+ *
356
+ * Application-wide settings (`ssh`, `authorizedKeys`, `observability`) may be
357
+ * declared on any of the class's Containers. Two Containers declaring
358
+ * different values fail the deploy with `ContainerApplicationSettingsConflict`.
359
+ *
327
360
  * **Example:** Save and restore the writable filesystem
328
361
  * ```typescript
329
- * const snapshot = yield* sandbox.snapshotContainer({ name: "workspace" });
362
+ * const snapshot = yield* node.snapshotContainer({ name: "workspace" });
330
363
  * // Persist this handle in Durable Object storage before stopping the instance.
331
364
  * yield* state.storage.put("snapshot", snapshot);
332
- * yield* sandbox.destroy();
333
- * yield* sandbox.start({
365
+ * yield* node.destroy();
366
+ * yield* node.start({
334
367
  * containerSnapshot: snapshot,
335
368
  * entrypoint: ["sleep", "infinity"],
336
369
  * enableInternet: false,
@@ -344,9 +377,62 @@ type ContainerShape<Shape> = {
344
377
  * when needed. Scope closure kills an unfinished exec process, but does not
345
378
  * signal its descendants. Use an Effect timeout to bound a command's lifetime.
346
379
  *
347
- * Native async Durable Objects use the same declaration on a Worker's `env`
348
- * and call `this.ctx.container` directly. Local development exposes the named
349
- * images through workerd and Docker.
380
+ * ### Native Durable Objects on Async Workers
381
+ * A plain Durable Object class on an async Worker attaches its Containers
382
+ * through its Durable Object binding:
383
+ * `Cloudflare.DurableObject(className, { containers })`. Each Container adds
384
+ * its image to the class's application, and the class reads it from
385
+ * `ctx.container.images` under the Container's id. A Container may be listed
386
+ * by several classes; each class gets its own copy of the image key.
387
+ *
388
+ * **Example:** Two classes sharing the Node image
389
+ * ```typescript
390
+ * // alchemy.run.ts
391
+ * const Node = Cloudflare.Container("Node", {
392
+ * schedulingPolicy: "durable_object",
393
+ * image: "node:24-slim",
394
+ * });
395
+ * const Python = Cloudflare.Container("Python", {
396
+ * schedulingPolicy: "durable_object",
397
+ * image: "python:3.13-slim",
398
+ * });
399
+ * const Rust = Cloudflare.Container("Rust", {
400
+ * schedulingPolicy: "durable_object",
401
+ * image: "rust:1.90-slim",
402
+ * });
403
+ * export const Worker = Cloudflare.Worker("App", {
404
+ * main: "./src/worker.ts",
405
+ * env: {
406
+ * AGENT: Cloudflare.DurableObject("Agent", { containers: [Node, Python] }),
407
+ * REVIEWER: Cloudflare.DurableObject("Reviewer", { containers: [Node, Rust] }),
408
+ * },
409
+ * });
410
+ * ```
411
+ *
412
+ * A Durable Object-managed Container placed directly in `env` fails with
413
+ * `DurableObjectContainerBindingError`; only fleet Containers bind that way.
414
+ *
415
+ * **Example:** Starting an image from the class
416
+ * ```typescript
417
+ * // src/worker.ts
418
+ * import { DurableObject } from "cloudflare:workers";
419
+ *
420
+ * export class Agent extends DurableObject {
421
+ * async run(cmd: string[]) {
422
+ * const container = this.ctx.container!;
423
+ * if (!container.running) {
424
+ * container.start({
425
+ * image: container.images.Node,
426
+ * entrypoint: ["sleep", "infinity"],
427
+ * enableInternet: false,
428
+ * });
429
+ * }
430
+ * return (await container.exec(cmd)).output();
431
+ * }
432
+ * }
433
+ * ```
434
+ *
435
+ * Local development runs every image through workerd and Docker.
350
436
  *
351
437
  * Switching an existing application to this scheduling policy requires a new
352
438
  * application and a new Durable Object class/namespace. Alchemy rejects an
@@ -683,7 +769,7 @@ type ContainerShape<Shape> = {
683
769
  * ```
684
770
  *
685
771
  * A container's shape cannot declare a method the handle already has
686
- * (`start`, `exec`, `images`, `getTcpPort`, …); declaring one is a type
772
+ * (`start`, `exec`, `image`, `getTcpPort`, …); declaring one is a type
687
773
  * error, because the handle's method would shadow the RPC call.
688
774
  *
689
775
  * ### Using a Container from a Layer
@@ -742,24 +828,11 @@ type ContainerShape<Shape> = {
742
828
  */
743
829
  export const Container: ResourceClassLike<ContainerApplication> &
744
830
  Pick<ResourceClass<ContainerApplication>, "ref"> & {
745
- <
746
- DOShape = unknown,
747
- const Id extends string = string,
748
- PropsReq = never,
749
- ImageName extends string = string,
750
- >(
751
- id: Id,
752
- props: NamedImageContainerProps<ImageName, PropsReq>,
753
- ): Container.Decl<Container<Id, ImageName>, {}, Id, PropsReq, DOShape, ImageName>;
754
831
  <DOShape = unknown, const Id extends string = string, PropsReq = never>(
755
832
  id: Id,
756
833
  props: ImageContainerProps<PropsReq>,
757
834
  ): Container.Decl<Container<Id>, {}, Id, PropsReq, DOShape>;
758
835
  <Self>(): {
759
- <const Id extends string, ImageName extends string, PropsReq = never>(
760
- id: Id,
761
- props: NamedImageContainerProps<ImageName, PropsReq>,
762
- ): Container.Decl<Self, {}, Id, PropsReq, unknown, ImageName>;
763
836
  <const Id extends string, PropsReq = never>(
764
837
  id: Id,
765
838
  props: ImageContainerProps<PropsReq>,
@@ -837,18 +910,15 @@ export declare namespace Container {
837
910
  Id extends string = string,
838
911
  Req = never,
839
912
  DOShape = unknown,
840
- ImageName extends string = string,
841
913
  >
842
914
  extends Effect.Effect<Self, never, Providers | Req>, Rpc<Shape>, Named<Id> {
843
- new (): Container<Id, ImageName> & Shape;
915
+ new (): Container<Id> & Shape;
844
916
  /**
845
917
  * @internal phantom — the Durable Object class type backing this
846
918
  * container when it is bound on an async Worker's `env`. Drives
847
919
  * `InferEnv` (`env.NAME` becomes `DurableObjectNamespace<DOShape>`).
848
920
  */
849
921
  readonly "~alchemy/Container/Shape": DOShape;
850
- /** @internal phantom — required image names available to the native client. */
851
- readonly "~alchemy/Container/Images": ImageName;
852
922
  /**
853
923
  * @internal — the explicit `className` from props (`undefined` defaults
854
924
  * to the binding name at bind time). Doubles as the runtime marker that
@@ -384,70 +384,73 @@ export interface ContainerSshConfiguration {
384
384
  port?: number;
385
385
  }
386
386
 
387
- /** An image published for a Durable Object to select when starting a container. */
388
- export type ContainerImageProps = {
389
- /** Image publication and build-cache configuration. */
390
- publish?: ContainerApplicationPropsBase["publish"];
391
- } & (
392
- | {
393
- /** Registry image to copy into Cloudflare's managed registry. */
394
- image: string;
395
- context?: never;
396
- dockerfile?: never;
397
- }
398
- | {
399
- image?: never;
400
- /** Docker build context. Defaults to the current directory. */
401
- context?: string;
402
- /** Dockerfile path or inline content. Defaults to the context's Dockerfile. */
403
- dockerfile?: string | InlineDockerfile;
404
- }
405
- );
387
+ /**
388
+ * Application-wide settings of a Durable Object class's container application.
389
+ * Every Container bound to the class may declare them; declarations must agree.
390
+ */
391
+ export interface DurableObjectApplicationSettings {
392
+ /**
393
+ * SSH access to every instance of the application. Removing a previously
394
+ * declared value disables SSH.
395
+ */
396
+ ssh?: ContainerSshConfiguration;
397
+ /** SSH public keys allowed to access instances. Removing this property clears previously managed keys. */
398
+ authorizedKeys?: Containers.UserSSHPublicKeyList;
399
+ /** Application-wide logging. Removing a previously declared value disables logs. Changes do not restart running containers. */
400
+ observability?: Containers.ContainerApplicationObservability;
401
+ }
406
402
 
407
403
  /**
408
- * A container whose image and compute size are chosen by its Durable Object
409
- * at runtime. The policy is immutable: migrating an existing application
410
- * requires a new application and Durable Object namespace.
404
+ * One image a Durable Object starts with `ctx.container.start()`.
405
+ *
406
+ * Cloudflare allows one container application per Durable Object class. Every
407
+ * `durable_object` Container bound to the class adds its image to that
408
+ * application, under the Container's id. Bind several Containers to start
409
+ * different images from the same class, one at a time. One Container may be
410
+ * bound to several classes.
411
+ *
412
+ * An Effect-native Durable Object binds a Container with `yield*`. An async
413
+ * Worker attaches it to a class with
414
+ * `Cloudflare.DurableObject(className, { containers })`.
415
+ *
416
+ * The policy is immutable: migrating an existing fleet application requires a
417
+ * new application and Durable Object namespace.
411
418
  */
412
- export interface DurableObjectContainerProps extends PlatformProps {
413
- /** Application name. Alchemy generates a name when omitted. */
414
- name?: string;
415
- /** Exported Durable Object class, when bound on an async Worker's env. */
416
- className?: string;
419
+ export interface DurableObjectContainerProps
420
+ extends DurableObjectApplicationSettings, PlatformProps {
417
421
  /** Select images and instance sizes in `ctx.container.start()`. */
418
422
  schedulingPolicy: "durable_object";
419
423
  /**
420
- * Images exposed by name through `ctx.container.images`. Alchemy builds or
421
- * copies each image into Cloudflare's registry and prepares its immutable
422
- * digest before uploading the Worker. Omit to use the managed
423
- * `cloudflare/debian-trixie` image or restore a filesystem snapshot.
424
- * Supports up to 100 images, with names between 1 and 128 characters.
424
+ * Registry image to copy into Cloudflare's managed registry, e.g.
425
+ * `"node:24-slim"`. Exclusive with {@link context} and {@link dockerfile}.
426
+ * Omit every image source to contribute no image: `start()` then uses the
427
+ * managed `cloudflare/debian-trixie` image unless given another.
425
428
  */
426
- images?: Record<string, ContainerImageProps>;
429
+ image?: string;
430
+ /**
431
+ * Docker build context. Setting `context` or `dockerfile` builds the image;
432
+ * `context` defaults to the current directory.
433
+ */
434
+ context?: string;
435
+ /** Dockerfile path or inline content. Defaults to the context's Dockerfile. */
436
+ dockerfile?: string | InlineDockerfile;
437
+ /** Image publication and build-cache configuration. */
438
+ publish?: ContainerApplicationPropsBase["publish"];
427
439
  /**
428
- * Maximum wait for Cloudflare to prepare each published image, including API calls.
440
+ * Maximum wait for Cloudflare to prepare the published image, including API calls.
429
441
  * Accepts an Effect duration such as "45 minutes". Must be finite and positive.
430
442
  * Cancellation stops waiting; another deployment reuses the published registry image.
431
- * Use an immutable source digest or change the image reference to refresh a named image.
443
+ * Use an immutable source digest or change the image reference to refresh the image.
432
444
  * @default "30 minutes"
433
445
  */
434
446
  imagePreparationTimeout?: Duration.Input;
435
- /**
436
- * SSH access to every instance of the application. Removing a previously
437
- * declared value disables SSH.
438
- */
439
- ssh?: ContainerSshConfiguration;
440
- /** SSH public keys allowed to access instances. Removing this property clears previously managed keys. */
441
- authorizedKeys?: Containers.UserSSHPublicKeyList;
442
- /** Application-wide logging. Removing a previously declared value disables logs. Changes do not restart running containers. */
443
- observability?: Containers.ContainerApplicationObservability;
444
447
  }
445
448
 
446
449
  /**
447
450
  * Fleet-scheduled applications select a deployment image through `main`
448
451
  * (bundled Effect program), `context`/`dockerfile`, or a pre-built `image`.
449
- * Durable Object-managed applications select images at runtime and can
450
- * publish named image sources through `images`.
452
+ * A Durable Object-managed Container publishes one image that its Durable
453
+ * Object selects at runtime.
451
454
  */
452
455
  export type ContainerApplicationProps =
453
456
  | EffectfulContainerProps
@@ -468,7 +471,6 @@ export interface AnyContainerApplicationProps extends Omit<
468
471
  schedulingPolicy?: ContainerApplication.SchedulingPolicy;
469
472
  ssh?: ContainerSshConfiguration;
470
473
  authorizedKeys?: Containers.UserSSHPublicKeyList;
471
- images?: Record<string, ContainerImageProps>;
472
474
  imagePreparationTimeout?: Duration.Input;
473
475
  main?: string;
474
476
  image?: string;
@@ -801,11 +803,15 @@ export interface ContainerApplication<Shape = unknown> extends Resource<
801
803
  {
802
804
  /**
803
805
  * Cloudflare-assigned unique identifier of the container application.
806
+ * Empty for a Durable Object-managed Container: the application belongs
807
+ * to the Durable Object class that binds it.
804
808
  */
805
809
  applicationId: string;
806
810
  /**
807
811
  * The resolved application name (either the provided `name` or the
808
812
  * deterministic physical name derived from the stack, stage, and logical ID).
813
+ * For a Durable Object-managed Container, the name of its default image
814
+ * repository.
809
815
  */
810
816
  applicationName: string;
811
817
  /**
@@ -840,12 +846,14 @@ export interface ContainerApplication<Shape = unknown> extends Resource<
840
846
  */
841
847
  configuration: Partial<ContainerApplication.Configuration> &
842
848
  Containers.DurableObjectContainerConfiguration;
843
- /** Application-wide logging for Durable Object-managed containers. */
849
+ /** Application-wide logging declared by a Durable Object-managed Container. */
844
850
  observability?: Containers.ContainerApplicationObservability;
845
- /** Prepared, digest-pinned images keyed by their runtime names. */
846
- images?: Record<string, string>;
847
- /** Local image sources keyed by their runtime names. */
848
- devImages?: Record<string, DevContainerImage>;
851
+ /**
852
+ * The prepared, digest-pinned image of a Durable Object-managed Container,
853
+ * or its local image key in `alchemy dev`. Unset when the Container
854
+ * declares no image source.
855
+ */
856
+ image?: string;
849
857
  /**
850
858
  * The Durable Object namespace attached to the application, if it is bound
851
859
  * to one.
@@ -872,7 +880,6 @@ export interface ContainerApplication<Shape = unknown> extends Resource<
872
880
  image: string;
873
881
  digest?: string;
874
882
  configuration?: string;
875
- images?: Record<string, string>;
876
883
  };
877
884
  dev: DevContainerImage | undefined;
878
885
  },