alchemy 2.0.0-beta.2 → 2.0.0-beta.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.
- package/bin/alchemy-effect.js +2 -2
- package/bin/alchemy-effect.js.map +1 -1
- package/package.json +18 -9
- package/src/Cloudflare/Container/Container.ts +122 -0
- package/src/Cloudflare/Container/ContainerApplication.ts +3 -0
- package/src/Cloudflare/Container/ContainerBinding.ts +1 -1
- package/src/Cloudflare/Container/StartContainer.ts +1 -1
- package/src/Cloudflare/D1/D1Database.ts +23 -4
- package/src/Cloudflare/KV/KVNamespace.ts +25 -0
- package/src/Cloudflare/R2/R2Bucket.ts +43 -0
- package/src/Cloudflare/Website/StaticSite.ts +57 -0
- package/src/Cloudflare/Website/Vite.ts +52 -8
- package/src/Cloudflare/Workers/{DurableObject.ts → DurableObjectNamespace.ts} +242 -0
- package/src/Cloudflare/Workers/DynamicWorkerLoader.ts +66 -7
- package/src/Cloudflare/Workers/WebSocket.ts +1 -1
- package/src/Cloudflare/Workers/Worker.ts +312 -7
- package/src/Cloudflare/Workers/Workflow.ts +53 -11
- package/src/Cloudflare/Workers/index.ts +1 -1
- package/src/GitHub/Comment.ts +224 -0
- package/src/GitHub/Secret.ts +212 -0
- package/src/GitHub/Variable.ts +123 -0
- package/src/GitHub/index.ts +3 -0
- package/src/Provider.ts +1 -1
- package/src/Util/dedent.ts +56 -0
- package/src/Util/index.ts +1 -0
|
@@ -119,6 +119,248 @@ export class DurableObjectNamespaceScope extends Context.Service<
|
|
|
119
119
|
DurableObjectNamespace
|
|
120
120
|
>()("Cloudflare.DurableObjectNamespace") {}
|
|
121
121
|
|
|
122
|
+
/**
|
|
123
|
+
* A Cloudflare Durable Object namespace that manages globally unique, stateful
|
|
124
|
+
* instances with WebSocket hibernation support.
|
|
125
|
+
*
|
|
126
|
+
* A Durable Object uses a two-phase pattern with two nested `Effect.gen`
|
|
127
|
+
* blocks. The outer Effect resolves shared dependencies (other DOs,
|
|
128
|
+
* containers, etc.). The inner Effect runs once per instance and returns
|
|
129
|
+
* the object's public methods and WebSocket handlers.
|
|
130
|
+
*
|
|
131
|
+
* ```typescript
|
|
132
|
+
* Effect.gen(function* () {
|
|
133
|
+
* // Phase 1: resolve shared dependencies
|
|
134
|
+
* const db = yield* Cloudflare.D1Connection.bind(MyDB);
|
|
135
|
+
*
|
|
136
|
+
* return Effect.gen(function* () {
|
|
137
|
+
* // Phase 2: per-instance setup and public API
|
|
138
|
+
* const state = yield* Cloudflare.DurableObjectState;
|
|
139
|
+
*
|
|
140
|
+
* return {
|
|
141
|
+
* save: (data: string) => db.exec("INSERT ..."),
|
|
142
|
+
* fetch: Effect.gen(function* () { ... }),
|
|
143
|
+
* webSocketMessage: Effect.fnUntraced(function* (ws, msg) { ... }),
|
|
144
|
+
* };
|
|
145
|
+
* });
|
|
146
|
+
* })
|
|
147
|
+
* ```
|
|
148
|
+
*
|
|
149
|
+
* There are two ways to define a Durable Object. See the
|
|
150
|
+
* {@link https://alchemy.run/concepts/platform | Platform concept} page
|
|
151
|
+
* for the full explanation.
|
|
152
|
+
*
|
|
153
|
+
* - **Inline** — Effect implementation passed directly, single file.
|
|
154
|
+
* - **Modular** — class and implementation in separate files for tree-shaking.
|
|
155
|
+
*
|
|
156
|
+
* @resource
|
|
157
|
+
*
|
|
158
|
+
* @section Inline Durable Objects
|
|
159
|
+
* Pass the Effect implementation as the second argument. This is the
|
|
160
|
+
* simplest approach — everything lives in one file. Convenient when
|
|
161
|
+
* the DO doesn't need to be referenced by other Workers or DOs that
|
|
162
|
+
* would pull in its runtime dependencies.
|
|
163
|
+
*
|
|
164
|
+
* @example Inline Durable Object
|
|
165
|
+
* ```typescript
|
|
166
|
+
* export default class Counter extends Cloudflare.DurableObjectNamespace<Counter>()(
|
|
167
|
+
* "Counter",
|
|
168
|
+
* Effect.gen(function* () {
|
|
169
|
+
* // init: bind resources
|
|
170
|
+
* const db = yield* Cloudflare.D1Connection.bind(MyDB);
|
|
171
|
+
*
|
|
172
|
+
* return Effect.gen(function* () {
|
|
173
|
+
* const state = yield* Cloudflare.DurableObjectState;
|
|
174
|
+
* const count = (yield* state.storage.get<number>("count")) ?? 0;
|
|
175
|
+
*
|
|
176
|
+
* return {
|
|
177
|
+
* // runtime: use them
|
|
178
|
+
* increment: () =>
|
|
179
|
+
* Effect.gen(function* () {
|
|
180
|
+
* const next = count + 1;
|
|
181
|
+
* yield* state.storage.put("count", next);
|
|
182
|
+
* return next;
|
|
183
|
+
* }),
|
|
184
|
+
* get: () => Effect.succeed(count),
|
|
185
|
+
* };
|
|
186
|
+
* });
|
|
187
|
+
* }),
|
|
188
|
+
* ) {}
|
|
189
|
+
* ```
|
|
190
|
+
*
|
|
191
|
+
* @section Modular Durable Objects
|
|
192
|
+
* When a Worker and a DO reference each other, or multiple Workers
|
|
193
|
+
* bind the same DO, define the class separately from its `.make()`
|
|
194
|
+
* call. The class is a lightweight identifier; `.make()` provides
|
|
195
|
+
* the runtime implementation as an `export default`. Rolldown treats
|
|
196
|
+
* `.make()` as pure, so the bundler tree-shakes it and all its
|
|
197
|
+
* runtime dependencies out of any consumer's bundle.
|
|
198
|
+
*
|
|
199
|
+
* The class and `.make()` can live in the same file. This is the
|
|
200
|
+
* same pattern used by `Worker` and `Container`.
|
|
201
|
+
*
|
|
202
|
+
* @example Modular Durable Object (class + .make() in one file)
|
|
203
|
+
* ```typescript
|
|
204
|
+
* // src/Counter.ts
|
|
205
|
+
* export default class Counter extends Cloudflare.DurableObjectNamespace<Counter>()(
|
|
206
|
+
* "Counter",
|
|
207
|
+
* ) {}
|
|
208
|
+
*
|
|
209
|
+
* export default Counter.make(
|
|
210
|
+
* Effect.gen(function* () {
|
|
211
|
+
* // init: bind resources
|
|
212
|
+
* const db = yield* Cloudflare.D1Connection.bind(MyDB);
|
|
213
|
+
*
|
|
214
|
+
* return Effect.gen(function* () {
|
|
215
|
+
* const state = yield* Cloudflare.DurableObjectState;
|
|
216
|
+
* const count = (yield* state.storage.get<number>("count")) ?? 0;
|
|
217
|
+
*
|
|
218
|
+
* return {
|
|
219
|
+
* // runtime: use them
|
|
220
|
+
* increment: () =>
|
|
221
|
+
* Effect.gen(function* () {
|
|
222
|
+
* const next = count + 1;
|
|
223
|
+
* yield* state.storage.put("count", next);
|
|
224
|
+
* yield* db.prepare("INSERT INTO logs (count) VALUES (?)").bind(next).run();
|
|
225
|
+
* return next;
|
|
226
|
+
* }),
|
|
227
|
+
* get: () => Effect.succeed(count),
|
|
228
|
+
* };
|
|
229
|
+
* });
|
|
230
|
+
* }),
|
|
231
|
+
* );
|
|
232
|
+
* ```
|
|
233
|
+
*
|
|
234
|
+
* @example Binding a modular DO from a Worker
|
|
235
|
+
* ```typescript
|
|
236
|
+
* // imports Counter; bundler tree-shakes .make()
|
|
237
|
+
* import Counter from "./Counter.ts";
|
|
238
|
+
*
|
|
239
|
+
* // init
|
|
240
|
+
* const counters = yield* Counter;
|
|
241
|
+
*
|
|
242
|
+
* return {
|
|
243
|
+
* fetch: Effect.gen(function* () {
|
|
244
|
+
* const counter = counters.getByName("user-123");
|
|
245
|
+
* return HttpServerResponse.text(String(yield* counter.get()));
|
|
246
|
+
* }),
|
|
247
|
+
* };
|
|
248
|
+
* ```
|
|
249
|
+
*
|
|
250
|
+
* @section RPC Methods
|
|
251
|
+
* Any function you return from the inner Effect becomes an RPC method
|
|
252
|
+
* that Workers can call through a stub. Methods must return an `Effect`.
|
|
253
|
+
* The caller gets a fully typed stub — if your DO returns `increment`
|
|
254
|
+
* and `get`, the stub exposes `counter.increment()` and `counter.get()`.
|
|
255
|
+
*
|
|
256
|
+
* @example Defining RPC methods
|
|
257
|
+
* ```typescript
|
|
258
|
+
* return {
|
|
259
|
+
* increment: () => Effect.succeed(++count),
|
|
260
|
+
* get: () => Effect.succeed(count),
|
|
261
|
+
* reset: () => Effect.sync(() => { count = 0; }),
|
|
262
|
+
* };
|
|
263
|
+
* ```
|
|
264
|
+
*
|
|
265
|
+
* @section Accessing Instance State
|
|
266
|
+
* Each Durable Object instance has its own transactional key-value
|
|
267
|
+
* storage via `Cloudflare.DurableObjectState`. Use `storage.get` and
|
|
268
|
+
* `storage.put` inside the inner Effect to persist data across requests
|
|
269
|
+
* and restarts.
|
|
270
|
+
*
|
|
271
|
+
* @example Reading and writing durable storage
|
|
272
|
+
* ```typescript
|
|
273
|
+
* const state = yield* Cloudflare.DurableObjectState;
|
|
274
|
+
*
|
|
275
|
+
* yield* state.storage.put("counter", 42);
|
|
276
|
+
* const value = yield* state.storage.get("counter");
|
|
277
|
+
* ```
|
|
278
|
+
*
|
|
279
|
+
* @section WebSocket Hibernation
|
|
280
|
+
* Durable Objects support WebSocket hibernation — the runtime can
|
|
281
|
+
* evict the object from memory while keeping connections open. Use
|
|
282
|
+
* `Cloudflare.upgrade()` to accept a connection, and return
|
|
283
|
+
* `webSocketMessage` / `webSocketClose` handlers to process events
|
|
284
|
+
* when the object wakes back up.
|
|
285
|
+
*
|
|
286
|
+
* @example Accepting a WebSocket connection
|
|
287
|
+
* ```typescript
|
|
288
|
+
* return {
|
|
289
|
+
* fetch: Effect.gen(function* () {
|
|
290
|
+
* const [response, socket] = yield* Cloudflare.upgrade();
|
|
291
|
+
* socket.serializeAttachment({ id: crypto.randomUUID() });
|
|
292
|
+
* return response;
|
|
293
|
+
* }),
|
|
294
|
+
* };
|
|
295
|
+
* ```
|
|
296
|
+
*
|
|
297
|
+
* @example Handling messages and close events
|
|
298
|
+
* ```typescript
|
|
299
|
+
* return {
|
|
300
|
+
* webSocketMessage: Effect.fnUntraced(function* (
|
|
301
|
+
* socket: Cloudflare.DurableWebSocket,
|
|
302
|
+
* message: string | Uint8Array,
|
|
303
|
+
* ) {
|
|
304
|
+
* const text = typeof message === "string"
|
|
305
|
+
* ? message
|
|
306
|
+
* : new TextDecoder().decode(message);
|
|
307
|
+
* // process the message
|
|
308
|
+
* }),
|
|
309
|
+
* webSocketClose: Effect.fnUntraced(function* (
|
|
310
|
+
* ws: Cloudflare.DurableWebSocket,
|
|
311
|
+
* code: number,
|
|
312
|
+
* reason: string,
|
|
313
|
+
* ) {
|
|
314
|
+
* yield* ws.close(code, reason);
|
|
315
|
+
* }),
|
|
316
|
+
* };
|
|
317
|
+
* ```
|
|
318
|
+
*
|
|
319
|
+
* @example Recovering sessions after hibernation
|
|
320
|
+
* ```typescript
|
|
321
|
+
* const state = yield* Cloudflare.DurableObjectState;
|
|
322
|
+
* const sockets = yield* state.getWebSockets();
|
|
323
|
+
*
|
|
324
|
+
* for (const socket of sockets) {
|
|
325
|
+
* const data = socket.deserializeAttachment<{ id: string }>();
|
|
326
|
+
* // re-populate your session map
|
|
327
|
+
* }
|
|
328
|
+
* ```
|
|
329
|
+
*
|
|
330
|
+
* @section Using from a Worker
|
|
331
|
+
* Yield the DO class in your Worker's init phase to get a namespace
|
|
332
|
+
* handle. Call `getByName` or `getById` to get a typed stub, then
|
|
333
|
+
* call any RPC method or forward an HTTP request with `fetch`.
|
|
334
|
+
*
|
|
335
|
+
* @example Calling RPC methods
|
|
336
|
+
* ```typescript
|
|
337
|
+
* // init
|
|
338
|
+
* const counters = yield* Counter;
|
|
339
|
+
*
|
|
340
|
+
* return {
|
|
341
|
+
* fetch: Effect.gen(function* () {
|
|
342
|
+
* const counter = counters.getByName("user-123");
|
|
343
|
+
* yield* counter.increment();
|
|
344
|
+
* const value = yield* counter.get();
|
|
345
|
+
* return HttpServerResponse.text(String(value));
|
|
346
|
+
* }),
|
|
347
|
+
* };
|
|
348
|
+
* ```
|
|
349
|
+
*
|
|
350
|
+
* @example Forwarding an HTTP request
|
|
351
|
+
* ```typescript
|
|
352
|
+
* // init
|
|
353
|
+
* const rooms = yield* Room;
|
|
354
|
+
*
|
|
355
|
+
* return {
|
|
356
|
+
* fetch: Effect.gen(function* () {
|
|
357
|
+
* const request = yield* HttpServerRequest;
|
|
358
|
+
* const room = rooms.getByName(roomId);
|
|
359
|
+
* return yield* room.fetch(request);
|
|
360
|
+
* }),
|
|
361
|
+
* };
|
|
362
|
+
* ```
|
|
363
|
+
*/
|
|
122
364
|
export const DurableObjectNamespace: DurableObjectNamespaceClass =
|
|
123
365
|
taggedFunction(DurableObjectNamespaceScope, ((
|
|
124
366
|
...args:
|
|
@@ -70,16 +70,40 @@ export type DynamicWorkerLoader = {
|
|
|
70
70
|
};
|
|
71
71
|
|
|
72
72
|
/**
|
|
73
|
-
*
|
|
73
|
+
* Load and run ephemeral Workers at runtime from inline JavaScript
|
|
74
|
+
* modules.
|
|
74
75
|
*
|
|
75
|
-
*
|
|
76
|
-
* Worker. At runtime
|
|
77
|
-
* and
|
|
76
|
+
* `DynamicWorkerLoader` registers a `worker_loader` binding on the
|
|
77
|
+
* parent Worker at deploy time. At runtime you call `.load()` with
|
|
78
|
+
* inline module source code and get back a fully typed Worker
|
|
79
|
+
* instance you can `fetch` or call RPC methods on. Each loaded
|
|
80
|
+
* Worker runs in its own isolate with full sandboxing.
|
|
78
81
|
*
|
|
79
|
-
*
|
|
82
|
+
* This is useful for evaluating user-provided code, running
|
|
83
|
+
* untrusted plugins, or dynamically generating Workers from
|
|
84
|
+
* templates.
|
|
85
|
+
*
|
|
86
|
+
* @resource
|
|
87
|
+
*
|
|
88
|
+
* @section Creating a Loader
|
|
89
|
+
* Yield `Cloudflare.DynamicWorkerLoader` in your Worker's init
|
|
90
|
+
* phase to register the binding. The string argument becomes the
|
|
91
|
+
* binding name on the deployed Worker.
|
|
92
|
+
*
|
|
93
|
+
* @example Registering a loader
|
|
80
94
|
* ```typescript
|
|
81
|
-
*
|
|
95
|
+
* // init
|
|
96
|
+
* const loader = yield* Cloudflare.DynamicWorkerLoader("Loader");
|
|
97
|
+
* ```
|
|
98
|
+
*
|
|
99
|
+
* @section Loading a Worker
|
|
100
|
+
* Call `loader.load()` with a compatibility date, a main module
|
|
101
|
+
* name, and a map of module names to source code strings. The
|
|
102
|
+
* returned instance exposes `.fetch()` for HTTP and RPC methods
|
|
103
|
+
* for named entrypoints.
|
|
82
104
|
*
|
|
105
|
+
* @example Loading and calling a dynamic Worker
|
|
106
|
+
* ```typescript
|
|
83
107
|
* const worker = loader.load({
|
|
84
108
|
* compatibilityDate: "2026-01-28",
|
|
85
109
|
* mainModule: "worker.js",
|
|
@@ -90,10 +114,45 @@ export type DynamicWorkerLoader = {
|
|
|
90
114
|
* }
|
|
91
115
|
* }`,
|
|
92
116
|
* },
|
|
117
|
+
* });
|
|
118
|
+
*
|
|
119
|
+
* const response = yield* worker.fetch(
|
|
120
|
+
* HttpClientRequest.get("https://worker/"),
|
|
121
|
+
* );
|
|
122
|
+
* ```
|
|
123
|
+
*
|
|
124
|
+
* @section Sandboxing
|
|
125
|
+
* Set `globalOutbound` to `null` to block all outbound network
|
|
126
|
+
* access from the dynamic Worker, or pass an RPC stub to intercept
|
|
127
|
+
* and proxy outbound requests.
|
|
128
|
+
*
|
|
129
|
+
* @example Blocking outbound access
|
|
130
|
+
* ```typescript
|
|
131
|
+
* const worker = loader.load({
|
|
132
|
+
* compatibilityDate: "2026-01-28",
|
|
133
|
+
* mainModule: "worker.js",
|
|
134
|
+
* modules: {
|
|
135
|
+
* "worker.js": `export default {
|
|
136
|
+
* async fetch(req) {
|
|
137
|
+
* // fetch() calls from here will fail
|
|
138
|
+
* return new Response("sandboxed");
|
|
139
|
+
* }
|
|
140
|
+
* }`,
|
|
141
|
+
* },
|
|
93
142
|
* globalOutbound: null,
|
|
94
143
|
* });
|
|
144
|
+
* ```
|
|
95
145
|
*
|
|
96
|
-
*
|
|
146
|
+
* @section Named Entrypoints
|
|
147
|
+
* If the dynamic Worker exports named entrypoints, use
|
|
148
|
+
* `.getEntrypoint(name)` to get a typed stub for calling its
|
|
149
|
+
* methods.
|
|
150
|
+
*
|
|
151
|
+
* @example Calling a named entrypoint
|
|
152
|
+
* ```typescript
|
|
153
|
+
* const worker = loader.load({ ... });
|
|
154
|
+
* const api = worker.getEntrypoint<{ greet: (name: string) => Effect.Effect<string> }>("api");
|
|
155
|
+
* const greeting = yield* api.greet("world");
|
|
97
156
|
* ```
|
|
98
157
|
*/
|
|
99
158
|
export const DynamicWorkerLoader = Effect.fnUntraced(function* (name: string) {
|
|
@@ -2,7 +2,7 @@ import type * as cf from "@cloudflare/workers-types";
|
|
|
2
2
|
import * as Effect from "effect/Effect";
|
|
3
3
|
import * as HttpBody from "effect/unstable/http/HttpBody";
|
|
4
4
|
import * as HttpServerResponse from "effect/unstable/http/HttpServerResponse";
|
|
5
|
-
import { DurableObjectState } from "./
|
|
5
|
+
import { DurableObjectState } from "./DurableObjectNamespace.ts";
|
|
6
6
|
|
|
7
7
|
export type RawWebSocket = cf.WebSocket;
|
|
8
8
|
|
|
@@ -50,7 +50,7 @@ import type { R2Bucket } from "../R2/R2Bucket.ts";
|
|
|
50
50
|
import type { AssetsConfig, AssetsProps } from "./Assets.ts";
|
|
51
51
|
import * as Assets from "./Assets.ts";
|
|
52
52
|
import cloudflare_workers from "./cloudflare_workers.ts";
|
|
53
|
-
import { isDurableObjectExport } from "./
|
|
53
|
+
import { isDurableObjectExport } from "./DurableObjectNamespace.ts";
|
|
54
54
|
import { workersHttpHandler } from "./HttpServer.ts";
|
|
55
55
|
import { Request } from "./Request.ts";
|
|
56
56
|
import { makeRpcStub } from "./Rpc.ts";
|
|
@@ -256,17 +256,322 @@ export type Worker<Bindings extends WorkerBindings = any> = Resource<
|
|
|
256
256
|
* A Cloudflare Worker host with deploy-time binding support and runtime export
|
|
257
257
|
* collection.
|
|
258
258
|
*
|
|
259
|
-
*
|
|
260
|
-
*
|
|
261
|
-
*
|
|
259
|
+
* A Worker follows a two-phase pattern. The outer `Effect.gen` runs at
|
|
260
|
+
* deploy time to bind resources (KV, R2, Durable Objects, etc.). It returns
|
|
261
|
+
* an object whose properties are the Worker's runtime handlers — `fetch` for
|
|
262
|
+
* HTTP requests and any additional RPC methods.
|
|
262
263
|
*
|
|
263
|
-
* @section Creating Workers
|
|
264
|
-
* @example Basic Worker
|
|
265
264
|
* ```typescript
|
|
266
|
-
*
|
|
265
|
+
* Effect.gen(function* () {
|
|
266
|
+
* // Phase 1: bind resources (runs at deploy time)
|
|
267
|
+
* const kv = yield* Cloudflare.KVNamespace.bind(MyKV);
|
|
268
|
+
*
|
|
269
|
+
* return {
|
|
270
|
+
* // Phase 2: runtime handlers (runs on each request)
|
|
271
|
+
* fetch: Effect.gen(function* () {
|
|
272
|
+
* const value = yield* kv.get("key");
|
|
273
|
+
* return HttpServerResponse.text(value ?? "not found");
|
|
274
|
+
* }),
|
|
275
|
+
* };
|
|
276
|
+
* })
|
|
277
|
+
* ```
|
|
278
|
+
*
|
|
279
|
+
* There are three ways to define a Worker, from simplest to most
|
|
280
|
+
* flexible. See the {@link https://alchemy.run/concepts/platform | Platform concept}
|
|
281
|
+
* page for the full explanation.
|
|
282
|
+
*
|
|
283
|
+
* - **Async** — plain `async fetch` handler, no Effect runtime in the bundle.
|
|
284
|
+
* - **Effect** — Effect implementation passed directly, single file.
|
|
285
|
+
* - **Layer** — class and `.make()` in a single file; Rolldown tree-shakes `.make()` from consumers.
|
|
286
|
+
*
|
|
287
|
+
* @section Async Workers
|
|
288
|
+
* You don't have to use Effect for your runtime code. If you create
|
|
289
|
+
* a Worker resource with `main` pointing at a file but provide no
|
|
290
|
+
* `Effect.gen` implementation, Alchemy bundles and deploys that file
|
|
291
|
+
* as-is. Your handler is a plain `async fetch` — no Effect runtime
|
|
292
|
+
* is included in the bundle.
|
|
293
|
+
*
|
|
294
|
+
* Use the `bindings` prop to declare which resources are available
|
|
295
|
+
* at runtime, and `Cloudflare.InferEnv` to extract a fully typed
|
|
296
|
+
* `env` object from those bindings.
|
|
297
|
+
*
|
|
298
|
+
* @example Defining an async Worker in your stack
|
|
299
|
+
* ```typescript
|
|
300
|
+
* // alchemy.run.ts
|
|
301
|
+
* const db = yield* Cloudflare.D1Database("DB");
|
|
302
|
+
* const bucket = yield* Cloudflare.R2Bucket("Bucket");
|
|
303
|
+
*
|
|
304
|
+
* export type WorkerEnv = Cloudflare.InferEnv<typeof Worker>;
|
|
305
|
+
*
|
|
306
|
+
* export const Worker = Cloudflare.Worker("Worker", {
|
|
267
307
|
* main: "./src/worker.ts",
|
|
308
|
+
* bindings: { db, bucket },
|
|
309
|
+
* });
|
|
310
|
+
* ```
|
|
311
|
+
*
|
|
312
|
+
* @example Writing the async handler
|
|
313
|
+
* ```typescript
|
|
314
|
+
* // src/worker.ts
|
|
315
|
+
* import type { WorkerEnv } from "../alchemy.run.ts";
|
|
316
|
+
*
|
|
317
|
+
* export default {
|
|
318
|
+
* async fetch(request: Request, env: WorkerEnv) {
|
|
319
|
+
* if (request.method === "GET") {
|
|
320
|
+
* const object = await env.bucket.get("key");
|
|
321
|
+
* return new Response(object?.body ?? null);
|
|
322
|
+
* }
|
|
323
|
+
* return new Response("Not Found", { status: 404 });
|
|
324
|
+
* },
|
|
325
|
+
* };
|
|
326
|
+
* ```
|
|
327
|
+
*
|
|
328
|
+
* @section Effect Workers
|
|
329
|
+
* Pass the Effect implementation as the third argument. This is the
|
|
330
|
+
* simplest Effect-based approach — everything lives in one file.
|
|
331
|
+
* Convenient for standalone Workers that don't need to be referenced
|
|
332
|
+
* by other Workers.
|
|
333
|
+
*
|
|
334
|
+
* @example Worker Effect
|
|
335
|
+
* ```typescript
|
|
336
|
+
* export default class MyWorker extends Cloudflare.Worker<MyWorker>()(
|
|
337
|
+
* "MyWorker",
|
|
338
|
+
* { main: import.meta.path },
|
|
339
|
+
* Effect.gen(function* () {
|
|
340
|
+
* // init: bind resources
|
|
341
|
+
* const kv = yield* Cloudflare.KVNamespace.bind(MyKV);
|
|
342
|
+
*
|
|
343
|
+
* return {
|
|
344
|
+
* // runtime: use them
|
|
345
|
+
* fetch: Effect.gen(function* () {
|
|
346
|
+
* const value = yield* kv.get("key");
|
|
347
|
+
* return HttpServerResponse.text(value ?? "not found");
|
|
348
|
+
* }),
|
|
349
|
+
* };
|
|
350
|
+
* }),
|
|
351
|
+
* ) {}
|
|
352
|
+
* ```
|
|
353
|
+
*
|
|
354
|
+
* @section Worker Layer
|
|
355
|
+
* When two Workers need to reference each other (e.g. WorkerA calls
|
|
356
|
+
* WorkerB and vice versa), or you simply want optimal tree-shaking,
|
|
357
|
+
* define the Worker class separately from its `.make()` call. The
|
|
358
|
+
* class is a lightweight identifier; `.make()` provides the runtime
|
|
359
|
+
* implementation as an `export default`. Rolldown treats `.make()`
|
|
360
|
+
* as pure, so any Worker that imports the class to bind it will not
|
|
361
|
+
* pull in the `.make()` dependencies — the bundler tree-shakes
|
|
362
|
+
* them away entirely.
|
|
363
|
+
*
|
|
364
|
+
* The class and `.make()` can live in the same file. This is the
|
|
365
|
+
* same pattern used by `Container` and `DurableObjectNamespace`,
|
|
366
|
+
* and is recommended for any cross-Worker or cross-DO bindings.
|
|
367
|
+
*
|
|
368
|
+
* @example Worker Layer (class + .make() in one file)
|
|
369
|
+
* ```typescript
|
|
370
|
+
* // src/WorkerB.ts
|
|
371
|
+
* export default class WorkerB extends Cloudflare.Worker<WorkerB>()(
|
|
372
|
+
* "WorkerB",
|
|
373
|
+
* { main: import.meta.path },
|
|
374
|
+
* ) {}
|
|
375
|
+
*
|
|
376
|
+
* export default WorkerB.make(
|
|
377
|
+
* Effect.gen(function* () {
|
|
378
|
+
* // init: bind resources
|
|
379
|
+
* const kv = yield* Cloudflare.KVNamespace.bind(MyKV);
|
|
380
|
+
*
|
|
381
|
+
* return {
|
|
382
|
+
* // runtime: use them
|
|
383
|
+
* greet: (name: string) =>
|
|
384
|
+
* Effect.gen(function* () {
|
|
385
|
+
* yield* kv.put("last-greeted", name);
|
|
386
|
+
* return `Hello ${name}`;
|
|
387
|
+
* }),
|
|
388
|
+
* };
|
|
389
|
+
* }),
|
|
390
|
+
* );
|
|
391
|
+
* ```
|
|
392
|
+
*
|
|
393
|
+
* @example Binding a Worker Layer from another Worker
|
|
394
|
+
* ```typescript
|
|
395
|
+
* // src/WorkerA.ts — imports WorkerB; bundler tree-shakes .make()
|
|
396
|
+
* import WorkerB from "./WorkerB.ts";
|
|
397
|
+
*
|
|
398
|
+
* export default class WorkerA extends Cloudflare.Worker<WorkerA>()(
|
|
399
|
+
* "WorkerA",
|
|
400
|
+
* { main: import.meta.path },
|
|
401
|
+
* Effect.gen(function* () {
|
|
402
|
+
* const b = yield* Cloudflare.Worker.bind(WorkerB);
|
|
403
|
+
* return {
|
|
404
|
+
* fetch: Effect.gen(function* () {
|
|
405
|
+
* return yield* b.greet("world");
|
|
406
|
+
* }),
|
|
407
|
+
* };
|
|
408
|
+
* }),
|
|
409
|
+
* ) {}
|
|
410
|
+
* ```
|
|
411
|
+
*
|
|
412
|
+
* @section Configuration
|
|
413
|
+
* The props object controls compatibility flags, static assets, and
|
|
414
|
+
* build options. These are evaluated at deploy time.
|
|
415
|
+
*
|
|
416
|
+
* @example Enabling Node.js compatibility
|
|
417
|
+
* ```typescript
|
|
418
|
+
* {
|
|
419
|
+
* main: import.meta.path,
|
|
420
|
+
* compatibility: {
|
|
421
|
+
* flags: ["nodejs_compat"],
|
|
422
|
+
* date: "2026-03-17",
|
|
423
|
+
* },
|
|
424
|
+
* }
|
|
425
|
+
* ```
|
|
426
|
+
*
|
|
427
|
+
* @example Serving static assets
|
|
428
|
+
* ```typescript
|
|
429
|
+
* {
|
|
430
|
+
* main: import.meta.path,
|
|
431
|
+
* assets: "./public",
|
|
432
|
+
* }
|
|
433
|
+
* ```
|
|
434
|
+
*
|
|
435
|
+
* @section R2 Bucket
|
|
436
|
+
* Bind an R2 bucket in the init phase with `Cloudflare.R2Bucket.bind`.
|
|
437
|
+
* The returned handle exposes `get`, `put`, `delete`, and `list`
|
|
438
|
+
* methods you can call in your runtime handlers.
|
|
439
|
+
*
|
|
440
|
+
* @example Binding and using R2
|
|
441
|
+
* ```typescript
|
|
442
|
+
* // init
|
|
443
|
+
* const bucket = yield* Cloudflare.R2Bucket.bind(MyBucket);
|
|
444
|
+
*
|
|
445
|
+
* return {
|
|
446
|
+
* fetch: Effect.gen(function* () {
|
|
447
|
+
* const request = yield* HttpServerRequest;
|
|
448
|
+
* const key = request.url.split("/").pop()!;
|
|
449
|
+
*
|
|
450
|
+
* if (request.method === "GET") {
|
|
451
|
+
* const object = yield* bucket.get(key);
|
|
452
|
+
* return object
|
|
453
|
+
* ? HttpServerResponse.text(yield* object.text())
|
|
454
|
+
* : HttpServerResponse.empty({ status: 404 });
|
|
455
|
+
* }
|
|
456
|
+
*
|
|
457
|
+
* yield* bucket.put(key, request.stream);
|
|
458
|
+
* return HttpServerResponse.empty({ status: 201 });
|
|
459
|
+
* }),
|
|
460
|
+
* };
|
|
461
|
+
* ```
|
|
462
|
+
*
|
|
463
|
+
* @section KV Namespace
|
|
464
|
+
* Bind a KV namespace with `Cloudflare.KVNamespace.bind`. KV provides
|
|
465
|
+
* eventually-consistent, low-latency key-value reads replicated
|
|
466
|
+
* globally across Cloudflare's edge.
|
|
467
|
+
*
|
|
468
|
+
* @example Binding and using KV
|
|
469
|
+
* ```typescript
|
|
470
|
+
* // init
|
|
471
|
+
* const kv = yield* Cloudflare.KVNamespace.bind(MyKV);
|
|
472
|
+
*
|
|
473
|
+
* return {
|
|
474
|
+
* fetch: Effect.gen(function* () {
|
|
475
|
+
* const value = yield* kv.get("my-key");
|
|
476
|
+
* return HttpServerResponse.text(value ?? "not found");
|
|
477
|
+
* }),
|
|
478
|
+
* };
|
|
479
|
+
* ```
|
|
480
|
+
*
|
|
481
|
+
* @section D1 Database
|
|
482
|
+
* Bind a D1 database with `Cloudflare.D1Connection.bind`. D1 is a
|
|
483
|
+
* serverless SQLite database — use `prepare` to build parameterized
|
|
484
|
+
* queries and `all`, `first`, or `run` to execute them.
|
|
485
|
+
*
|
|
486
|
+
* @example Binding and querying D1
|
|
487
|
+
* ```typescript
|
|
488
|
+
* // init
|
|
489
|
+
* const db = yield* Cloudflare.D1Connection.bind(MyDB);
|
|
490
|
+
*
|
|
491
|
+
* return {
|
|
492
|
+
* fetch: Effect.gen(function* () {
|
|
493
|
+
* const results = yield* db
|
|
494
|
+
* .prepare("SELECT * FROM users WHERE id = ?")
|
|
495
|
+
* .bind(userId)
|
|
496
|
+
* .all();
|
|
497
|
+
* return yield* HttpServerResponse.json(results);
|
|
498
|
+
* }),
|
|
499
|
+
* };
|
|
500
|
+
* ```
|
|
501
|
+
*
|
|
502
|
+
* @section Durable Objects
|
|
503
|
+
* Yield a `DurableObjectNamespace` class in the init phase to get a
|
|
504
|
+
* namespace handle. Call `getByName` or `getById` to get a typed RPC
|
|
505
|
+
* stub, then call its methods from your runtime handlers.
|
|
506
|
+
*
|
|
507
|
+
* @example Using a Durable Object
|
|
508
|
+
* ```typescript
|
|
509
|
+
* // init
|
|
510
|
+
* const counters = yield* Counter;
|
|
511
|
+
*
|
|
512
|
+
* return {
|
|
513
|
+
* fetch: Effect.gen(function* () {
|
|
514
|
+
* const counter = counters.getByName("user-123");
|
|
515
|
+
* const value = yield* counter.increment();
|
|
516
|
+
* return HttpServerResponse.text(String(value));
|
|
517
|
+
* }),
|
|
518
|
+
* };
|
|
519
|
+
* ```
|
|
520
|
+
*
|
|
521
|
+
* @section Containers
|
|
522
|
+
* Containers run long-lived processes alongside Durable Objects. Bind
|
|
523
|
+
* one with `Cloudflare.Container.bind` and start it with
|
|
524
|
+
* `Cloudflare.start`. You can call typed methods on the running
|
|
525
|
+
* container or make HTTP requests to its exposed ports.
|
|
526
|
+
*
|
|
527
|
+
* @example Binding and starting a Container
|
|
528
|
+
* ```typescript
|
|
529
|
+
* // init (inside a DurableObjectNamespace)
|
|
530
|
+
* const sandbox = yield* Cloudflare.Container.bind(Sandbox);
|
|
531
|
+
*
|
|
532
|
+
* return Effect.gen(function* () {
|
|
533
|
+
* const container = yield* Cloudflare.start(sandbox);
|
|
534
|
+
*
|
|
535
|
+
* return {
|
|
536
|
+
* exec: (cmd: string) => container.exec(cmd),
|
|
537
|
+
* fetch: Effect.gen(function* () {
|
|
538
|
+
* const { fetch } = yield* container.getTcpPort(3000);
|
|
539
|
+
* const res = yield* fetch(HttpClientRequest.get("http://container/"));
|
|
540
|
+
* return HttpServerResponse.fromClientResponse(res);
|
|
541
|
+
* }),
|
|
542
|
+
* };
|
|
268
543
|
* });
|
|
269
544
|
* ```
|
|
545
|
+
*
|
|
546
|
+
* @section Dynamic Workers
|
|
547
|
+
* `DynamicWorkerLoader` lets you spin up ephemeral Workers at runtime
|
|
548
|
+
* from inline JavaScript modules. This is useful for sandboxing
|
|
549
|
+
* user-provided code or running untrusted scripts in isolation.
|
|
550
|
+
*
|
|
551
|
+
* @example Loading a dynamic Worker
|
|
552
|
+
* ```typescript
|
|
553
|
+
* // init
|
|
554
|
+
* const loader = yield* Cloudflare.DynamicWorkerLoader("Loader");
|
|
555
|
+
*
|
|
556
|
+
* return {
|
|
557
|
+
* fetch: Effect.gen(function* () {
|
|
558
|
+
* const worker = loader.load({
|
|
559
|
+
* compatibilityDate: "2026-01-28",
|
|
560
|
+
* mainModule: "worker.js",
|
|
561
|
+
* modules: {
|
|
562
|
+
* "worker.js": `export default {
|
|
563
|
+
* async fetch(req) { return new Response("sandboxed"); }
|
|
564
|
+
* }`,
|
|
565
|
+
* },
|
|
566
|
+
* });
|
|
567
|
+
*
|
|
568
|
+
* const res = yield* worker.fetch(
|
|
569
|
+
* HttpClientRequest.get("https://worker/"),
|
|
570
|
+
* );
|
|
571
|
+
* return HttpServerResponse.fromClientResponse(res);
|
|
572
|
+
* }),
|
|
573
|
+
* };
|
|
574
|
+
* ```
|
|
270
575
|
*/
|
|
271
576
|
export const Worker: Platform<
|
|
272
577
|
Worker,
|