1import * as Alchemy from "alchemy";
2import * as Cloudflare from "alchemy/Cloudflare";
3import { Config } from "effect";
4import * as Effect from "effect/Effect";
5import type { Counter as CounterClass } from "./src/worker.ts";
6
7export const DB = Cloudflare.D1.Database("DB");
8
9export const Bucket = Cloudflare.R2.Bucket("Bucket", { forceDestroy: true });
10
11// Queue producer + consumer wiring (both sides exercised by the same worker).
12// The Worker sends a message via `env.QUEUE.send(...)` from POST /queue/send,
13// then receives and persists it via its `queue(batch)` handler — end-to-end
14// regression guard for the Queue, QueueWrite, and Consumer resources.
15export const Queue = Cloudflare.Queues.Queue("Queue");
16
17export const Counter = Cloudflare.DurableObject<CounterClass>("Counter", {
18 className: "Counter",
19});
20
21export type WorkerEnv = Cloudflare.InferEnv<typeof Worker>;
22
23export const Worker = Cloudflare.Worker("Worker", {
24 main: "./src/worker.ts",
25 assets: {
26 directory: "./public",
27 },
28 env: {
29 // Self-contained default so the example deploys without external secrets;
30 // the integ test asserts this value round-trips through env.API_KEY.
31 API_KEY: Config.Redacted("SOME_API_KEY").pipe(
32 Config.withDefault("SOME_API_KEY"),
33 ),
34 DB,
35 Bucket,
36 Queue,
37 Counter,
38 },
39});
40
41export default Alchemy.Stack(
42 "CloudflareWorker",
43 {
44 providers: Cloudflare.providers(),
45 state: Cloudflare.state(),
46 },
47 Effect.gen(function* () {
48 const queue = yield* Queue;
49 const worker = yield* Worker;
50 // create a random resource to test redacted storage
51 yield* Alchemy.Random("Random");
52
53 // Register the same worker script as a consumer of Queue. The worker's
54 // `queue(batch)` handler (see src/worker.ts) receives each message batch.
55 yield* Cloudflare.Queues.Consumer("Consumer", {
56 queueId: queue.queueId,
57 scriptName: worker.workerName,
58 settings: {
59 batchSize: 10,
60 maxRetries: 3,
61 maxWaitTimeMs: 5000,
62 },
63 });
64
65 return { url: worker.url.as<string>() };
66 }),
67);