alchemy 2.0.0-beta.2 → 2.0.0-beta.test-export-fix-3

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 (65) hide show
  1. package/bin/{alchemy-effect.js → alchemy.js} +173 -173
  2. package/bin/alchemy.js.map +1 -0
  3. package/bin/{alchemy-effect.sh → alchemy.sh} +2 -2
  4. package/lib/cli/index.js +169 -169
  5. package/lib/cli/index.js.map +1 -1
  6. package/package.json +75 -67
  7. package/src/AWS/AGENTS.md +10 -10
  8. package/src/AWS/Assets.ts +1 -1
  9. package/src/AWS/Credentials.ts +0 -1
  10. package/src/AWS/DynamoDB/Table.ts +63 -10
  11. package/src/AWS/EC2/hosted.ts +1 -1
  12. package/src/AWS/ECS/Task.ts +1 -1
  13. package/src/AWS/Kinesis/Stream.ts +44 -8
  14. package/src/AWS/Lambda/Function.ts +218 -10
  15. package/src/AWS/S3/Bucket.ts +26 -19
  16. package/src/AWS/S3/BucketNotifications.ts +1 -1
  17. package/src/AWS/SNS/Topic.ts +47 -10
  18. package/src/AWS/SQS/Queue.ts +66 -0
  19. package/src/Cloudflare/Container/Container.ts +122 -0
  20. package/src/Cloudflare/Container/ContainerApplication.ts +6 -3
  21. package/src/Cloudflare/Container/ContainerBinding.ts +2 -4
  22. package/src/Cloudflare/Container/StartContainer.ts +1 -1
  23. package/src/Cloudflare/D1/D1Database.ts +23 -4
  24. package/src/Cloudflare/KV/KVNamespace.ts +25 -0
  25. package/src/Cloudflare/Providers.ts +1 -6
  26. package/src/Cloudflare/R2/R2Bucket.ts +45 -2
  27. package/src/Cloudflare/Website/StaticSite.ts +79 -6
  28. package/src/Cloudflare/Website/Vite.ts +67 -13
  29. package/src/Cloudflare/Workers/Assets.ts +170 -207
  30. package/src/Cloudflare/Workers/DurableObjectNamespace.ts +629 -0
  31. package/src/Cloudflare/Workers/DurableObjectState.ts +63 -0
  32. package/src/Cloudflare/Workers/DurableObjectStorage.ts +256 -0
  33. package/src/Cloudflare/Workers/DynamicWorkerLoader.ts +66 -7
  34. package/src/Cloudflare/Workers/InferEnv.ts +11 -7
  35. package/src/Cloudflare/Workers/Rpc.ts +13 -2
  36. package/src/Cloudflare/Workers/ScheduledEvents.ts +185 -0
  37. package/src/Cloudflare/Workers/WebSocket.ts +1 -1
  38. package/src/Cloudflare/Workers/Worker.ts +406 -49
  39. package/src/Cloudflare/Workers/Workflow.ts +53 -11
  40. package/src/Cloudflare/Workers/index.ts +4 -1
  41. package/src/Construct.ts +2 -2
  42. package/src/GitHub/Comment.ts +224 -0
  43. package/src/GitHub/Secret.ts +257 -0
  44. package/src/GitHub/Variable.ts +166 -0
  45. package/src/GitHub/index.ts +3 -0
  46. package/src/Kubernetes/client.ts +2 -2
  47. package/src/Output.ts +1 -1
  48. package/src/Platform.ts +1 -1
  49. package/src/Provider.ts +1 -1
  50. package/src/Test/Vitest.ts +4 -3
  51. package/src/Util/PlatformServices.ts +21 -0
  52. package/src/Util/dedent.ts +59 -0
  53. package/src/Util/index.ts +1 -0
  54. package/bin/alchemy-effect.js.map +0 -1
  55. package/src/Cloudflare/Workers/DurableObject.ts +0 -527
  56. package/src/Daemon/Client.ts +0 -116
  57. package/src/Daemon/Config.ts +0 -28
  58. package/src/Daemon/Errors.ts +0 -48
  59. package/src/Daemon/Lock.ts +0 -162
  60. package/src/Daemon/ProcessRegistry.ts +0 -284
  61. package/src/Daemon/RpcSchema.ts +0 -43
  62. package/src/Daemon/RpcServer.ts +0 -231
  63. package/src/Daemon/index.ts +0 -27
  64. package/src/Spawn.ts +0 -23
  65. /package/bin/{alchemy-effect.ts → alchemy.ts} +0 -0
