alchemy 2.0.0-beta.2 → 2.0.0-beta.4
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} +171 -171
- package/bin/alchemy.js.map +1 -0
- package/bin/{alchemy-effect.sh → alchemy.sh} +2 -2
- package/lib/cli/index.d.ts.map +1 -1
- package/lib/cli/index.js +167 -167
- package/lib/cli/index.js.map +1 -1
- package/package.json +54 -45
- package/src/AWS/AGENTS.md +10 -10
- package/src/AWS/Assets.ts +1 -1
- package/src/AWS/AuthProvider.ts +392 -0
- package/src/AWS/Credentials.ts +0 -1
- package/src/AWS/DynamoDB/Table.ts +63 -10
- package/src/AWS/EC2/VpcEndpoint.ts +4 -4
- 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/Auth/AuthProvider.ts +33 -0
- package/src/Auth/Credentials.ts +56 -0
- package/src/Auth/Env.ts +45 -0
- package/src/Auth/Profile.ts +72 -0
- package/src/Auth/index.ts +2 -0
- package/src/Cloudflare/Auth/AuthProvider.ts +670 -0
- package/src/Cloudflare/Auth/OAuthClient.ts +315 -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 +101 -15
- package/src/Cloudflare/Website/Vite.ts +86 -20
- 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 +572 -67
- 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 +10 -5
- package/src/Provider.ts +1 -1
- package/src/Resource.ts +4 -4
- package/src/Test/Vitest.ts +4 -3
- package/src/Util/Clank.ts +77 -0
- 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
|
@@ -2,6 +2,7 @@ import type * as cf from "@cloudflare/workers-types";
|
|
|
2
2
|
import cloudflareRolldown from "@distilled.cloud/cloudflare-rolldown-plugin";
|
|
3
3
|
import cloudflareVite from "@distilled.cloud/cloudflare-vite-plugin";
|
|
4
4
|
import * as workers from "@distilled.cloud/cloudflare/workers";
|
|
5
|
+
import * as zones from "@distilled.cloud/cloudflare/zones";
|
|
5
6
|
import type * as Cause from "effect/Cause";
|
|
6
7
|
import * as Context from "effect/Context";
|
|
7
8
|
import * as Data from "effect/Data";
|
|
@@ -26,7 +27,7 @@ import { findCwdForBundle } from "../../Bundle/TempRoot.ts";
|
|
|
26
27
|
import type { ScopedPlanStatusSession } from "../../Cli/Cli.ts";
|
|
27
28
|
import { isResolved } from "../../Diff.ts";
|
|
28
29
|
import type { HttpEffect } from "../../Http.ts";
|
|
29
|
-
import type {
|
|
30
|
+
import type { InputProps } from "../../Input.ts";
|
|
30
31
|
import * as Output from "../../Output.ts";
|
|
31
32
|
import { createPhysicalName } from "../../PhysicalName.ts";
|
|
32
33
|
import {
|
|
@@ -47,10 +48,20 @@ import { fromCloudflareFetcher } from "../Fetcher.ts";
|
|
|
47
48
|
import { CloudflareLogs } from "../Logs.ts";
|
|
48
49
|
import type { Providers } from "../Providers.ts";
|
|
49
50
|
import type { R2Bucket } from "../R2/R2Bucket.ts";
|
|
50
|
-
import
|
|
51
|
-
|
|
51
|
+
import {
|
|
52
|
+
isAssets,
|
|
53
|
+
readAssets,
|
|
54
|
+
uploadAssets,
|
|
55
|
+
type Assets,
|
|
56
|
+
type AssetsConfig,
|
|
57
|
+
type AssetsProps,
|
|
58
|
+
} from "./Assets.ts";
|
|
52
59
|
import cloudflare_workers from "./cloudflare_workers.ts";
|
|
53
|
-
import {
|
|
60
|
+
import {
|
|
61
|
+
isDurableObjectExport,
|
|
62
|
+
isDurableObjectNamespaceLike,
|
|
63
|
+
type DurableObjectNamespaceLike,
|
|
64
|
+
} from "./DurableObjectNamespace.ts";
|
|
54
65
|
import { workersHttpHandler } from "./HttpServer.ts";
|
|
55
66
|
import { Request } from "./Request.ts";
|
|
56
67
|
import { makeRpcStub } from "./Rpc.ts";
|
|
@@ -105,12 +116,12 @@ export interface AssetsWithHash {
|
|
|
105
116
|
/**
|
|
106
117
|
* Path to the assets directory.
|
|
107
118
|
*/
|
|
108
|
-
path:
|
|
119
|
+
path: string;
|
|
109
120
|
/**
|
|
110
121
|
* Pre-computed hash of the assets. When provided, this hash is used for diffing
|
|
111
122
|
* to determine if the worker needs to be redeployed.
|
|
112
123
|
*/
|
|
113
|
-
hash:
|
|
124
|
+
hash: string;
|
|
114
125
|
/**
|
|
115
126
|
* Optional assets configuration.
|
|
116
127
|
*/
|
|
@@ -161,7 +172,11 @@ export type WorkerServices = Worker | WorkerEnvironment | Request;
|
|
|
161
172
|
|
|
162
173
|
export type WorkerShape = Main<WorkerServices>;
|
|
163
174
|
|
|
164
|
-
export type WorkerBindingResource =
|
|
175
|
+
export type WorkerBindingResource =
|
|
176
|
+
| Assets
|
|
177
|
+
| R2Bucket
|
|
178
|
+
| D1Database
|
|
179
|
+
| DurableObjectNamespaceLike<any>;
|
|
165
180
|
|
|
166
181
|
export type WorkerBindings = {
|
|
167
182
|
[bindingName in string]: WorkerBindingResource;
|
|
@@ -173,8 +188,26 @@ export type WorkerBindingProps = {
|
|
|
173
188
|
| Effect.Effect<WorkerBindingResource, any, any>;
|
|
174
189
|
};
|
|
175
190
|
|
|
191
|
+
type NormalizedBindings<
|
|
192
|
+
Bindings extends WorkerBindingProps = {},
|
|
193
|
+
AssetsConfig extends WorkerAssetsConfig | undefined = undefined,
|
|
194
|
+
> = {
|
|
195
|
+
[B in keyof Bindings]: Bindings[B] extends Effect.Effect<
|
|
196
|
+
infer T extends WorkerBindingResource,
|
|
197
|
+
any,
|
|
198
|
+
any
|
|
199
|
+
>
|
|
200
|
+
? T
|
|
201
|
+
: Extract<Bindings[B], WorkerBindingResource>;
|
|
202
|
+
} & (undefined extends AssetsConfig ? {} : { ASSETS: Assets });
|
|
203
|
+
|
|
204
|
+
export type WorkerAssetsConfig = string | AssetsProps | AssetsWithHash;
|
|
205
|
+
|
|
176
206
|
export interface WorkerProps<
|
|
177
207
|
Bindings extends WorkerBindingProps = any,
|
|
208
|
+
Assets extends WorkerAssetsConfig | undefined =
|
|
209
|
+
| WorkerAssetsConfig
|
|
210
|
+
| undefined,
|
|
178
211
|
> extends PlatformProps {
|
|
179
212
|
/**
|
|
180
213
|
* Worker name override. If omitted, Alchemy derives a deterministic physical
|
|
@@ -192,11 +225,7 @@ export interface WorkerProps<
|
|
|
192
225
|
* - An AssetsProps object with directory and config
|
|
193
226
|
* - An object with path and hash (e.g., from a Build resource)
|
|
194
227
|
*/
|
|
195
|
-
assets?:
|
|
196
|
-
| string
|
|
197
|
-
| AssetsProps
|
|
198
|
-
| AssetsWithHash
|
|
199
|
-
| (AssetsWithHash & { [K: string]: any });
|
|
228
|
+
assets?: Assets;
|
|
200
229
|
subdomain?: {
|
|
201
230
|
enabled?: boolean;
|
|
202
231
|
previewsEnabled?: boolean;
|
|
@@ -219,6 +248,12 @@ export interface WorkerProps<
|
|
|
219
248
|
env?: Record<string, string | Redacted.Redacted<string>>;
|
|
220
249
|
exports?: string[];
|
|
221
250
|
bindings?: Bindings;
|
|
251
|
+
/**
|
|
252
|
+
* One or more custom hostnames (e.g. `"app.example.com"`) to bind to this
|
|
253
|
+
* Worker. The Cloudflare Zone is inferred from the hostname — the zone must
|
|
254
|
+
* already exist in the account.
|
|
255
|
+
*/
|
|
256
|
+
domain?: string | string[];
|
|
222
257
|
build?: {
|
|
223
258
|
/**
|
|
224
259
|
* Whether to generate a metafile for the worker bundle.
|
|
@@ -239,6 +274,7 @@ export type Worker<Bindings extends WorkerBindings = any> = Resource<
|
|
|
239
274
|
tags: string[] | undefined;
|
|
240
275
|
durableObjectNamespaces: Record<string, string>;
|
|
241
276
|
accountId: string;
|
|
277
|
+
domains: { hostname: string; id: string; zoneId: string }[];
|
|
242
278
|
hash?: {
|
|
243
279
|
assets: string | undefined;
|
|
244
280
|
bundle: string | undefined;
|
|
@@ -256,17 +292,326 @@ export type Worker<Bindings extends WorkerBindings = any> = Resource<
|
|
|
256
292
|
* A Cloudflare Worker host with deploy-time binding support and runtime export
|
|
257
293
|
* collection.
|
|
258
294
|
*
|
|
259
|
-
*
|
|
260
|
-
*
|
|
261
|
-
*
|
|
295
|
+
* A Worker follows a two-phase pattern. The outer `Effect.gen` runs at
|
|
296
|
+
* deploy time to bind resources (KV, R2, Durable Objects, etc.). It returns
|
|
297
|
+
* an object whose properties are the Worker's runtime handlers — `fetch` for
|
|
298
|
+
* HTTP requests and any additional RPC methods.
|
|
262
299
|
*
|
|
263
|
-
* @section Creating Workers
|
|
264
|
-
* @example Basic Worker
|
|
265
300
|
* ```typescript
|
|
266
|
-
*
|
|
301
|
+
* Effect.gen(function* () {
|
|
302
|
+
* // Phase 1: bind resources (runs at deploy time)
|
|
303
|
+
* const kv = yield* Cloudflare.KVNamespace.bind(MyKV);
|
|
304
|
+
*
|
|
305
|
+
* return {
|
|
306
|
+
* // Phase 2: runtime handlers (runs on each request)
|
|
307
|
+
* fetch: Effect.gen(function* () {
|
|
308
|
+
* const value = yield* kv.get("key");
|
|
309
|
+
* return HttpServerResponse.text(value ?? "not found");
|
|
310
|
+
* }),
|
|
311
|
+
* };
|
|
312
|
+
* })
|
|
313
|
+
* ```
|
|
314
|
+
*
|
|
315
|
+
* There are three ways to define a Worker, from simplest to most
|
|
316
|
+
* flexible. See the {@link https://alchemy.run/concepts/platform | Platform concept}
|
|
317
|
+
* page for the full explanation.
|
|
318
|
+
*
|
|
319
|
+
* - **Async** — plain `async fetch` handler, no Effect runtime in the bundle.
|
|
320
|
+
* - **Effect** — Effect implementation passed directly, single file.
|
|
321
|
+
* - **Layer** — class and `.make()` in a single file; Rolldown tree-shakes `.make()` from consumers.
|
|
322
|
+
*
|
|
323
|
+
* @section Async Workers
|
|
324
|
+
* You don't have to use Effect for your runtime code. If you create
|
|
325
|
+
* a Worker resource with `main` pointing at a file but provide no
|
|
326
|
+
* `Effect.gen` implementation, Alchemy bundles and deploys that file
|
|
327
|
+
* as-is. Your handler is a plain `async fetch` — no Effect runtime
|
|
328
|
+
* is included in the bundle.
|
|
329
|
+
*
|
|
330
|
+
* Use the `bindings` prop to declare which resources are available
|
|
331
|
+
* at runtime, and `Cloudflare.InferEnv` to extract a fully typed
|
|
332
|
+
* `env` object from those bindings.
|
|
333
|
+
*
|
|
334
|
+
* See the {@link https://alchemy.run/guides/async-worker | Async Workers Guide}
|
|
335
|
+
* for a comprehensive walkthrough of all binding types (R2, D1,
|
|
336
|
+
* Durable Objects, Assets, and more).
|
|
337
|
+
*
|
|
338
|
+
* @example Defining an async Worker in your stack
|
|
339
|
+
* ```typescript
|
|
340
|
+
* // alchemy.run.ts
|
|
341
|
+
* const db = yield* Cloudflare.D1Database("DB");
|
|
342
|
+
* const bucket = yield* Cloudflare.R2Bucket("Bucket");
|
|
343
|
+
*
|
|
344
|
+
* export type WorkerEnv = Cloudflare.InferEnv<typeof Worker>;
|
|
345
|
+
*
|
|
346
|
+
* export const Worker = Cloudflare.Worker("Worker", {
|
|
267
347
|
* main: "./src/worker.ts",
|
|
348
|
+
* bindings: { db, bucket },
|
|
349
|
+
* });
|
|
350
|
+
* ```
|
|
351
|
+
*
|
|
352
|
+
* @example Writing the async handler
|
|
353
|
+
* ```typescript
|
|
354
|
+
* // src/worker.ts
|
|
355
|
+
* import type { WorkerEnv } from "../alchemy.run.ts";
|
|
356
|
+
*
|
|
357
|
+
* export default {
|
|
358
|
+
* async fetch(request: Request, env: WorkerEnv) {
|
|
359
|
+
* if (request.method === "GET") {
|
|
360
|
+
* const object = await env.bucket.get("key");
|
|
361
|
+
* return new Response(object?.body ?? null);
|
|
362
|
+
* }
|
|
363
|
+
* return new Response("Not Found", { status: 404 });
|
|
364
|
+
* },
|
|
365
|
+
* };
|
|
366
|
+
* ```
|
|
367
|
+
*
|
|
368
|
+
* @section Effect Workers
|
|
369
|
+
* Pass the Effect implementation as the third argument. This is the
|
|
370
|
+
* simplest Effect-based approach — everything lives in one file.
|
|
371
|
+
* Convenient for standalone Workers that don't need to be referenced
|
|
372
|
+
* by other Workers.
|
|
373
|
+
*
|
|
374
|
+
* @example Worker Effect
|
|
375
|
+
* ```typescript
|
|
376
|
+
* export default class MyWorker extends Cloudflare.Worker<MyWorker>()(
|
|
377
|
+
* "MyWorker",
|
|
378
|
+
* { main: import.meta.path },
|
|
379
|
+
* Effect.gen(function* () {
|
|
380
|
+
* // init: bind resources
|
|
381
|
+
* const kv = yield* Cloudflare.KVNamespace.bind(MyKV);
|
|
382
|
+
*
|
|
383
|
+
* return {
|
|
384
|
+
* // runtime: use them
|
|
385
|
+
* fetch: Effect.gen(function* () {
|
|
386
|
+
* const value = yield* kv.get("key");
|
|
387
|
+
* return HttpServerResponse.text(value ?? "not found");
|
|
388
|
+
* }),
|
|
389
|
+
* };
|
|
390
|
+
* }),
|
|
391
|
+
* ) {}
|
|
392
|
+
* ```
|
|
393
|
+
*
|
|
394
|
+
* @section Worker Layer
|
|
395
|
+
* When two Workers need to reference each other (e.g. WorkerA calls
|
|
396
|
+
* WorkerB and vice versa), or you simply want optimal tree-shaking,
|
|
397
|
+
* define the Worker class separately from its `.make()` call. The
|
|
398
|
+
* class is a lightweight identifier; `.make()` provides the runtime
|
|
399
|
+
* implementation as an `export default`. Rolldown treats `.make()`
|
|
400
|
+
* as pure, so any Worker that imports the class to bind it will not
|
|
401
|
+
* pull in the `.make()` dependencies — the bundler tree-shakes
|
|
402
|
+
* them away entirely.
|
|
403
|
+
*
|
|
404
|
+
* The class and `.make()` can live in the same file. This is the
|
|
405
|
+
* same pattern used by `Container` and `DurableObjectNamespace`,
|
|
406
|
+
* and is recommended for any cross-Worker or cross-DO bindings.
|
|
407
|
+
*
|
|
408
|
+
* @example Worker Layer (class + .make() in one file)
|
|
409
|
+
* ```typescript
|
|
410
|
+
* // src/WorkerB.ts
|
|
411
|
+
* export default class WorkerB extends Cloudflare.Worker<WorkerB>()(
|
|
412
|
+
* "WorkerB",
|
|
413
|
+
* { main: import.meta.path },
|
|
414
|
+
* ) {}
|
|
415
|
+
*
|
|
416
|
+
* export default WorkerB.make(
|
|
417
|
+
* Effect.gen(function* () {
|
|
418
|
+
* // init: bind resources
|
|
419
|
+
* const kv = yield* Cloudflare.KVNamespace.bind(MyKV);
|
|
420
|
+
*
|
|
421
|
+
* return {
|
|
422
|
+
* // runtime: use them
|
|
423
|
+
* greet: (name: string) =>
|
|
424
|
+
* Effect.gen(function* () {
|
|
425
|
+
* yield* kv.put("last-greeted", name);
|
|
426
|
+
* return `Hello ${name}`;
|
|
427
|
+
* }),
|
|
428
|
+
* };
|
|
429
|
+
* }),
|
|
430
|
+
* );
|
|
431
|
+
* ```
|
|
432
|
+
*
|
|
433
|
+
* @example Binding a Worker Layer from another Worker
|
|
434
|
+
* ```typescript
|
|
435
|
+
* // src/WorkerA.ts — imports WorkerB; bundler tree-shakes .make()
|
|
436
|
+
* import WorkerB from "./WorkerB.ts";
|
|
437
|
+
*
|
|
438
|
+
* export default class WorkerA extends Cloudflare.Worker<WorkerA>()(
|
|
439
|
+
* "WorkerA",
|
|
440
|
+
* { main: import.meta.path },
|
|
441
|
+
* Effect.gen(function* () {
|
|
442
|
+
* const b = yield* Cloudflare.Worker.bind(WorkerB);
|
|
443
|
+
* return {
|
|
444
|
+
* fetch: Effect.gen(function* () {
|
|
445
|
+
* return yield* b.greet("world");
|
|
446
|
+
* }),
|
|
447
|
+
* };
|
|
448
|
+
* }),
|
|
449
|
+
* ) {}
|
|
450
|
+
* ```
|
|
451
|
+
*
|
|
452
|
+
* @section Configuration
|
|
453
|
+
* The props object controls compatibility flags, static assets, and
|
|
454
|
+
* build options. These are evaluated at deploy time.
|
|
455
|
+
*
|
|
456
|
+
* @example Enabling Node.js compatibility
|
|
457
|
+
* ```typescript
|
|
458
|
+
* {
|
|
459
|
+
* main: import.meta.path,
|
|
460
|
+
* compatibility: {
|
|
461
|
+
* flags: ["nodejs_compat"],
|
|
462
|
+
* date: "2026-03-17",
|
|
463
|
+
* },
|
|
464
|
+
* }
|
|
465
|
+
* ```
|
|
466
|
+
*
|
|
467
|
+
* @example Serving static assets
|
|
468
|
+
* ```typescript
|
|
469
|
+
* {
|
|
470
|
+
* main: import.meta.path,
|
|
471
|
+
* assets: "./public",
|
|
472
|
+
* }
|
|
473
|
+
* ```
|
|
474
|
+
*
|
|
475
|
+
* @section R2 Bucket
|
|
476
|
+
* Bind an R2 bucket in the init phase with `Cloudflare.R2Bucket.bind`.
|
|
477
|
+
* The returned handle exposes `get`, `put`, `delete`, and `list`
|
|
478
|
+
* methods you can call in your runtime handlers.
|
|
479
|
+
*
|
|
480
|
+
* @example Binding and using R2
|
|
481
|
+
* ```typescript
|
|
482
|
+
* // init
|
|
483
|
+
* const bucket = yield* Cloudflare.R2Bucket.bind(MyBucket);
|
|
484
|
+
*
|
|
485
|
+
* return {
|
|
486
|
+
* fetch: Effect.gen(function* () {
|
|
487
|
+
* const request = yield* HttpServerRequest;
|
|
488
|
+
* const key = request.url.split("/").pop()!;
|
|
489
|
+
*
|
|
490
|
+
* if (request.method === "GET") {
|
|
491
|
+
* const object = yield* bucket.get(key);
|
|
492
|
+
* return object
|
|
493
|
+
* ? HttpServerResponse.text(yield* object.text())
|
|
494
|
+
* : HttpServerResponse.empty({ status: 404 });
|
|
495
|
+
* }
|
|
496
|
+
*
|
|
497
|
+
* yield* bucket.put(key, request.stream);
|
|
498
|
+
* return HttpServerResponse.empty({ status: 201 });
|
|
499
|
+
* }),
|
|
500
|
+
* };
|
|
501
|
+
* ```
|
|
502
|
+
*
|
|
503
|
+
* @section KV Namespace
|
|
504
|
+
* Bind a KV namespace with `Cloudflare.KVNamespace.bind`. KV provides
|
|
505
|
+
* eventually-consistent, low-latency key-value reads replicated
|
|
506
|
+
* globally across Cloudflare's edge.
|
|
507
|
+
*
|
|
508
|
+
* @example Binding and using KV
|
|
509
|
+
* ```typescript
|
|
510
|
+
* // init
|
|
511
|
+
* const kv = yield* Cloudflare.KVNamespace.bind(MyKV);
|
|
512
|
+
*
|
|
513
|
+
* return {
|
|
514
|
+
* fetch: Effect.gen(function* () {
|
|
515
|
+
* const value = yield* kv.get("my-key");
|
|
516
|
+
* return HttpServerResponse.text(value ?? "not found");
|
|
517
|
+
* }),
|
|
518
|
+
* };
|
|
519
|
+
* ```
|
|
520
|
+
*
|
|
521
|
+
* @section D1 Database
|
|
522
|
+
* Bind a D1 database with `Cloudflare.D1Connection.bind`. D1 is a
|
|
523
|
+
* serverless SQLite database — use `prepare` to build parameterized
|
|
524
|
+
* queries and `all`, `first`, or `run` to execute them.
|
|
525
|
+
*
|
|
526
|
+
* @example Binding and querying D1
|
|
527
|
+
* ```typescript
|
|
528
|
+
* // init
|
|
529
|
+
* const db = yield* Cloudflare.D1Connection.bind(MyDB);
|
|
530
|
+
*
|
|
531
|
+
* return {
|
|
532
|
+
* fetch: Effect.gen(function* () {
|
|
533
|
+
* const results = yield* db
|
|
534
|
+
* .prepare("SELECT * FROM users WHERE id = ?")
|
|
535
|
+
* .bind(userId)
|
|
536
|
+
* .all();
|
|
537
|
+
* return yield* HttpServerResponse.json(results);
|
|
538
|
+
* }),
|
|
539
|
+
* };
|
|
540
|
+
* ```
|
|
541
|
+
*
|
|
542
|
+
* @section Durable Objects
|
|
543
|
+
* Yield a `DurableObjectNamespace` class in the init phase to get a
|
|
544
|
+
* namespace handle. Call `getByName` or `getById` to get a typed RPC
|
|
545
|
+
* stub, then call its methods from your runtime handlers.
|
|
546
|
+
*
|
|
547
|
+
* @example Using a Durable Object
|
|
548
|
+
* ```typescript
|
|
549
|
+
* // init
|
|
550
|
+
* const counters = yield* Counter;
|
|
551
|
+
*
|
|
552
|
+
* return {
|
|
553
|
+
* fetch: Effect.gen(function* () {
|
|
554
|
+
* const counter = counters.getByName("user-123");
|
|
555
|
+
* const value = yield* counter.increment();
|
|
556
|
+
* return HttpServerResponse.text(String(value));
|
|
557
|
+
* }),
|
|
558
|
+
* };
|
|
559
|
+
* ```
|
|
560
|
+
*
|
|
561
|
+
* @section Containers
|
|
562
|
+
* Containers run long-lived processes alongside Durable Objects. Bind
|
|
563
|
+
* one with `Cloudflare.Container.bind` and start it with
|
|
564
|
+
* `Cloudflare.start`. You can call typed methods on the running
|
|
565
|
+
* container or make HTTP requests to its exposed ports.
|
|
566
|
+
*
|
|
567
|
+
* @example Binding and starting a Container
|
|
568
|
+
* ```typescript
|
|
569
|
+
* // init (inside a DurableObjectNamespace)
|
|
570
|
+
* const sandbox = yield* Cloudflare.Container.bind(Sandbox);
|
|
571
|
+
*
|
|
572
|
+
* return Effect.gen(function* () {
|
|
573
|
+
* const container = yield* Cloudflare.start(sandbox);
|
|
574
|
+
*
|
|
575
|
+
* return {
|
|
576
|
+
* exec: (cmd: string) => container.exec(cmd),
|
|
577
|
+
* fetch: Effect.gen(function* () {
|
|
578
|
+
* const { fetch } = yield* container.getTcpPort(3000);
|
|
579
|
+
* const res = yield* fetch(HttpClientRequest.get("http://container/"));
|
|
580
|
+
* return HttpServerResponse.fromClientResponse(res);
|
|
581
|
+
* }),
|
|
582
|
+
* };
|
|
268
583
|
* });
|
|
269
584
|
* ```
|
|
585
|
+
*
|
|
586
|
+
* @section Dynamic Workers
|
|
587
|
+
* `DynamicWorkerLoader` lets you spin up ephemeral Workers at runtime
|
|
588
|
+
* from inline JavaScript modules. This is useful for sandboxing
|
|
589
|
+
* user-provided code or running untrusted scripts in isolation.
|
|
590
|
+
*
|
|
591
|
+
* @example Loading a dynamic Worker
|
|
592
|
+
* ```typescript
|
|
593
|
+
* // init
|
|
594
|
+
* const loader = yield* Cloudflare.DynamicWorkerLoader("Loader");
|
|
595
|
+
*
|
|
596
|
+
* return {
|
|
597
|
+
* fetch: Effect.gen(function* () {
|
|
598
|
+
* const worker = loader.load({
|
|
599
|
+
* compatibilityDate: "2026-01-28",
|
|
600
|
+
* mainModule: "worker.js",
|
|
601
|
+
* modules: {
|
|
602
|
+
* "worker.js": `export default {
|
|
603
|
+
* async fetch(req) { return new Response("sandboxed"); }
|
|
604
|
+
* }`,
|
|
605
|
+
* },
|
|
606
|
+
* });
|
|
607
|
+
*
|
|
608
|
+
* const res = yield* worker.fetch(
|
|
609
|
+
* HttpClientRequest.get("https://worker/"),
|
|
610
|
+
* );
|
|
611
|
+
* return HttpServerResponse.fromClientResponse(res);
|
|
612
|
+
* }),
|
|
613
|
+
* };
|
|
614
|
+
* ```
|
|
270
615
|
*/
|
|
271
616
|
export const Worker: Platform<
|
|
272
617
|
Worker,
|
|
@@ -274,19 +619,24 @@ export const Worker: Platform<
|
|
|
274
619
|
WorkerShape,
|
|
275
620
|
WorkerExecutionContext
|
|
276
621
|
> & {
|
|
277
|
-
<
|
|
622
|
+
<
|
|
623
|
+
const Bindings extends WorkerBindingProps,
|
|
624
|
+
const Assets extends WorkerAssetsConfig | undefined = undefined,
|
|
625
|
+
Req = never,
|
|
626
|
+
>(
|
|
278
627
|
id: string,
|
|
279
|
-
props:
|
|
628
|
+
props:
|
|
629
|
+
| InputProps<WorkerProps<Bindings, Assets>>
|
|
630
|
+
| Effect.Effect<InputProps<WorkerProps<Bindings, Assets>>, never, Req>,
|
|
280
631
|
): Effect.Effect<
|
|
281
632
|
Worker<{
|
|
282
|
-
[
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
}>
|
|
633
|
+
[binding in keyof NormalizedBindings<
|
|
634
|
+
Bindings,
|
|
635
|
+
Assets
|
|
636
|
+
>]: NormalizedBindings<Bindings, Assets>[binding];
|
|
637
|
+
}>,
|
|
638
|
+
never,
|
|
639
|
+
Req | Providers
|
|
290
640
|
>;
|
|
291
641
|
} = Platform(WorkerTypeId, {
|
|
292
642
|
onCreate: Effect.fnUntraced(function* (
|
|
@@ -303,31 +653,45 @@ export const Worker: Platform<
|
|
|
303
653
|
? yield* bindingEff
|
|
304
654
|
: bindingEff;
|
|
305
655
|
|
|
306
|
-
const bindingMeta: InputProps<WorkerBinding> | undefined =
|
|
307
|
-
binding
|
|
656
|
+
const bindingMeta: InputProps<WorkerBinding> | undefined = isAssets(
|
|
657
|
+
binding,
|
|
658
|
+
)
|
|
659
|
+
? {
|
|
660
|
+
type: "assets",
|
|
661
|
+
name: bindingName,
|
|
662
|
+
}
|
|
663
|
+
: isDurableObjectNamespaceLike(binding)
|
|
308
664
|
? {
|
|
309
|
-
type: "
|
|
310
|
-
id: binding.databaseId,
|
|
665
|
+
type: "durable_object_namespace",
|
|
311
666
|
name: bindingName,
|
|
667
|
+
className: binding.className ?? binding.name,
|
|
312
668
|
}
|
|
313
|
-
: binding.Type === "Cloudflare.
|
|
669
|
+
: binding.Type === "Cloudflare.D1Database"
|
|
314
670
|
? {
|
|
315
|
-
type: "
|
|
671
|
+
type: "d1",
|
|
672
|
+
id: binding.databaseId,
|
|
316
673
|
name: bindingName,
|
|
317
|
-
bucketName: binding.bucketName,
|
|
318
|
-
jurisdiction: binding.jurisdiction.pipe(
|
|
319
|
-
Output.map((jurisdiction) =>
|
|
320
|
-
jurisdiction === "default" ? undefined : jurisdiction,
|
|
321
|
-
),
|
|
322
|
-
),
|
|
323
674
|
}
|
|
324
|
-
:
|
|
325
|
-
|
|
675
|
+
: binding.Type === "Cloudflare.R2Bucket"
|
|
676
|
+
? {
|
|
677
|
+
type: "r2_bucket",
|
|
678
|
+
name: bindingName,
|
|
679
|
+
bucketName: binding.bucketName,
|
|
680
|
+
jurisdiction: binding.jurisdiction.pipe(
|
|
681
|
+
Output.map((jurisdiction) =>
|
|
682
|
+
jurisdiction === "default" ? undefined : jurisdiction,
|
|
683
|
+
),
|
|
684
|
+
),
|
|
685
|
+
}
|
|
686
|
+
: // TODO(sam): handle others
|
|
687
|
+
undefined;
|
|
326
688
|
|
|
327
689
|
if (bindingMeta) {
|
|
328
690
|
yield* resource.bind`${bindingName}`({
|
|
329
691
|
bindings: [bindingMeta],
|
|
330
692
|
});
|
|
693
|
+
} else {
|
|
694
|
+
return yield* Effect.die(`Unknown binding type: ${bindingName}`);
|
|
331
695
|
}
|
|
332
696
|
}
|
|
333
697
|
}
|
|
@@ -552,7 +916,6 @@ export const WorkerProvider = () =>
|
|
|
552
916
|
const virtualEntryPlugin = yield* Bundle.virtualEntryPlugin;
|
|
553
917
|
const stack = yield* Stack;
|
|
554
918
|
|
|
555
|
-
const { read, upload } = yield* Assets.Assets;
|
|
556
919
|
const createScriptSubdomain = yield* workers.createScriptSubdomain;
|
|
557
920
|
const createScriptTail = yield* workers.createScriptTail;
|
|
558
921
|
const deleteScript = yield* workers.deleteScript;
|
|
@@ -562,6 +925,10 @@ export const WorkerProvider = () =>
|
|
|
562
925
|
const getSubdomain = yield* workers.getSubdomain;
|
|
563
926
|
const listScripts = yield* workers.listScripts;
|
|
564
927
|
const putScript = yield* workers.putScript;
|
|
928
|
+
const putDomain = yield* workers.putDomain;
|
|
929
|
+
const listDomains = yield* workers.listDomains;
|
|
930
|
+
const deleteDomain = yield* workers.deleteDomain;
|
|
931
|
+
const listZones = yield* zones.listZones;
|
|
565
932
|
const telemetry = yield* CloudflareLogs;
|
|
566
933
|
const defaultCompatibilityDate = yield* Effect.promise(() =>
|
|
567
934
|
// @ts-expect-error no types for workerd
|
|
@@ -588,6 +955,95 @@ export const WorkerProvider = () =>
|
|
|
588
955
|
maxLength: 54,
|
|
589
956
|
}).pipe(Effect.map((name) => name.toLowerCase()));
|
|
590
957
|
|
|
958
|
+
const normalizeDomains = (
|
|
959
|
+
domain: string | string[] | undefined,
|
|
960
|
+
): string[] =>
|
|
961
|
+
domain === undefined
|
|
962
|
+
? []
|
|
963
|
+
: Array.isArray(domain)
|
|
964
|
+
? Array.from(new Set(domain))
|
|
965
|
+
: [domain];
|
|
966
|
+
|
|
967
|
+
/**
|
|
968
|
+
* Infer the Cloudflare Zone ID for a given hostname by listing the
|
|
969
|
+
* account's zones and matching the hostname against each zone's name —
|
|
970
|
+
* walking up the DNS label hierarchy until a match is found.
|
|
971
|
+
*/
|
|
972
|
+
const inferZoneIdForHostname = (
|
|
973
|
+
hostname: string,
|
|
974
|
+
zoneCache: Map<string, string>,
|
|
975
|
+
) =>
|
|
976
|
+
Effect.gen(function* () {
|
|
977
|
+
const cached = zoneCache.get(hostname);
|
|
978
|
+
if (cached) return cached;
|
|
979
|
+
|
|
980
|
+
const zoneList = yield* listZones({}).pipe(
|
|
981
|
+
Effect.map((response) => response.result ?? []),
|
|
982
|
+
);
|
|
983
|
+
for (const zone of zoneList) {
|
|
984
|
+
zoneCache.set(zone.name, zone.id);
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
const parts = hostname.split(".");
|
|
988
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
989
|
+
const candidate = parts.slice(i).join(".");
|
|
990
|
+
const match = zoneList.find((z) => z.name === candidate);
|
|
991
|
+
if (match) {
|
|
992
|
+
zoneCache.set(hostname, match.id);
|
|
993
|
+
return match.id;
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
return yield* Effect.die(
|
|
997
|
+
`Could not infer Cloudflare Zone for hostname "${hostname}". ` +
|
|
998
|
+
"Ensure the parent zone exists in this account.",
|
|
999
|
+
);
|
|
1000
|
+
});
|
|
1001
|
+
|
|
1002
|
+
const reconcileDomains = (
|
|
1003
|
+
scriptName: string,
|
|
1004
|
+
desired: string[],
|
|
1005
|
+
previous: Worker["Attributes"]["domains"],
|
|
1006
|
+
) =>
|
|
1007
|
+
Effect.gen(function* () {
|
|
1008
|
+
const desiredSet = new Set(desired);
|
|
1009
|
+
const toRemove = previous.filter((p) => !desiredSet.has(p.hostname));
|
|
1010
|
+
yield* Effect.all(
|
|
1011
|
+
toRemove.map((d) =>
|
|
1012
|
+
deleteDomain({ accountId, domainId: d.id }).pipe(
|
|
1013
|
+
Effect.catchTag("DomainNotFound", () => Effect.void),
|
|
1014
|
+
),
|
|
1015
|
+
),
|
|
1016
|
+
{ concurrency: "unbounded" },
|
|
1017
|
+
);
|
|
1018
|
+
|
|
1019
|
+
if (desired.length === 0) return [];
|
|
1020
|
+
|
|
1021
|
+
const zoneCache = new Map<string, string>();
|
|
1022
|
+
const applied = yield* Effect.all(
|
|
1023
|
+
desired.map((hostname) =>
|
|
1024
|
+
Effect.gen(function* () {
|
|
1025
|
+
const zoneId = yield* inferZoneIdForHostname(
|
|
1026
|
+
hostname,
|
|
1027
|
+
zoneCache,
|
|
1028
|
+
);
|
|
1029
|
+
const res = yield* putDomain({
|
|
1030
|
+
accountId,
|
|
1031
|
+
hostname,
|
|
1032
|
+
service: scriptName,
|
|
1033
|
+
zoneId,
|
|
1034
|
+
});
|
|
1035
|
+
return {
|
|
1036
|
+
hostname,
|
|
1037
|
+
id: res.id ?? "",
|
|
1038
|
+
zoneId: res.zoneId ?? zoneId,
|
|
1039
|
+
};
|
|
1040
|
+
}),
|
|
1041
|
+
),
|
|
1042
|
+
{ concurrency: "unbounded" },
|
|
1043
|
+
);
|
|
1044
|
+
return applied;
|
|
1045
|
+
});
|
|
1046
|
+
|
|
591
1047
|
const createAlchemyWorkerTags = (id: string) => [
|
|
592
1048
|
`alchemy:stack:${stack.name}`,
|
|
593
1049
|
`alchemy:stage:${stack.stage}`,
|
|
@@ -669,7 +1125,9 @@ export const WorkerProvider = () =>
|
|
|
669
1125
|
const prepareAssets = Effect.fnUntraced(function* (
|
|
670
1126
|
assets: WorkerProps["assets"],
|
|
671
1127
|
) {
|
|
672
|
-
if (!assets)
|
|
1128
|
+
if (!assets) {
|
|
1129
|
+
return undefined;
|
|
1130
|
+
}
|
|
673
1131
|
|
|
674
1132
|
// Handle AssetsWithHash (from Build resource)
|
|
675
1133
|
// Props are resolved by Plan, so Input<string> values are already strings at runtime
|
|
@@ -678,7 +1136,7 @@ export const WorkerProvider = () =>
|
|
|
678
1136
|
"path" in assets &&
|
|
679
1137
|
"hash" in assets
|
|
680
1138
|
) {
|
|
681
|
-
const result = yield*
|
|
1139
|
+
const result = yield* readAssets({
|
|
682
1140
|
directory: assets.path as string,
|
|
683
1141
|
config: assets.config,
|
|
684
1142
|
});
|
|
@@ -689,7 +1147,7 @@ export const WorkerProvider = () =>
|
|
|
689
1147
|
}
|
|
690
1148
|
|
|
691
1149
|
// Handle string path or AssetsProps
|
|
692
|
-
return yield*
|
|
1150
|
+
return yield* readAssets(
|
|
693
1151
|
typeof assets === "string" ? { directory: assets } : assets,
|
|
694
1152
|
);
|
|
695
1153
|
});
|
|
@@ -760,8 +1218,8 @@ import * as Stream from "effect/Stream";
|
|
|
760
1218
|
import { env, DurableObject${hasWfClasses ? ", WorkflowEntrypoint" : ""} } from "cloudflare:workers";
|
|
761
1219
|
import { MinimumLogLevel } from "effect/References";
|
|
762
1220
|
import { NodeServices } from "@effect/platform-node";
|
|
763
|
-
import { Stack } from "alchemy
|
|
764
|
-
import { WorkerEnvironment, makeDurableObjectBridge${hasWfClasses ? ", makeWorkflowBridge" : ""}, ExportedHandlerMethods } from "alchemy
|
|
1221
|
+
import { Stack } from "alchemy/Stack";
|
|
1222
|
+
import { WorkerEnvironment, makeDurableObjectBridge${hasWfClasses ? ", makeWorkflowBridge" : ""}, ExportedHandlerMethods } from "alchemy/Cloudflare";
|
|
765
1223
|
|
|
766
1224
|
import entry from "${importPath}";
|
|
767
1225
|
|
|
@@ -927,7 +1385,7 @@ ${[
|
|
|
927
1385
|
const [assets, bundle] = yield* Effect.all(
|
|
928
1386
|
[
|
|
929
1387
|
assetsDirectory
|
|
930
|
-
?
|
|
1388
|
+
? readAssets({
|
|
931
1389
|
directory: assetsDirectory,
|
|
932
1390
|
config:
|
|
933
1391
|
typeof props.assets === "object" && "config" in props.assets
|
|
@@ -1007,7 +1465,12 @@ ${[
|
|
|
1007
1465
|
yield* Effect.logInfo(
|
|
1008
1466
|
`Cloudflare Worker ${olds ? "update" : "create"}: uploading assets for ${name}`,
|
|
1009
1467
|
);
|
|
1010
|
-
const { jwt } = yield*
|
|
1468
|
+
const { jwt } = yield* uploadAssets(
|
|
1469
|
+
accountId,
|
|
1470
|
+
name,
|
|
1471
|
+
assets,
|
|
1472
|
+
session,
|
|
1473
|
+
);
|
|
1011
1474
|
metadataAssets = {
|
|
1012
1475
|
jwt,
|
|
1013
1476
|
config: assets.config,
|
|
@@ -1281,6 +1744,18 @@ ${[
|
|
|
1281
1744
|
);
|
|
1282
1745
|
yield* setWorkerSubdomain(name, enable);
|
|
1283
1746
|
}
|
|
1747
|
+
const desiredDomains = normalizeDomains(news.domain);
|
|
1748
|
+
const previousDomains = output?.domains ?? [];
|
|
1749
|
+
if (desiredDomains.length > 0 || previousDomains.length > 0) {
|
|
1750
|
+
yield* session.note(
|
|
1751
|
+
`Reconciling custom domains (${desiredDomains.length}) ...`,
|
|
1752
|
+
);
|
|
1753
|
+
}
|
|
1754
|
+
const domains = yield* reconcileDomains(
|
|
1755
|
+
name,
|
|
1756
|
+
desiredDomains,
|
|
1757
|
+
previousDomains,
|
|
1758
|
+
);
|
|
1284
1759
|
return {
|
|
1285
1760
|
workerId: worker.id ?? name,
|
|
1286
1761
|
workerName: name,
|
|
@@ -1292,6 +1767,7 @@ ${[
|
|
|
1292
1767
|
tags: settings.tags ?? metadata.tags,
|
|
1293
1768
|
durableObjectNamespaces,
|
|
1294
1769
|
accountId,
|
|
1770
|
+
domains,
|
|
1295
1771
|
hash,
|
|
1296
1772
|
} satisfies Worker["Attributes"];
|
|
1297
1773
|
});
|
|
@@ -1337,7 +1813,14 @@ ${[
|
|
|
1337
1813
|
if (!output) {
|
|
1338
1814
|
return;
|
|
1339
1815
|
}
|
|
1340
|
-
|
|
1816
|
+
const newDomains = normalizeDomains(news.domain).sort();
|
|
1817
|
+
const oldDomains = (output?.domains ?? [])
|
|
1818
|
+
.map((d) => d.hostname)
|
|
1819
|
+
.sort();
|
|
1820
|
+
const domainsChanged =
|
|
1821
|
+
newDomains.length !== oldDomains.length ||
|
|
1822
|
+
newDomains.some((d, i) => d !== oldDomains[i]);
|
|
1823
|
+
if (domainsChanged || (yield* hasChanged(id, news, output))) {
|
|
1341
1824
|
return {
|
|
1342
1825
|
action: "update",
|
|
1343
1826
|
stables:
|
|
@@ -1466,6 +1949,7 @@ ${[
|
|
|
1466
1949
|
tags: existingSettings?.tags ?? tags,
|
|
1467
1950
|
durableObjectNamespaces,
|
|
1468
1951
|
accountId,
|
|
1952
|
+
domains: [],
|
|
1469
1953
|
} satisfies Worker["Attributes"];
|
|
1470
1954
|
}),
|
|
1471
1955
|
read: Effect.fnUntraced(
|
|
@@ -1475,23 +1959,28 @@ ${[
|
|
|
1475
1959
|
yield* Effect.logInfo(
|
|
1476
1960
|
`Cloudflare Worker read: checking ${workerName}`,
|
|
1477
1961
|
);
|
|
1478
|
-
const [worker, subdomain, settings] =
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1962
|
+
const [worker, subdomain, settings, domainsList] =
|
|
1963
|
+
yield* Effect.all([
|
|
1964
|
+
listScripts({
|
|
1965
|
+
accountId,
|
|
1966
|
+
}).pipe(
|
|
1967
|
+
Effect.map((workers) =>
|
|
1968
|
+
workers.result.find((worker) => worker.id === workerName),
|
|
1969
|
+
),
|
|
1484
1970
|
),
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1971
|
+
getScriptSubdomain({
|
|
1972
|
+
accountId,
|
|
1973
|
+
scriptName: workerName,
|
|
1974
|
+
}),
|
|
1975
|
+
getScriptSettings({
|
|
1976
|
+
accountId,
|
|
1977
|
+
scriptName: workerName,
|
|
1978
|
+
}),
|
|
1979
|
+
listDomains({
|
|
1980
|
+
accountId,
|
|
1981
|
+
service: workerName,
|
|
1982
|
+
}).pipe(Effect.map((r) => r.result ?? [])),
|
|
1983
|
+
]);
|
|
1495
1984
|
if (!worker) {
|
|
1496
1985
|
yield* Effect.logInfo(
|
|
1497
1986
|
`Cloudflare Worker read: ${workerName} not found in script list`,
|
|
@@ -1513,6 +2002,11 @@ ${[
|
|
|
1513
2002
|
durableObjectNamespaces: getDurableObjectNamespaces(
|
|
1514
2003
|
settings.bindings,
|
|
1515
2004
|
),
|
|
2005
|
+
domains: domainsList.flatMap((d) =>
|
|
2006
|
+
d.id && d.hostname && d.zoneId
|
|
2007
|
+
? [{ id: d.id, hostname: d.hostname, zoneId: d.zoneId }]
|
|
2008
|
+
: [],
|
|
2009
|
+
),
|
|
1516
2010
|
} satisfies Worker["Attributes"];
|
|
1517
2011
|
},
|
|
1518
2012
|
(effect) =>
|
|
@@ -1614,6 +2108,17 @@ ${[
|
|
|
1614
2108
|
yield* Effect.logInfo(
|
|
1615
2109
|
`Cloudflare Worker delete: deleting ${output.workerName}`,
|
|
1616
2110
|
);
|
|
2111
|
+
if (output.domains?.length) {
|
|
2112
|
+
yield* Effect.all(
|
|
2113
|
+
output.domains.map((d) =>
|
|
2114
|
+
deleteDomain({
|
|
2115
|
+
accountId: output.accountId,
|
|
2116
|
+
domainId: d.id,
|
|
2117
|
+
}).pipe(Effect.catchTag("DomainNotFound", () => Effect.void)),
|
|
2118
|
+
),
|
|
2119
|
+
{ concurrency: "unbounded" },
|
|
2120
|
+
);
|
|
2121
|
+
}
|
|
1617
2122
|
yield* deleteScript({
|
|
1618
2123
|
accountId: output.accountId,
|
|
1619
2124
|
scriptName: output.workerName,
|