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.
- package/bin/{alchemy-effect.js → alchemy.js} +173 -173
- package/bin/alchemy.js.map +1 -0
- package/bin/{alchemy-effect.sh → alchemy.sh} +2 -2
- package/lib/cli/index.js +169 -169
- package/lib/cli/index.js.map +1 -1
- package/package.json +75 -67
- package/src/AWS/AGENTS.md +10 -10
- package/src/AWS/Assets.ts +1 -1
- package/src/AWS/Credentials.ts +0 -1
- package/src/AWS/DynamoDB/Table.ts +63 -10
- package/src/AWS/EC2/hosted.ts +1 -1
- package/src/AWS/ECS/Task.ts +1 -1
- package/src/AWS/Kinesis/Stream.ts +44 -8
- package/src/AWS/Lambda/Function.ts +218 -10
- package/src/AWS/S3/Bucket.ts +26 -19
- package/src/AWS/S3/BucketNotifications.ts +1 -1
- package/src/AWS/SNS/Topic.ts +47 -10
- package/src/AWS/SQS/Queue.ts +66 -0
- package/src/Cloudflare/Container/Container.ts +122 -0
- package/src/Cloudflare/Container/ContainerApplication.ts +6 -3
- package/src/Cloudflare/Container/ContainerBinding.ts +2 -4
- 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/Providers.ts +1 -6
- package/src/Cloudflare/R2/R2Bucket.ts +45 -2
- package/src/Cloudflare/Website/StaticSite.ts +79 -6
- package/src/Cloudflare/Website/Vite.ts +67 -13
- package/src/Cloudflare/Workers/Assets.ts +170 -207
- package/src/Cloudflare/Workers/DurableObjectNamespace.ts +629 -0
- package/src/Cloudflare/Workers/DurableObjectState.ts +63 -0
- package/src/Cloudflare/Workers/DurableObjectStorage.ts +256 -0
- package/src/Cloudflare/Workers/DynamicWorkerLoader.ts +66 -7
- package/src/Cloudflare/Workers/InferEnv.ts +11 -7
- package/src/Cloudflare/Workers/Rpc.ts +13 -2
- package/src/Cloudflare/Workers/ScheduledEvents.ts +185 -0
- package/src/Cloudflare/Workers/WebSocket.ts +1 -1
- package/src/Cloudflare/Workers/Worker.ts +406 -49
- package/src/Cloudflare/Workers/Workflow.ts +53 -11
- package/src/Cloudflare/Workers/index.ts +4 -1
- package/src/Construct.ts +2 -2
- package/src/GitHub/Comment.ts +224 -0
- package/src/GitHub/Secret.ts +257 -0
- package/src/GitHub/Variable.ts +166 -0
- package/src/GitHub/index.ts +3 -0
- package/src/Kubernetes/client.ts +2 -2
- package/src/Output.ts +1 -1
- package/src/Platform.ts +1 -1
- package/src/Provider.ts +1 -1
- package/src/Test/Vitest.ts +4 -3
- package/src/Util/PlatformServices.ts +21 -0
- package/src/Util/dedent.ts +59 -0
- package/src/Util/index.ts +1 -0
- package/bin/alchemy-effect.js.map +0 -1
- package/src/Cloudflare/Workers/DurableObject.ts +0 -527
- package/src/Daemon/Client.ts +0 -116
- package/src/Daemon/Config.ts +0 -28
- package/src/Daemon/Errors.ts +0 -48
- package/src/Daemon/Lock.ts +0 -162
- package/src/Daemon/ProcessRegistry.ts +0 -284
- package/src/Daemon/RpcSchema.ts +0 -43
- package/src/Daemon/RpcServer.ts +0 -231
- package/src/Daemon/index.ts +0 -27
- package/src/Spawn.ts +0 -23
- /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 {
|
|
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
|
|
51
|
-
|
|
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 {
|
|
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:
|
|
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:
|
|
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 =
|
|
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
|
-
*
|
|
260
|
-
*
|
|
261
|
-
*
|
|
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
|
-
*
|
|
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
|
-
<
|
|
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
|
-
[
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
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
|
|
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: "
|
|
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.
|
|
656
|
+
: binding.Type === "Cloudflare.D1Database"
|
|
314
657
|
? {
|
|
315
|
-
type: "
|
|
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
|
-
:
|
|
325
|
-
|
|
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)
|
|
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*
|
|
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*
|
|
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
|
|
764
|
-
import { WorkerEnvironment, makeDurableObjectBridge${hasWfClasses ? ", makeWorkflowBridge" : ""}, ExportedHandlerMethods } from "alchemy
|
|
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
|
-
?
|
|
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*
|
|
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
|
-
*
|
|
189
|
+
* A Cloudflare Workflow that orchestrates durable, multi-step tasks with
|
|
190
|
+
* automatic retries and at-least-once delivery.
|
|
190
191
|
*
|
|
191
|
-
*
|
|
192
|
-
*
|
|
193
|
-
*
|
|
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
|
-
*
|
|
196
|
-
*
|
|
197
|
-
*
|
|
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
|
-
* @
|
|
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
|
-
*
|
|
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 "./
|
|
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";
|