@@ -26,7 +26,7 @@ import { findCwdForBundle } from "../../Bundle/TempRoot.ts";
26
26
  import type { ScopedPlanStatusSession } from "../../Cli/Cli.ts";
27
27
  import { isResolved } from "../../Diff.ts";
28
28
  import type { HttpEffect } from "../../Http.ts";
29
- import type { Input, InputProps } from "../../Input.ts";
29
+ import type { InputProps } from "../../Input.ts";
30
30
  import * as Output from "../../Output.ts";
31
31
  import { createPhysicalName } from "../../PhysicalName.ts";
32
32
  import {
@@ -47,10 +47,20 @@ import { fromCloudflareFetcher } from "../Fetcher.ts";
47
47
  import { CloudflareLogs } from "../Logs.ts";
48
48
  import type { Providers } from "../Providers.ts";
49
49
  import type { R2Bucket } from "../R2/R2Bucket.ts";
50
- import type { AssetsConfig, AssetsProps } from "./Assets.ts";
51
- import * as Assets from "./Assets.ts";
50
+ import {
51
+ isAssets,
52
+ readAssets,
53
+ uploadAssets,
54
+ type Assets,
55
+ type AssetsConfig,
56
+ type AssetsProps,
57
+ } from "./Assets.ts";
52
58
  import cloudflare_workers from "./cloudflare_workers.ts";
53
- import { isDurableObjectExport } from "./DurableObject.ts";
59
+ import {
60
+ isDurableObjectExport,
61
+ isDurableObjectNamespaceLike,
62
+ type DurableObjectNamespaceLike,
63
+ } from "./DurableObjectNamespace.ts";
54
64
  import { workersHttpHandler } from "./HttpServer.ts";
55
65
  import { Request } from "./Request.ts";
56
66
  import { makeRpcStub } from "./Rpc.ts";
@@ -105,12 +115,12 @@ export interface AssetsWithHash {
105
115
  /**
106
116
  * Path to the assets directory.
107
117
  */
108
- path: Input<string>;
118
+ path: string;
109
119
  /**
110
120
  * Pre-computed hash of the assets. When provided, this hash is used for diffing
111
121
  * to determine if the worker needs to be redeployed.
112
122
  */
113
- hash: Input<string>;
123
+ hash: string;
114
124
  /**
115
125
  * Optional assets configuration.
116
126
  */
@@ -161,7 +171,11 @@ export type WorkerServices = Worker | WorkerEnvironment | Request;
161
171
 
162
172
  export type WorkerShape = Main<WorkerServices>;
163
173
 
164
- export type WorkerBindingResource = R2Bucket | D1Database;
174
+ export type WorkerBindingResource =
175
+ | Assets
176
+ | R2Bucket
177
+ | D1Database
178
+ | DurableObjectNamespaceLike<any>;
165
179
 
166
180
  export type WorkerBindings = {
167
181
  [bindingName in string]: WorkerBindingResource;
@@ -173,8 +187,26 @@ export type WorkerBindingProps = {
173
187
  | Effect.Effect<WorkerBindingResource, any, any>;
174
188
  };
175
189
 
190
+ type NormalizedBindings<
191
+ Bindings extends WorkerBindingProps = {},
192
+ AssetsConfig extends WorkerAssetsConfig | undefined = undefined,
193
+ > = {
194
+ [B in keyof Bindings]: Bindings[B] extends Effect.Effect<
195
+ infer T extends WorkerBindingResource,
196
+ any,
197
+ any
198
+ >
199
+ ? T
200
+ : Extract<Bindings[B], WorkerBindingResource>;
201
+ } & (undefined extends AssetsConfig ? {} : { ASSETS: Assets });
202
+
203
+ export type WorkerAssetsConfig = string | AssetsProps | AssetsWithHash;
204
+
176
205
  export interface WorkerProps<
177
206
  Bindings extends WorkerBindingProps = any,
207
+ Assets extends WorkerAssetsConfig | undefined =
208
+ | WorkerAssetsConfig
209
+ | undefined,
178
210
  > extends PlatformProps {
179
211
  /**
180
212
  * Worker name override. If omitted, Alchemy derives a deterministic physical
@@ -192,11 +224,7 @@ export interface WorkerProps<
192
224
  * - An AssetsProps object with directory and config
193
225
  * - An object with path and hash (e.g., from a Build resource)
194
226
  */
195
- assets?:
196
- | string
197
- | AssetsProps
198
- | AssetsWithHash
199
- | (AssetsWithHash & { [K: string]: any });
227
+ assets?: Assets;
200
228
  subdomain?: {
201
229
  enabled?: boolean;
202
230
  previewsEnabled?: boolean;
@@ -256,17 +284,326 @@ export type Worker<Bindings extends WorkerBindings = any> = Resource<
256
284
  * A Cloudflare Worker host with deploy-time binding support and runtime export
257
285
  * collection.
258
286
  *
259
- * `Worker` behaves like a resource during deploy, but it also carries a runtime
260
- * execution context so KV, R2, Durable Objects, assets, and service bindings
261
- * can be inferred from the worker program itself.
287
+ * A Worker follows a two-phase pattern. The outer `Effect.gen` runs at
288
+ * deploy time to bind resources (KV, R2, Durable Objects, etc.). It returns
289
+ * an object whose properties are the Worker's runtime handlers — `fetch` for
290
+ * HTTP requests and any additional RPC methods.
262
291
  *
263
- * @section Creating Workers
264
- * @example Basic Worker
265
292
  * ```typescript
266
- * const worker = yield* Worker("ApiWorker", {
293
+ * Effect.gen(function* () {
294
+ * // Phase 1: bind resources (runs at deploy time)
295
+ * const kv = yield* Cloudflare.KVNamespace.bind(MyKV);
296
+ *
297
+ * return {
298
+ * // Phase 2: runtime handlers (runs on each request)
299
+ * fetch: Effect.gen(function* () {
300
+ * const value = yield* kv.get("key");
301
+ * return HttpServerResponse.text(value ?? "not found");
302
+ * }),
303
+ * };
304
+ * })
305
+ * ```
306
+ *
307
+ * There are three ways to define a Worker, from simplest to most
308
+ * flexible. See the {@link https://alchemy.run/concepts/platform | Platform concept}
309
+ * page for the full explanation.
310
+ *
311
+ * - **Async** — plain `async fetch` handler, no Effect runtime in the bundle.
312
+ * - **Effect** — Effect implementation passed directly, single file.
313
+ * - **Layer** — class and `.make()` in a single file; Rolldown tree-shakes `.make()` from consumers.
314
+ *
315
+ * @section Async Workers
316
+ * You don't have to use Effect for your runtime code. If you create
317
+ * a Worker resource with `main` pointing at a file but provide no
318
+ * `Effect.gen` implementation, Alchemy bundles and deploys that file
319
+ * as-is. Your handler is a plain `async fetch` — no Effect runtime
320
+ * is included in the bundle.
321
+ *
322
+ * Use the `bindings` prop to declare which resources are available
323
+ * at runtime, and `Cloudflare.InferEnv` to extract a fully typed
324
+ * `env` object from those bindings.
325
+ *
326
+ * See the {@link https://alchemy.run/guides/async-worker | Async Workers Guide}
327
+ * for a comprehensive walkthrough of all binding types (R2, D1,
328
+ * Durable Objects, Assets, and more).
329
+ *
330
+ * @example Defining an async Worker in your stack
331
+ * ```typescript
332
+ * // alchemy.run.ts
333
+ * const db = yield* Cloudflare.D1Database("DB");
334
+ * const bucket = yield* Cloudflare.R2Bucket("Bucket");
335
+ *
336
+ * export type WorkerEnv = Cloudflare.InferEnv<typeof Worker>;
337
+ *
338
+ * export const Worker = Cloudflare.Worker("Worker", {
267
339
  * main: "./src/worker.ts",
340
+ * bindings: { db, bucket },
268
341
  * });
269
342
  * ```
343
+ *
344
+ * @example Writing the async handler
345
+ * ```typescript
346
+ * // src/worker.ts
347
+ * import type { WorkerEnv } from "../alchemy.run.ts";
348
+ *
349
+ * export default {
350
+ * async fetch(request: Request, env: WorkerEnv) {
351
+ * if (request.method === "GET") {
352
+ * const object = await env.bucket.get("key");
353
+ * return new Response(object?.body ?? null);
354
+ * }
355
+ * return new Response("Not Found", { status: 404 });
356
+ * },
357
+ * };
358
+ * ```
359
+ *
360
+ * @section Effect Workers
361
+ * Pass the Effect implementation as the third argument. This is the
362
+ * simplest Effect-based approach — everything lives in one file.
363
+ * Convenient for standalone Workers that don't need to be referenced
364
+ * by other Workers.
365
+ *
366
+ * @example Worker Effect
367
+ * ```typescript
368
+ * export default class MyWorker extends Cloudflare.Worker<MyWorker>()(
369
+ * "MyWorker",
370
+ * { main: import.meta.path },
371
+ * Effect.gen(function* () {
372
+ * // init: bind resources
373
+ * const kv = yield* Cloudflare.KVNamespace.bind(MyKV);
374
+ *
375
+ * return {
376
+ * // runtime: use them
377
+ * fetch: Effect.gen(function* () {
378
+ * const value = yield* kv.get("key");
379
+ * return HttpServerResponse.text(value ?? "not found");
380
+ * }),
381
+ * };
382
+ * }),
383
+ * ) {}
384
+ * ```
385
+ *
386
+ * @section Worker Layer
387
+ * When two Workers need to reference each other (e.g. WorkerA calls
388
+ * WorkerB and vice versa), or you simply want optimal tree-shaking,
389
+ * define the Worker class separately from its `.make()` call. The
390
+ * class is a lightweight identifier; `.make()` provides the runtime
391
+ * implementation as an `export default`. Rolldown treats `.make()`
392
+ * as pure, so any Worker that imports the class to bind it will not
393
+ * pull in the `.make()` dependencies — the bundler tree-shakes
394
+ * them away entirely.
395
+ *
396
+ * The class and `.make()` can live in the same file. This is the
397
+ * same pattern used by `Container` and `DurableObjectNamespace`,
398
+ * and is recommended for any cross-Worker or cross-DO bindings.
399
+ *
400
+ * @example Worker Layer (class + .make() in one file)
401
+ * ```typescript
402
+ * // src/WorkerB.ts
403
+ * export default class WorkerB extends Cloudflare.Worker<WorkerB>()(
404
+ * "WorkerB",
405
+ * { main: import.meta.path },
406
+ * ) {}
407
+ *
408
+ * export default WorkerB.make(
409
+ * Effect.gen(function* () {
410
+ * // init: bind resources
411
+ * const kv = yield* Cloudflare.KVNamespace.bind(MyKV);
412
+ *
413
+ * return {
414
+ * // runtime: use them
415
+ * greet: (name: string) =>
416
+ * Effect.gen(function* () {
417
+ * yield* kv.put("last-greeted", name);
418
+ * return `Hello ${name}`;
419
+ * }),
420
+ * };
421
+ * }),
422
+ * );
423
+ * ```
424
+ *
425
+ * @example Binding a Worker Layer from another Worker
426
+ * ```typescript
427
+ * // src/WorkerA.ts — imports WorkerB; bundler tree-shakes .make()
428
+ * import WorkerB from "./WorkerB.ts";
429
+ *
430
+ * export default class WorkerA extends Cloudflare.Worker<WorkerA>()(
431
+ * "WorkerA",
432
+ * { main: import.meta.path },
433
+ * Effect.gen(function* () {
434
+ * const b = yield* Cloudflare.Worker.bind(WorkerB);
435
+ * return {
436
+ * fetch: Effect.gen(function* () {
437
+ * return yield* b.greet("world");
438
+ * }),
439
+ * };
440
+ * }),
441
+ * ) {}
442
+ * ```
443
+ *
444
+ * @section Configuration
445
+ * The props object controls compatibility flags, static assets, and
446
+ * build options. These are evaluated at deploy time.
447
+ *
448
+ * @example Enabling Node.js compatibility
449
+ * ```typescript
450
+ * {
451
+ * main: import.meta.path,
452
+ * compatibility: {
453
+ * flags: ["nodejs_compat"],
454
+ * date: "2026-03-17",
455
+ * },
456
+ * }
457
+ * ```
458
+ *
459
+ * @example Serving static assets
460
+ * ```typescript
461
+ * {
462
+ * main: import.meta.path,
463
+ * assets: "./public",
464
+ * }
465
+ * ```
466
+ *
467
+ * @section R2 Bucket
468
+ * Bind an R2 bucket in the init phase with `Cloudflare.R2Bucket.bind`.
469
+ * The returned handle exposes `get`, `put`, `delete`, and `list`
470
+ * methods you can call in your runtime handlers.
471
+ *
472
+ * @example Binding and using R2
473
+ * ```typescript
474
+ * // init
475
+ * const bucket = yield* Cloudflare.R2Bucket.bind(MyBucket);
476
+ *
477
+ * return {
478
+ * fetch: Effect.gen(function* () {
479
+ * const request = yield* HttpServerRequest;
480
+ * const key = request.url.split("/").pop()!;
481
+ *
482
+ * if (request.method === "GET") {
483
+ * const object = yield* bucket.get(key);
484
+ * return object
485
+ * ? HttpServerResponse.text(yield* object.text())
486
+ * : HttpServerResponse.empty({ status: 404 });
487
+ * }
488
+ *
489
+ * yield* bucket.put(key, request.stream);
490
+ * return HttpServerResponse.empty({ status: 201 });
491
+ * }),
492
+ * };
493
+ * ```
494
+ *
495
+ * @section KV Namespace
496
+ * Bind a KV namespace with `Cloudflare.KVNamespace.bind`. KV provides
497
+ * eventually-consistent, low-latency key-value reads replicated
498
+ * globally across Cloudflare's edge.
499
+ *
500
+ * @example Binding and using KV
501
+ * ```typescript
502
+ * // init
503
+ * const kv = yield* Cloudflare.KVNamespace.bind(MyKV);
504
+ *
505
+ * return {
506
+ * fetch: Effect.gen(function* () {
507
+ * const value = yield* kv.get("my-key");
508
+ * return HttpServerResponse.text(value ?? "not found");
509
+ * }),
510
+ * };
511
+ * ```
512
+ *
513
+ * @section D1 Database
514
+ * Bind a D1 database with `Cloudflare.D1Connection.bind`. D1 is a
515
+ * serverless SQLite database — use `prepare` to build parameterized
516
+ * queries and `all`, `first`, or `run` to execute them.
517
+ *
518
+ * @example Binding and querying D1
519
+ * ```typescript
520
+ * // init
521
+ * const db = yield* Cloudflare.D1Connection.bind(MyDB);
522
+ *
523
+ * return {
524
+ * fetch: Effect.gen(function* () {
525
+ * const results = yield* db
526
+ * .prepare("SELECT * FROM users WHERE id = ?")
527
+ * .bind(userId)
528
+ * .all();
529
+ * return yield* HttpServerResponse.json(results);
530
+ * }),
531
+ * };
532
+ * ```
533
+ *
534
+ * @section Durable Objects
535
+ * Yield a `DurableObjectNamespace` class in the init phase to get a
536
+ * namespace handle. Call `getByName` or `getById` to get a typed RPC
537
+ * stub, then call its methods from your runtime handlers.
538
+ *
539
+ * @example Using a Durable Object
540
+ * ```typescript
541
+ * // init
542
+ * const counters = yield* Counter;
543
+ *
544
+ * return {
545
+ * fetch: Effect.gen(function* () {
546
+ * const counter = counters.getByName("user-123");
547
+ * const value = yield* counter.increment();
548
+ * return HttpServerResponse.text(String(value));
549
+ * }),
550
+ * };
551
+ * ```
552
+ *
553
+ * @section Containers
554
+ * Containers run long-lived processes alongside Durable Objects. Bind
555
+ * one with `Cloudflare.Container.bind` and start it with
556
+ * `Cloudflare.start`. You can call typed methods on the running
557
+ * container or make HTTP requests to its exposed ports.
558
+ *
559
+ * @example Binding and starting a Container
560
+ * ```typescript
561
+ * // init (inside a DurableObjectNamespace)
562
+ * const sandbox = yield* Cloudflare.Container.bind(Sandbox);
563
+ *
564
+ * return Effect.gen(function* () {
565
+ * const container = yield* Cloudflare.start(sandbox);
566
+ *
567
+ * return {
568
+ * exec: (cmd: string) => container.exec(cmd),
569
+ * fetch: Effect.gen(function* () {
570
+ * const { fetch } = yield* container.getTcpPort(3000);
571
+ * const res = yield* fetch(HttpClientRequest.get("http://container/"));
572
+ * return HttpServerResponse.fromClientResponse(res);
573
+ * }),
574
+ * };
575
+ * });
576
+ * ```
577
+ *
578
+ * @section Dynamic Workers
579
+ * `DynamicWorkerLoader` lets you spin up ephemeral Workers at runtime
580
+ * from inline JavaScript modules. This is useful for sandboxing
581
+ * user-provided code or running untrusted scripts in isolation.
582
+ *
583
+ * @example Loading a dynamic Worker
584
+ * ```typescript
585
+ * // init
586
+ * const loader = yield* Cloudflare.DynamicWorkerLoader("Loader");
587
+ *
588
+ * return {
589
+ * fetch: Effect.gen(function* () {
590
+ * const worker = loader.load({
591
+ * compatibilityDate: "2026-01-28",
592
+ * mainModule: "worker.js",
593
+ * modules: {
594
+ * "worker.js": `export default {
595
+ * async fetch(req) { return new Response("sandboxed"); }
596
+ * }`,
597
+ * },
598
+ * });
599
+ *
600
+ * const res = yield* worker.fetch(
601
+ * HttpClientRequest.get("https://worker/"),
602
+ * );
603
+ * return HttpServerResponse.fromClientResponse(res);
604
+ * }),
605
+ * };
606
+ * ```
270
607
  */
271
608
  export const Worker: Platform<
272
609
  Worker,
@@ -274,18 +611,18 @@ export const Worker: Platform<
274
611
  WorkerShape,
275
612
  WorkerExecutionContext
276
613
  > & {
277
- <const Bindings extends WorkerBindingProps>(
614
+ <
615
+ const Bindings extends WorkerBindingProps,
616
+ const Assets extends WorkerAssetsConfig | undefined = undefined,
617
+ >(
278
618
  id: string,
279
- props: InputProps<WorkerProps<Bindings>>,
619
+ props: InputProps<WorkerProps<Bindings, Assets>>,
280
620
  ): Effect.Effect<
281
621
  Worker<{
282
- [B in keyof Bindings]: Bindings[B] extends Effect.Effect<
283
- infer T extends WorkerBindingResource,
284
- any,
285
- any
286
- >
287
- ? T
288
- : Extract<Bindings[B], WorkerBindingResource>;
622
+ [binding in keyof NormalizedBindings<
623
+ Bindings,
624
+ Assets
625
+ >]: NormalizedBindings<Bindings, Assets>[binding];
289
626
  }>
290
627
  >;
291
628
  } = Platform(WorkerTypeId, {
@@ -303,31 +640,45 @@ export const Worker: Platform<
303
640
  ? yield* bindingEff
304
641
  : bindingEff;
305
642
 
306
- const bindingMeta: InputProps<WorkerBinding> | undefined =
307
- binding.Type === "Cloudflare.D1Database"
643
+ const bindingMeta: InputProps<WorkerBinding> | undefined = isAssets(
644
+ binding,
645
+ )
646
+ ? {
647
+ type: "assets",
648
+ name: bindingName,
649
+ }
650
+ : isDurableObjectNamespaceLike(binding)
308
651
  ? {
309
- type: "d1",
310
- id: binding.databaseId,
652
+ type: "durable_object_namespace",
311
653
  name: bindingName,
654
+ className: binding.className ?? binding.name,
312
655
  }
313
- : binding.Type === "Cloudflare.R2Bucket"
656
+ : binding.Type === "Cloudflare.D1Database"
314
657
  ? {
315
- type: "r2_bucket",
658
+ type: "d1",
659
+ id: binding.databaseId,
316
660
  name: bindingName,
317
- bucketName: binding.bucketName,
318
- jurisdiction: binding.jurisdiction.pipe(
319
- Output.map((jurisdiction) =>
320
- jurisdiction === "default" ? undefined : jurisdiction,
321
- ),
322
- ),
323
661
  }
324
- : // TODO(sam): handle others
325
- undefined;
662
+ : binding.Type === "Cloudflare.R2Bucket"
663
+ ? {
664
+ type: "r2_bucket",
665
+ name: bindingName,
666
+ bucketName: binding.bucketName,
667
+ jurisdiction: binding.jurisdiction.pipe(
668
+ Output.map((jurisdiction) =>
669
+ jurisdiction === "default" ? undefined : jurisdiction,
670
+ ),
671
+ ),
672
+ }
673
+ : // TODO(sam): handle others
674
+ undefined;
326
675
 
327
676
  if (bindingMeta) {
328
677
  yield* resource.bind`${bindingName}`({
329
678
  bindings: [bindingMeta],
330
679
  });
680
+ } else {
681
+ return yield* Effect.die(`Unknown binding type: ${bindingName}`);
331
682
  }
332
683
  }
333
684
  }
@@ -552,7 +903,6 @@ export const WorkerProvider = () =>
552
903
  const virtualEntryPlugin = yield* Bundle.virtualEntryPlugin;
553
904
  const stack = yield* Stack;
554
905
 
555
- const { read, upload } = yield* Assets.Assets;
556
906
  const createScriptSubdomain = yield* workers.createScriptSubdomain;
557
907
  const createScriptTail = yield* workers.createScriptTail;
558
908
  const deleteScript = yield* workers.deleteScript;
@@ -669,7 +1019,9 @@ export const WorkerProvider = () =>
669
1019
  const prepareAssets = Effect.fnUntraced(function* (
670
1020
  assets: WorkerProps["assets"],
671
1021
  ) {
672
- if (!assets) return undefined;
1022
+ if (!assets) {
1023
+ return undefined;
1024
+ }
673
1025
 
674
1026
  // Handle AssetsWithHash (from Build resource)
675
1027
  // Props are resolved by Plan, so Input<string> values are already strings at runtime
@@ -678,7 +1030,7 @@ export const WorkerProvider = () =>
678
1030
  "path" in assets &&
679
1031
  "hash" in assets
680
1032
  ) {
681
- const result = yield* read({
1033
+ const result = yield* readAssets({
682
1034
  directory: assets.path as string,
683
1035
  config: assets.config,
684
1036
  });
@@ -689,7 +1041,7 @@ export const WorkerProvider = () =>
689
1041
  }
690
1042
 
691
1043
  // Handle string path or AssetsProps
692
- return yield* read(
1044
+ return yield* readAssets(
693
1045
  typeof assets === "string" ? { directory: assets } : assets,
694
1046
  );
695
1047
  });
@@ -760,8 +1112,8 @@ import * as Stream from "effect/Stream";
760
1112
  import { env, DurableObject${hasWfClasses ? ", WorkflowEntrypoint" : ""} } from "cloudflare:workers";
761
1113
  import { MinimumLogLevel } from "effect/References";
762
1114
  import { NodeServices } from "@effect/platform-node";
763
- import { Stack } from "alchemy-effect/Stack";
764
- import { WorkerEnvironment, makeDurableObjectBridge${hasWfClasses ? ", makeWorkflowBridge" : ""}, ExportedHandlerMethods } from "alchemy-effect/Cloudflare";
1115
+ import { Stack } from "alchemy/Stack";
1116
+ import { WorkerEnvironment, makeDurableObjectBridge${hasWfClasses ? ", makeWorkflowBridge" : ""}, ExportedHandlerMethods } from "alchemy/Cloudflare";
765
1117
 
766
1118
  import entry from "${importPath}";
767
1119
 
@@ -927,7 +1279,7 @@ ${[
927
1279
  const [assets, bundle] = yield* Effect.all(
928
1280
  [
929
1281
  assetsDirectory
930
- ? read({
1282
+ ? readAssets({
931
1283
  directory: assetsDirectory,
932
1284
  config:
933
1285
  typeof props.assets === "object" && "config" in props.assets
@@ -1007,7 +1359,12 @@ ${[
1007
1359
  yield* Effect.logInfo(
1008
1360
  `Cloudflare Worker ${olds ? "update" : "create"}: uploading assets for ${name}`,
1009
1361
  );
1010
- const { jwt } = yield* upload(accountId, name, assets, session);
1362
+ const { jwt } = yield* uploadAssets(
1363
+ accountId,
1364
+ name,
1365
+ assets,
1366
+ session,
1367
+ );
1011
1368
  metadataAssets = {
1012
1369
  jwt,
1013
1370
  config: assets.config,
@@ -186,30 +186,72 @@ export class WorkflowScope extends Context.Service<
186
186
  >()("Cloudflare.Workflow") {}
187
187
 
188
188
  /**
189
- * Declare a Cloudflare Workflow inside a Worker program.
189
+ * A Cloudflare Workflow that orchestrates durable, multi-step tasks with
190
+ * automatic retries and at-least-once delivery.
190
191
  *
191
- * The outer Effect resolves infrastructure dependencies (Durable Objects,
192
- * etc.) and returns the workflow body -- an Effect that uses `WorkflowEvent`
193
- * and step primitives (`task`, `sleep`, `sleepUntil`).
192
+ * A Workflow follows the same two-phase pattern as Workers and Durable
193
+ * Objects. The outer `Effect.gen` resolves shared dependencies. The inner
194
+ * `Effect.gen` is the workflow body — it reads the triggering event and
195
+ * runs steps using `task`, `sleep`, and `sleepUntil`.
194
196
  *
195
- * Internally this creates a `WorkflowResource` that manages the Cloudflare
196
- * Workflows API lifecycle (PUT / DELETE), similar to how `bindContainer`
197
- * creates a `ContainerApplication`.
197
+ * ```typescript
198
+ * Effect.gen(function* () {
199
+ * // Phase 1: resolve dependencies
200
+ * const notifier = yield* NotificationService;
201
+ *
202
+ * return Effect.gen(function* () {
203
+ * // Phase 2: workflow body (durable steps)
204
+ * const event = yield* Cloudflare.WorkflowEvent;
205
+ * const result = yield* Cloudflare.task("process", doWork(event.payload));
206
+ * yield* Cloudflare.sleep("cooldown", "10 seconds");
207
+ * return result;
208
+ * });
209
+ * })
210
+ * ```
211
+ *
212
+ * @resource
198
213
  *
199
- * @example
214
+ * @section Defining a Workflow
215
+ * @example Minimal workflow
200
216
  * ```typescript
201
217
  * export default class MyWorkflow extends Cloudflare.Workflow<MyWorkflow>()(
202
218
  * "MyWorkflow",
203
219
  * Effect.gen(function* () {
204
220
  * return Effect.gen(function* () {
205
221
  * const event = yield* Cloudflare.WorkflowEvent;
206
- * const data = yield* Cloudflare.task("fetch-data", Effect.succeed({ ok: true }));
207
- * yield* Cloudflare.sleep("pause", "5 seconds");
208
- * return data;
222
+ * return { received: event.payload };
209
223
  * });
210
224
  * }),
211
225
  * ) {}
212
226
  * ```
227
+ *
228
+ * @section Step Primitives
229
+ * @example Running a named task
230
+ * ```typescript
231
+ * const result = yield* Cloudflare.task(
232
+ * "process-order",
233
+ * Effect.succeed({ orderId: "abc", total: 42 }),
234
+ * );
235
+ * ```
236
+ *
237
+ * @example Sleeping between steps
238
+ * ```typescript
239
+ * yield* Cloudflare.sleep("cooldown", "30 seconds");
240
+ * ```
241
+ *
242
+ * @section Starting and Monitoring Instances
243
+ * @example Creating an instance from a Worker
244
+ * ```typescript
245
+ * const workflow = yield* MyWorkflow;
246
+ * const instance = yield* workflow.create({ orderId: "abc" });
247
+ * ```
248
+ *
249
+ * @example Checking instance status
250
+ * ```typescript
251
+ * const workflow = yield* MyWorkflow;
252
+ * const handle = yield* workflow.get(instanceId);
253
+ * const status = yield* handle.status();
254
+ * ```
213
255
  */
214
256
  export const Workflow: WorkflowClass = taggedFunction(WorkflowScope, ((
215
257
  ...args: [] | [name: string, impl: Effect.Effect<WorkflowBody>]
@@ -1,12 +1,15 @@
1
1
  export * from "./Assets.ts";
2
2
  export * from "./ConfigProvider.ts";
3
- export * from "./DurableObject.ts";
3
+ export * from "./DurableObjectNamespace.ts";
4
+ export * from "./DurableObjectState.ts";
5
+ export * from "./DurableObjectStorage.ts";
4
6
  export * from "./DynamicWorkerLoader.ts";
5
7
  export * from "./Fetch.ts";
6
8
  export * from "./HttpServer.ts";
7
9
  export * from "./InferEnv.ts";
8
10
  export * from "./Request.ts";
9
11
  export * from "./Rpc.ts";
12
+ export * from "./ScheduledEvents.ts";
10
13
  export * from "./WebSocket.ts";
11
14
  export * from "./Worker.ts";
12
15
  export * from "./Workflow.ts";