1---
2title: Runtime
3description: A Runtime is a Resource that carries the code it runs — a Worker, Lambda Function, Container, or Server declared with the Effectful Constructor pattern. Bind what you need, return what you expose.
4---
5
6In Alchemy, a **Runtime** is a [Resource](/infrastructure-as-code/resource)
7that carries the code it runs: a Cloudflare Worker, Lambda Function,
8ECS Task, Container, or Server. The props are the cloud configuration.
9The Effect is the code. Here is the running example from
10[What is Alchemy?](/what-is-alchemy), which this page takes apart:
11
12```typescript
13import * as Cloudflare from "alchemy/Cloudflare";
14import * as Effect from "effect/Effect";
15import * as HttpServerResponse from "effect/http/HttpServerResponse";
16
17export const Uploads = Cloudflare.R2.Bucket("Uploads");
18
19export default Cloudflare.Worker(
20 "Api",
21 { main: import.meta.url },
22 Effect.gen(function* () {
23 const bucket = yield* Cloudflare.R2.ReadWriteBucket(Uploads);
24
25 return {
26 fetch: Effect.gen(function* () {
27 const obj = yield* bucket.get("hello.txt");
28 return obj
29 ? HttpServerResponse.text(yield* obj.text())
30 : HttpServerResponse.text("Not found", { status: 404 });
31 }),
32 };
33 }).pipe(Effect.provide(Cloudflare.R2.ReadWriteBucketBinding)),
34);
35```
36
37One deploy ships both. `main: import.meta.url` tells Alchemy where the
38code lives. At deploy time Alchemy bundles this file's default export,
39which by convention is the Worker itself, and uploads that bundle as
40the Worker's script. Most Runtimes need no other props, because
41everything else they depend on comes in through bindings.
42
43The rest of this page follows that Effect. Alchemy also supports the
44traditional two-file shape, a config file pointing at a plain
45`async fetch` handler, compared in
46[Effect handlers vs async handlers](#effect-handlers-vs-async-handlers).
47
48## The Effectful Constructor
49
50Every Runtime is declared with the same Effect. It binds
51the resources it needs, then returns the interface it exposes:
52
53```typescript
54Effect.gen(function* () {
55 // bind what you need
56 const bucket = yield* Cloudflare.R2.ReadWriteBucket(Uploads);
57
58 // return what you expose
59 return {
60 fetch: Effect.gen(function* () {
61 /* use bucket */
62 }),
63 };
64});
65```
66
67This is the **Effectful Constructor**. The outer Effect is the
68**Construction phase**, which runs at deploy time and again at cold
69start. What it returns is the **Runtime phase**, which runs per
70request inside the deployed Runtime. A Worker or Lambda Function
71returns its handlers. A Durable Object returns its RPC methods. A
72Workflow returns its run function. A
73[Layer](/infrastructure-as-effects/layers) returns a service. Learn it
74once and every Runtime in Alchemy is a variation on it.
75
76## Fetch
77
78`fetch` serves HTTP. It is a per-request Effect. Yield
79`HttpServerRequest` to read the request and return an
80`HttpServerResponse`:
81
82```typescript
83import * as Effect from "effect/Effect";
84import { HttpServerRequest } from "effect/http/HttpServerRequest";
85import * as HttpServerResponse from "effect/http/HttpServerResponse";
86
87Effect.gen(function* () {
88 return {
89 fetch: Effect.gen(function* () {
90 const request = yield* HttpServerRequest;
91 return HttpServerResponse.text(`hello from ${request.url}`);
92 }),
93 };
94});
95```
96
97It runs inside the deployed Runtime, once per request.
98[Phases](/infrastructure-as-effects/phases) covers exactly when.
99
100## RPC
101
102The interface isn't limited to `fetch`. Add methods that return an
103`Effect` or a `Stream`, and any resource that binds this Worker calls
104them through a typed client:
105
106```typescript
107export default class Greeter extends Cloudflare.Worker<Greeter>()(
108 "Greeter",
109 { main: import.meta.url },
110 Effect.gen(function* () {
111 return {
112 greet: (name: string) => Effect.succeed(`hello ${name}`),
113 fetch: Effect.gen(function* () {
114 return HttpServerResponse.text("ok");
115 }),
116 };
117 }),
118) {}
119```
120
121Another Worker binds `Greeter` and calls `greet` as if it were local:
122
123```typescript
124import Greeter from "./Greeter.ts";
125
126export default Cloudflare.Worker(
127 "Api",
128 { main: import.meta.url },
129 Effect.gen(function* () {
130 const greeter = yield* Cloudflare.Workers.bindWorker(Greeter);
131
132 return {
133 fetch: Effect.gen(function* () {
134 return HttpServerResponse.text(yield* greeter.greet("world"));
135 }),
136 };
137 }),
138);
139```
140
141No schema, no runtime validation. Serialization is automatic and
142`greeter.greet` is typed from the class. This is
143[Schemaless RPC](/apis/schemaless), the default for internal calls. The
144[APIs](/apis) section adds the schema'd surfaces for trust boundaries.
145
146## Bindings
147
148The `yield*` lines at the top of the constructor are Bindings. Each one
149connects a resource to this Runtime and hands back a client:
150
151```typescript
152const bucket = yield* Cloudflare.R2.ReadWriteBucket(Uploads);
153const kv = yield* Cloudflare.KV.ReadWriteNamespace(Sessions);
154
155// inside fetch
156yield* bucket.put("key", "value");
157yield* kv.get("session-id");
158```
159
160A Cloudflare R2 binding and an AWS S3 binding look the same from here.
161What differs is what they generate at deploy time: a native Worker
162binding on one, an IAM statement and an environment variable on the
163other. See [Bindings](/infrastructure-as-effects/binding) for how they
164work.
165
166## Layers
167
168A Binding is infrastructure the Runtime uses directly. A Layer
169packages infrastructure behind a service of your own — it owns its
170resources and bindings internally, and providing it on the
171constructor is what brings them into the deploy:
172
173```typescript
174export default Cloudflare.Worker(
175 "Api",
176 { main: import.meta.url },
177 Effect.gen(function* () {
178 const jobs = yield* JobService; // a service of your own
179
180 return {
181 fetch: Effect.gen(function* () {
182 return HttpServerResponse.json(yield* jobs.getJob("job-1"));
183 }),
184 };
185 }).pipe(Effect.provide(JobServiceKV)), // the Layer brings a KV Namespace with it
186);
187```
188
189On the next deploy the Layer's namespace is created and bound to this
190Worker, and the handler only ever sees `JobService`. Swap
191`JobServiceKV` for a DynamoDB-backed Layer and the handler doesn't
192change. This is the fundamental building block of Infrastructure as
193Effects, and [Layers](/infrastructure-as-effects/layers) builds one
194from scratch.
195
196## Three ways to declare a Runtime
197
198#### Inline
199
200The inline form above is enough when nothing else needs to reference
201the Worker by name:
202
203```typescript
204export default Cloudflare.Worker("Api", { main: import.meta.url }, effect);
205```
206
207#### Class
208
209Wrap it in a class and the type becomes nominal. Hovers, errors, and
210Stack outputs say `Api` instead of an anonymous shape:
211
212```typescript
213export default class Api extends Cloudflare.Worker<Api>()(
214 "Api",
215 { main: import.meta.url },
216 effect,
217) {}
218```
219
220#### Tag and Layer
221
222The third form splits identity from implementation. The class declares
223only a **Tag**. `.make(props, effect)` produces the implementation
224**Layer**:
225
226```typescript
227// src/Api.ts
228export class Api extends Cloudflare.Worker<Api, {}>()("Api") {}
229
230export default Api.make(
231 { main: import.meta.url },
232 Effect.gen(function* () {
233 /* the Effectful Constructor */
234 }),
235);
236```
237
238The Layer is provided to the Stack, not to another Worker:
239
240```typescript
241// alchemy.run.ts
242import ApiLive, { Api } from "./src/Api.ts";
243
244export default Alchemy.Stack(
245 "MyApp",
246 { providers: Cloudflare.providers(), state: Cloudflare.state() },
247 Effect.gen(function* () {
248 const api = yield* Api;
249 return { url: api.url };
250 }).pipe(Effect.provide(ApiLive)),
251);
252```
253
254Other Runtimes bind to the Tag, never the implementation. Importing a
255Tag pulls in no runtime code, so a Worker that binds `Api` doesn't
256bundle `Api`'s implementation, which may not even target the same
257runtime. One Tag can also have several implementations, each consumer
258providing the one that fits. This split is what makes
259[Circular Bindings](/infrastructure-as-effects/circular-bindings)
260possible.
261
262## Effect handlers vs async handlers
263
264The Effect style gives handlers typed errors, composable retries, and
265bindings resolved through Effect's context:
266
267```typescript
268export default Cloudflare.Worker(
269 "Worker",
270 { main: import.meta.url },
271 Effect.gen(function* () {
272 const bucket = yield* Cloudflare.R2.ReadWriteBucket(Uploads);
273 return {
274 fetch: Effect.gen(function* () {
275 /* ... */
276 }),
277 };
278 }),
279);
280```
281
282Because bindings are context, a handler can also depend on a service
283of your own instead of a resource, with the storage swapped by
284providing a different [Layer](#layers).
285
286The async style is a standard `async fetch`. Bindings go on the
287resource's `env` prop and are typed with `InferEnv`:
288
289```typescript
290// alchemy.run.ts
291export type WorkerEnv = Cloudflare.InferEnv<typeof Worker>;
292
293export const Worker = Cloudflare.Worker("Worker", {
294 main: "./src/worker.ts",
295 env: { Uploads },
296});
297```
298
299```typescript
300// src/worker.ts
301import type { WorkerEnv } from "../alchemy.run.ts";
302
303export default {
304 async fetch(request: Request, env: WorkerEnv) {
305 const obj = await env.Uploads.get("hello.txt");
306 return obj
307 ? new Response(await obj.text())
308 : new Response("Not found", { status: 404 });
309 },
310};
311```
312
313There is no service boundary here. The handler reaches for `env.Uploads`
314directly, so a different storage backend means a different handler.
315
316Both styles deploy through the same provider and produce the same
317artifact. The Effect style unlocks [Layers](#layers), structured
318retries, and fine-grained testing. The async style fits existing
319handler code.
320
321## Instance scope vs request scope
322
323The constructor runs once per instance: a Worker isolate, a Lambda
324sandbox, a server process. Each event runs the handler in a fresh
325`Scope`:
326
327```typescript
328Effect.gen(function* () {
329 // instance scope: runs once, reused by every event
330 const bucket = yield* Cloudflare.R2.ReadWriteBucket(Uploads);
331
332 return {
333 fetch: Effect.gen(function* () {
334 // request scope: runs per event, closed when the event settles
335 }),
336 };
337});
338```
339
340Build things at instance scope. Use things at request scope:
341
342```typescript
343Effect.gen(function* () {
344 const secret = yield* fetchSecret;
345 // ✓ one-shot I/O producing a plain value, cached for every event
346
347 const conn = yield* acquireConnection;
348 // ✗ disposable. The instance scope may never close, so the
349 // connection may never be released
350
351 return {
352 fetch: Effect.gen(function* () {
353 const conn = yield* acquireConnection;
354 // ✓ acquired per event, released when the event settles
355 }),
356 };
357});
358```
359
360Anything shared across calls within one event is memoized on the
361request scope. `Drizzle.Postgres` opens its pool on the first query of
362an event, every query in that event reuses it, and the pool closes when
363the event settles. The
364[SQL connection lifecycle](/sql/effect-sql/lifecycle) is the canonical
365statement of this contract.
366
367A finalizer added inside a handler runs when the event settles:
368
369```typescript
370fetch: Effect.gen(function* () {
371 yield* Effect.addFinalizer(() => flushMetrics.pipe(Effect.ignore));
372 return HttpServerResponse.text("ok");
373}),
374```
375
376On workerd it runs after the response, via `ctx.waitUntil`. On Lambda
377it runs before the response leaves, so keep it cheap and put anything
378that must not be lost in the handler itself.
379
380Instance finalizers are best effort. On workerd they never run, because
381isolates are frozen or evicted without a teardown hook. On Lambda they
382get a 500 ms `SIGTERM` window at spin-down, skipped on hard failures.
383On servers they run on graceful process exit. Design so nothing needs
384instance-level cleanup.
385
386## Where next
387
388- [Bindings](/infrastructure-as-effects/binding) — what
389 `yield* ReadWriteBucket(Uploads)` generates: IAM, env injection, a
390 typed client.
391- [APIs](/apis) — how bound resources call the methods you return.
392- [Phases](/infrastructure-as-effects/phases) — when the constructor
393 runs vs the interface it returns, and why.
394- [Custom Runtime](/infrastructure-as-effects/custom-runtime) — bring
395 the Runtime model to a new compute target.