1import type * as cf from "@cloudflare/workers-types";
2import * as workers from "@distilled.cloud/cloudflare/workers";
3import type { ConfigError } from "effect/Config";
4import * as Effect from "effect/Effect";
5import type * as Layer from "effect/Layer";
6import * as Redacted from "effect/Redacted";
7import { type MemoOptions } from "../../Command/Memo.ts";
8import type { Dependencies } from "../../Dependencies.ts";
9import type { InputProps } from "../../Input.ts";
10import type { Named, Tag } from "../../Named.ts";
11import type * as Output from "../../Output.ts";
12import {
13 Platform,
14 type Main,
15 type MainRpc,
16 type MakeShape,
17 type PlatformIdentity,
18 type PlatformProps,
19 type PlatformServices,
20} from "../../Platform.ts";
21import {
22 isResourceOfType,
23 Resource,
24 type ResourceClass,
25 type ResourceClassLike,
26} from "../../Resource.ts";
27import type { Rpc } from "../../Rpc.ts";
28import type { RuntimeContext } from "../../RuntimeContext.ts";
29import type { Self as SelfService } from "../../Self.ts";
30import { CloudflareEnvironment } from "../CloudflareEnvironment.ts";
31import type { Container } from "../Containers/Container.ts";
32import type { DevContainerImage } from "../Containers/ContainerApplication.ts";
33import type { DevOrigin } from "../Hyperdrive/Connection.ts";
34import type { Providers } from "../Providers.ts";
35import type { DispatchNamespace } from "../WorkersForPlatforms/DispatchNamespace.ts";
36import type { WorkflowBinding, WorkflowLike } from "../Workflows/Workflow.ts";
37import type { Reference as ZoneReference } from "../Zone/lookup.ts";
38import { type Assets, type AssetsConfig, type AssetsProps } from "./Assets.ts";
39import type {
40 WorkerAccessConfig,
41 WorkerAccessIdentity,
42} from "./WorkerAccess.ts";
43import {
44 WorkerEnvironment,
45 WorkerExecutionContext,
46 WorkerTypeId,
47} from "./WorkerRuntime.ts";
48import { Request } from "./Request.ts";
49import type { ModuleRule } from "./Sources/Prebuilt.ts";
50import type { WorkerBuildOptions } from "./Sources/Rolldown.ts";
51import { bindWorkerAsyncBindings } from "./WorkerAsyncBindings.ts";
52import type {
53 WorkerBinding,
54 WorkerBindingResource,
55 WorkerBindings,
56} from "./WorkerBinding.ts";
57import {
58 makeWorkerRuntimeContext,
59 type WorkerExport,
60 type WorkerRuntimeContext,
61} from "./WorkerRuntimeContext.ts";
62
63export * from "./WorkerRuntime.ts";
64
65export const isWorker = <T>(value: T): value is T & Worker =>
66 isResourceOfType(value, WorkerTypeId);
67
68/**
69 * Assets configuration that includes a pre-computed hash.
70 * When hash is provided, it's used directly for diffing instead of computing from directory contents.
71 * This is useful when integrating with Build resources that produce a deterministic hash.
72 */
73export interface AssetsWithHash extends AssetsProps {
74 /**
75 * Pre-computed hash of the assets. When provided, this hash is used for diffing
76 * to determine if the worker needs to be redeployed.
77 */
78 hash: string;
79}
80
81export interface WorkerObservability extends Exclude<
82 workers.PutScriptRequest["metadata"]["observability"],
83 undefined
84> {}
85
86export interface WorkerLimits extends Exclude<
87 workers.PutScriptRequest["metadata"]["limits"],
88 undefined
89> {}
90
91export interface WorkerCache extends Exclude<
92 workers.PutScriptRequest["metadata"]["cacheOptions"],
93 undefined
94> {}
95
96export type WorkerPlacement = Exclude<
97 workers.PutScriptRequest["metadata"]["placement"],
98 undefined
99>;
100
101export type WorkerServices =
102 | Worker
103 | Request
104 | WorkerExecutionContext
105 | WorkerEnvironment
106 | CloudflareEnvironment
107 | Container.Application<any>
108 | SelfService;
109
110export type WorkerShape<Req = never> = Main<WorkerServices | Req> &
111 MainRpc<WorkerServices | Req>;
112
113export type WorkerEnv = Record<
114 string,
115 | string
116 | number
117 | boolean
118 | null
119 | readonly unknown[]
120 | { readonly [key: string]: unknown }
121 | Redacted.Redacted<string>
122>;
123
124export type WorkerBindingProps = {
125 [bindingName in string]:
126 | WorkerBindingResource
127 | Effect.Effect<WorkerBindingResource, any, any>;
128};
129
130export type NormalizedBindings<
131 Bindings extends WorkerBindingProps = {},
132 AssetsConfig extends WorkerAssetsConfig | undefined = undefined,
133> = {
134 // Containers are declarations and Outputs stay deferred at declaration time.
135 [B in keyof Bindings]: Bindings[B] extends
136 | Container.Decl.Any
137 | Output.Output<any, any>
138 ? Bindings[B]
139 : Bindings[B] extends Effect.Effect<
140 infer T extends WorkerBindingResource,
141 any,
142 any
143 >
144 ? T
145 : Extract<Bindings[B], WorkerBindingResource>;
146} & (undefined extends AssetsConfig ? {} : { ASSETS: Assets });
147
148/**
149 * An external Worker's declared `env` as exposed on its declaration
150 * (`worker.env`): every entry is the binding value as declared (with
151 * Effect-valued entries resolved), except a Workflow binding, which surfaces
152 * as a {@link WorkflowBinding} carrying the Workflow's physical name as an
153 * `Output` of the current deploy.
154 */
155export type WorkerEnvBindings<Bindings> = {
156 readonly [B in keyof Bindings]: Bindings[B] extends WorkflowLike<infer Params>
157 ? WorkflowBinding<Params>
158 : Bindings[B];
159};
160
161export type WorkerAssetsConfig =
162 | string
163 | (AssetsConfig & { directory?: never })
164 | AssetsProps
165 | AssetsWithHash;
166
167/**
168 * Fine-grained control over the Worker's `workers.dev` surface. The two
169 * toggles are independent on the Cloudflare API.
170 */
171export interface WorkersDevConfig {
172 /**
173 * Serve the Worker at its stable `workers.dev` URL
174 * (`https://<worker-name>.<account-subdomain>.workers.dev`).
175 * @default true
176 */
177 enabled?: boolean;
178 /**
179 * Enable version preview URLs — a distinct
180 * `https://<version-prefix>-<worker-name>.<account-subdomain>.workers.dev`
181 * URL per uploaded version, plus stable aliased preview URLs
182 * (`https://<alias>-...`) for versions uploaded with an alias.
183 *
184 * When previews are enabled but {@link enabled} is `false`, the current
185 * version's preview URL becomes the Worker's primary `url` output.
186 * @default true
187 */
188 previewsEnabled?: boolean;
189}
190
191/**
192 * The Worker's custom-domain configuration: one canonical hostname, plus
193 * optional aliases that also serve the Worker and redirect hostnames that
194 * 301 to the canonical name.
195 */
196export interface WorkerDomainConfig {
197 /**
198 * The canonical hostname (e.g. `"example.com"`). Attached to the Worker
199 * as a Cloudflare custom domain — DNS record and edge certificate are
200 * managed automatically. The Cloudflare zone is inferred from the
201 * hostname unless {@link zoneId}, {@link zone}, or {@link zoneName} is
202 * set. The zone must already exist in the account.
203 *
204 * When set, `https://<name>` is the Worker's primary `url` output.
205 */
206 name: string;
207 /**
208 * Additional hostnames that serve the Worker (e.g. `"www.example.com"`,
209 * `"api.example.com"`). Each is attached as its own custom domain.
210 * Order matters: aliases follow `name` in the `urls` output.
211 */
212 aliases?: string[];
213 /**
214 * Hostnames that permanently redirect (HTTP 301, path and query
215 * preserved) to {@link name} — e.g. `"old.example.com"`. Each is
216 * attached as a custom domain (for DNS + TLS) with a redirect rule in
217 * the zone's `http_request_dynamic_redirect` phase, which runs before
218 * the Worker — redirected requests never invoke it. Redirect hostnames
219 * serve no content, so they appear in the `domain` output but never in
220 * `urls`.
221 */
222 redirects?: string[];
223 /**
224 * Cloudflare zone ID for every hostname in this config. Equivalent to
225 * Wrangler's `zone_id`. Wins over {@link zone} / {@link zoneName}.
226 * When omitted, each hostname's zone is looked up by name
227 * (`GET /zones?name=`), not by listing the account's first page of zones.
228 */
229 zoneId?: string;
230 /**
231 * Cloudflare zone name, e.g. `"example.com"`. Equivalent to Wrangler's
232 * `zone_name`.
233 */
234 zoneName?: string;
235 /**
236 * Zone reference — a zone ID, zone name, or `{ zoneId, name? }` object.
237 * Alternative to {@link zoneId} / {@link zoneName}.
238 */
239 zone?: ZoneReference;
240 /**
241 * Opt into custom-domain Worker Previews (private beta). When `true`,
242 * Previews of this Worker are served at `<preview-name>.<name>` (and a
243 * pinned `<deployment-id>-<preview-name>.<name>` per deploy). Cloudflare
244 * provisions a wildcard DNS record and certificate. Equivalent to
245 * Wrangler's `previews_enabled` on a custom-domain route.
246 *
247 * Unset, the field is not sent to Cloudflare — existing custom-domain
248 * attaches are unchanged. This is a **parent** setting: enable it on
249 * the production Worker, not on the Preview Worker. A dedicated Preview
250 * hostname (`previews.example.com`) avoids colliding with existing
251 * subdomains.
252 *
253 * @default false
254 */
255 previews?: boolean;
256}
257
258export interface WorkerRouteConfig {
259 /**
260 * URL pattern to match incoming requests against, e.g.
261 * `"subdomain.example.com/*"` or `"example.com/api/*"`.
262 */
263 pattern: string;
264 /**
265 * Cloudflare zone ID. Equivalent to Wrangler's `zone_id`.
266 */
267 zoneId?: string;
268 /**
269 * Cloudflare zone name, e.g. `"example.com"`. Equivalent to Wrangler's
270 * `zone_name`.
271 */
272 zoneName?: string;
273 /**
274 * Zone reference — a zone ID, zone name, or `{ zoneId, name? }` object.
275 * Alternative to `zoneId` / `zoneName`.
276 */
277 zone?: ZoneReference;
278}
279
280/**
281 * Version-affinity configuration — keeps each user on one version for the
282 * duration of a gradual rollout.
283 *
284 * Percentages route each request independently, so one user can bounce
285 * between versions mid-rollout. Cloudflare pins a request to a version
286 * deterministically when it carries a `Cloudflare-Workers-Version-Key`
287 * header: the key is hashed and assigned a version from the current
288 * percentages, so equal keys always land on the same version, and pinned
289 * users only move forward as percentages rise.
290 *
291 * Setting `affinity` provisions that header as infrastructure: a `rewrite`
292 * Transform Rule in the `http_request_late_transform` phase of every zone
293 * the Worker serves on (custom domains and zone routes), scoped to the
294 * Worker's hostnames, filling the header from the configured source. The
295 * rule is updated in place as the config changes and removed when
296 * `affinity` is removed or the Worker is destroyed; other rules in the
297 * zone's shared entrypoint are left untouched.
298 *
299 * Exactly one of {@link cookie}, {@link header}, or {@link key} may be
300 * set; {@link ip} combines with `cookie`/`header` as a fallback or stands
301 * alone as the sole source.
302 *
303 * Transform Rules only see **zone traffic** — the Worker must have a
304 * `domain` or `routes` (or, with `version.parent`, the parent must).
305 * A workers.dev-only Worker fails the deploy with
306 * `WorkerVersionConfigError`; on the bare workers.dev URL clients must
307 * send the header themselves.
308 */
309export interface WorkerVersionAffinity {
310 /**
311 * Pin by the value of this request cookie (e.g. a session id). Requests
312 * without the cookie fall back to {@link ip} when set, otherwise they
313 * route independently by percentage.
314 */
315 cookie?: string;
316 /**
317 * Pin by the value of this request header (e.g. a tenant or user id
318 * your edge already stamps, or a stable auth claim forwarded as a
319 * header). Requests without the header fall back to {@link ip} when
320 * set, otherwise they route independently by percentage.
321 */
322 header?: string;
323 /**
324 * Pin by client IP. On its own (`{ ip: true }`) every request is keyed
325 * by `ip.src`; combined with {@link cookie} or {@link header} it is the
326 * fallback for requests missing the primary source (e.g. sticky by
327 * session cookie, falling back to sticky IP before the cookie is set).
328 */
329 ip?: boolean;
330 /**
331 * Escape hatch: a raw [Rules-language](https://developers.cloudflare.com/ruleset-engine/rules-language/)
332 * expression that computes the version key — e.g. a JWT claim via API
333 * Shield's `http.request.jwt.claims`, or any composite of request
334 * fields. Applied to every request on the Worker's hostnames; cannot be
335 * combined with the other sources.
336 */
337 key?: string;
338}
339
340/**
341 * Versioning configuration for a Worker deploy — controls Worker
342 * [versions and gradual deployments](https://developers.cloudflare.com/workers/configuration/versions-and-deployments/).
343 *
344 * Two modes, selected by {@link parent}:
345 *
346 * - **Version worker** (`parent` set): instead of creating its own script,
347 * this Worker uploads an immutable *version* to the referenced parent
348 * Worker's script. With `traffic: 0` (the default) the parent's live
349 * deployment is untouched and the version is reachable only at its
350 * preview URL (`<version-prefix>-<name>.<subdomain>.workers.dev`) —
351 * the PR-preview use case. With `traffic > 0` the version becomes a
352 * canary taking that percentage of the parent's traffic.
353 * - **Gradual rollout** (`parent` omitted): when this Worker deploys
354 * itself, the newly uploaded version receives {@link traffic} percent
355 * and the currently-live version keeps the remainder, instead of the
356 * default 100% cutover.
357 *
358 * A gradual rollout carries only what a version can: code, static assets,
359 * bindings, compatibility settings, and cache configuration. A deploy that
360 * changes Durable Object class migrations must go out at
361 * 100% (migrations cannot ride a rollout), and script-level settings
362 * (tags, observability, limits, placement, logpush) keep their live
363 * values until the next full deploy.
364 */
365export interface WorkerVersionOptions {
366 /**
367 * The Worker that owns the script this version is uploaded to. Accepts a
368 * Worker reference — typically `yield* Cloudflare.Worker.ref(id, { stage,
369 * stack })` for a Worker deployed in another stage/stack, or a
370 * locally-declared Worker — or a literal script name as an escape hatch.
371 *
372 * When set, this resource does not create a script of its own: it
373 * uploads a version (code + static assets + bindings + compatibility
374 * settings) to the parent's script. Script-level settings apply immediately
375 * to *all* versions of the parent, so they cannot be set on a version worker —
376 * `name`, `namespace`, `crons`, `domain`, `routes`, `tags`,
377 * `logpush`, `observability`, `placement`, `limits`, and `subdomain` are
378 * rejected, as are locally-hosted Durable Object or Workflow classes
379 * (their migrations would mutate the parent).
380 *
381 * Changing the parent replaces the resource (a version belongs to
382 * exactly one script).
383 */
384 parent?: string | Worker;
385 /**
386 * Percentage of traffic (0–100) the newly uploaded version receives; the
387 * currently-live version keeps the remainder. Cloudflare deployments
388 * split between at most two versions, so one canary can be active per
389 * script at a time.
390 *
391 * With {@link parent} set, defaults to `0`: the version is
392 * preview-URL-only and the parent's live deployment is untouched.
393 * Without `parent`, defaults to `100` (today's full cutover); `0` means
394 * "upload the version without deploying it" (the equivalent of
395 * `wrangler versions upload`).
396 *
397 * Percentages replace the previous deployment, they never add. Each
398 * versioned deploy splits its new version against the *stable* version —
399 * the majority holder of the current deployment — so deploying at 10 and
400 * then at 20 yields the second version at 20% against the stable version
401 * at 80%, with the earlier 10% version dropped from routing entirely
402 * (still uploaded, but unreachable even via a version-override header).
403 * With unchanged code that replacement is exactly a ramp; with changed
404 * code it swaps canaries. The first deploy of a script always goes to
405 * 100%, since there is no previous version to split with.
406 *
407 * Note that a subsequent full deploy of the parent (or of this Worker
408 * itself, at the default 100) resets traffic to 100% of its own new
409 * version.
410 */
411 traffic?: number;
412 /**
413 * Preview-URL alias for the uploaded version. Aliased preview URLs
414 * (`<alias>-<name>.<subdomain>.workers.dev`) are stable — each upload
415 * with the same alias re-points the URL at the new version — which is
416 * what makes them useful as shareable PR-preview links and lets
417 * `Worker.URL` resolve before the version exists.
418 *
419 * For a version worker ({@link parent} set) an alias is derived
420 * automatically from the stack, stage, and logical id, so every version
421 * worker gets a stable preview URL out of the box; set this to override
422 * it. Must start with a lowercase letter, contain only lowercase
423 * letters, digits, and dashes, and `<alias>-<worker-name>` must fit in
424 * 63 characters (a DNS label).
425 */
426 alias?: string;
427 /**
428 * Keep each user on one version for the duration of the rollout by
429 * pinning them to a stable request property — a session cookie, a
430 * header, the client IP, or a raw Rules-language expression:
431 *
432 * ```typescript
433 * version: {
434 * traffic: 10,
435 * // sticky by session cookie, falling back to sticky IP
436 * affinity: { cookie: "session_id", ip: true },
437 * }
438 * ```
439 *
440 * Provisions a Transform Rule that fills the
441 * `Cloudflare-Workers-Version-Key` header on the zones the Worker
442 * serves on — the Worker (or, with {@link parent}, the parent) must
443 * have a `domain` or `routes`. With `parent` set, the rule lands on the
444 * parent's zones, pinning users across the canary split. See
445 * {@link WorkerVersionAffinity}.
446 */
447 affinity?: WorkerVersionAffinity;
448 /**
449 * Human-readable annotation attached to the uploaded version, shown in
450 * the Cloudflare dashboard and `wrangler versions list`.
451 */
452 message?: string;
453 /**
454 * Machine-readable tag annotation attached to the uploaded version
455 * (e.g. a git commit SHA or PR number).
456 */
457 tag?: string;
458}
459
460/**
461 * Worker Preview configuration — uploads this Worker as a first-class
462 * [Preview](https://developers.cloudflare.com/workers/previews/) of
463 * another Worker's script instead of creating a script of its own.
464 *
465 * Distinct from {@link WorkerVersionOptions}: a version is an immutable
466 * upload onto the parent script (gradual rollouts, canaries, Version
467 * URLs). A Preview is a named copy with its own variables, secrets,
468 * bindings, and isolated same-Worker Durable Object / Container state.
469 * Cloudflare recommends Previews for branch and pull-request testing.
470 *
471 * Mutually exclusive with {@link WorkerVersionOptions.parent}.
472 */
473export interface WorkerPreviewOptions {
474 /**
475 * The Worker this Preview belongs to. Accepts a Worker reference —
476 * typically `yield* Cloudflare.Worker.ref(id, { stage, stack })` for
477 * a Worker deployed in another stage/stack, or a locally-declared
478 * Worker — or a literal script name as an escape hatch.
479 *
480 * When set, this resource does not create a script of its own: it
481 * creates (or updates) a Preview of the parent's script and deploys
482 * this Worker's code, assets, and bindings to it. Script-level
483 * settings that belong to the parent — `name`, `namespace`, `crons`,
484 * `domain`, `routes`, `workersDev`, `access` — cannot be set on a
485 * Preview Worker. Locally-hosted Durable Object and Workflow classes
486 * *are* allowed: each Preview gets its own isolated namespace.
487 *
488 * Changing the parent replaces the resource (a Preview belongs to
489 * exactly one script).
490 */
491 of: string | Worker;
492 /**
493 * Preview name. Defaults to a DNS-safe form of the stack stage
494 * (`pr-123`, `feat-login`). Appears in Preview URLs:
495 * `https://<name>-<worker>.<subdomain>.workers.dev` and, when the
496 * parent has {@link WorkerDomainConfig.previews} enabled,
497 * `https://<name>.<domain>`.
498 *
499 * Must start with a lowercase letter, contain only lowercase letters,
500 * digits, and dashes, and `<name>-<worker-name>` must fit in 63
501 * characters (a DNS label).
502 */
503 name?: string;
504 /**
505 * Human-readable annotation attached to the Preview deployment,
506 * shown in the Cloudflare dashboard.
507 */
508 message?: string;
509 /**
510 * Machine-readable tag annotation attached to the Preview deployment
511 * (e.g. a git commit SHA or PR number).
512 */
513 tag?: string;
514}
515
516export interface WorkerProps<
517 // PERF: unconstrained for the same reason as `Worker<Bindings>` above —
518 // the `extends WorkerBindingProps` proof is expensive for generic mapped
519 // types and the call-site overloads already constrain user input.
520 Bindings = any,
521 Assets extends WorkerAssetsConfig | undefined =
522 | WorkerAssetsConfig
523 | undefined,
524> extends PlatformProps {
525 /**
526 * Worker name override. If omitted, Alchemy derives a deterministic physical
527 * name from the stack, stage, and logical ID.
528 */
529 name?: string;
530 /**
531 * Deploy this Worker into a Workers for Platforms dispatch namespace as a
532 * "user worker" — a customer Worker that a platform Worker dispatches to at
533 * runtime via a dynamic-dispatch binding — instead of as a regular
534 * account-level Worker.
535 *
536 * Accepts the namespace name or a {@link DispatchNamespace} resource. The
537 * Worker's put/read/delete switch from the account-level
538 * `/workers/scripts` endpoints to the dispatch-namespace
539 * `/workers/dispatch/namespaces/:namespace/scripts` endpoints.
540 *
541 * User workers are not directly routable: they have no `workers.dev`
542 * subdomain, custom domains, or cron triggers, so {@link url},
543 * {@link domain}, and {@link crons} are ignored when this is set. Changing
544 * the namespace (or moving a Worker in or out of one) replaces the Worker,
545 * since an account-level script and a dispatch-namespace script are
546 * distinct cloud resources.
547 *
548 * @see https://developers.cloudflare.com/cloudflare-for-platforms/workers-for-platforms/
549 */
550 namespace?: string | DispatchNamespace;
551 /**
552 * Worker versions & gradual deployments. Set `version.parent` to upload
553 * this Worker as a canary *version* of another Worker's script
554 * instead of creating its own; set `version.traffic` below 100 to
555 * gradually roll out a deploy of this Worker's own script. For branch
556 * and pull-request testing, use {@link preview} instead. See
557 * {@link WorkerVersionOptions}.
558 */
559 version?: WorkerVersionOptions;
560 /**
561 * Opt into Cloudflare's [Worker Previews](https://developers.cloudflare.com/workers/previews/)
562 * (private beta). Unset, this Worker deploys as a normal script and none
563 * of the Preview APIs are called. Set `preview.of` to upload this Worker
564 * as a Preview of another Worker's script instead: own URL, bindings,
565 * and isolated Durable Object state; the parent's live deployment is
566 * untouched. Mutually exclusive with {@link version.parent}. See
567 * {@link WorkerPreviewOptions}.
568 */
569 preview?: WorkerPreviewOptions;
570 /**
571 * Controls the Worker's `workers.dev` surface.
572 *
573 * - `true` (the default) — serve the Worker at its stable `workers.dev`
574 * URL and enable version preview URLs.
575 * - `false` — no `workers.dev` URLs at all.
576 * - An object — toggle the stable URL and version previews independently,
577 * e.g. `{ enabled: false, previewsEnabled: true }` keeps the stable URL
578 * off while each deployed version stays reachable at its preview URL.
579 *
580 * @default true
581 */
582 workersDev?: boolean | WorkersDevConfig;
583 /**
584 * Protect this Worker with Cloudflare Access. Two forms:
585 *
586 * **Dedicated** — `{ policies, ... }` declares an Access application
587 * owned by this Worker (namespaced under it as `<Worker>/Access`),
588 * created, updated, and deleted with it:
589 * ```ts
590 * access: {
591 * policies: [
592 * { decision: "allow", include: [{ emailDomain: "example.com" }] },
593 * ],
594 * }
595 * ```
596 *
597 * **Shared** — pass a `Cloudflare.Access.Application` directly to enroll
598 * into it. Access policies are application-wide: every enrolled Worker
599 * is gated by the same policy set:
600 * ```ts
601 * access: TeamOnly
602 * ```
603 *
604 * Either way the Worker's `worker` destination — and a `preview_worker`
605 * destination unless `previews: false` (dedicated form) — is pushed onto
606 * the application, covering custom domains, routes, the `workers.dev`
607 * URL, and version preview URLs. Removing the prop (or deleting the
608 * Worker) un-enrolls it.
609 *
610 * At runtime, read the authenticated identity from `ctx.access` via
611 * `Cloudflare.Access.Context`; under `alchemy dev`, simulate it with
612 * `dev: { access: ... }`. Also accepted by every `Cloudflare.Website.*`
613 * framework. See the
614 * [Protect a Worker with Access](/cloudflare/security/access) guide.
615 */
616 access?: WorkerAccessConfig;
617 /**
618 * Static assets to serve. Can be:
619 * - A string path to the assets directory
620 * - An AssetsProps object with directory and config
621 * - An object with path and hash (e.g., from a Build resource)
622 *
623 * Plans hash the directory contents, so an unchanged tree converges to a
624 * noop. Supplying a precomputed `hash` (e.g. from a Build resource) makes
625 * that hash authoritative instead — the directory is not read during
626 * planning at all.
627 *
628 * Requests are served assets-first by default: a request matching a file
629 * never invokes the Worker. `runWorkerFirst` inverts that — `true` routes
630 * every request through the Worker ahead of the asset layer (serve files
631 * yourself via the `ASSETS` binding), and a glob array (e.g. `["/api/*"]`)
632 * routes only matching paths worker-first. The same routing applies under
633 * `alchemy dev`.
634 *
635 * When neither {@link main} nor {@link script} is provided, the Worker is
636 * deployed **assets-only**: no script is uploaded at all and Cloudflare's
637 * asset layer serves every request, applying `htmlHandling` /
638 * `notFoundHandling` (including single-page-application fallback) itself.
639 */
640 assets?: Assets;
641 /** @internal used by Cloudflare.Website.Vite resource */
642 vite?: ViteOptions;
643 /**
644 * An external source provider for this Worker — a package that builds
645 * the assets and server bundle (and serves local dev) in place of the
646 * built-in bundling pipeline. Used by framework integrations
647 * (Next/OpenNext, Astro, SvelteKit, Waku); most users configure it
648 * through the framework's `Website.*` wrapper rather than directly.
649 *
650 * The named package must be installed in your project — it is loaded
651 * with a dynamic `import()` and its default export must satisfy the
652 * `WorkerSourceModule` contract (`{ make(options) }`).
653 *
654 * Mutually exclusive with {@link script}, {@link vite}, and
655 * {@link main} — a source is self-contained; a provider that needs a
656 * custom entry takes it in its own `options`.
657 */
658 source?: WorkerSourceDescriptor;
659 logpush?: boolean;
660 /**
661 * Cloudflare Workers Observability settings. Controls Workers Logs
662 * (`logs`) and Workers Traces (`traces`), each with their own
663 * `enabled`, `headSamplingRate`, and `persist` toggles.
664 *
665 * If omitted, defaults to `{ enabled: true, logs: { enabled: true,
666 * invocationLogs: true } }`. Traces are off by default — opt in via
667 * `traces: { enabled: true, ... }`.
668 */
669 observability?: WorkerObservability;
670 /**
671 * Workers Cache settings. When `enabled` is `true`, Cloudflare checks a
672 * regionally tiered cache in front of the Worker on every HTTP request —
673 * cache hits are served from the edge without invoking the Worker at all.
674 * The Worker controls what gets cached via standard response headers
675 * (`Cache-Control`, `Cache-Tag`, `Vary`), including
676 * `stale-while-revalidate`.
677 *
678 * Set `crossVersionCache: true` to share cached responses across Worker
679 * versions (by default the cache is scoped to a single version, so every
680 * deploy starts cold).
681 *
682 * If omitted, Workers Cache is disabled — unless the Worker's init phase
683 * enables it via `yield* Cloudflare.cache()`, which is the preferred way
684 * for Effect-native Workers (and also returns the runtime purge client).
685 * When both are set, this prop takes precedence.
686 *
687 * @see https://blog.cloudflare.com/workers-cache/
688 */
689 cache?: WorkerCache;
690 tags?: string[];
691 /**
692 * Path to the Worker's entry module. Bundled with rolldown before
693 * upload. Mutually exclusive with {@link script} — provide at most one.
694 * Omit both (with {@link assets} set) to deploy an assets-only Worker.
695 *
696 * A `.py` entry deploys a Python Worker instead: no bundling runs — the
697 * entry plus every sibling `.py` file upload as Python modules, the
698 * `python_workers` compatibility flag is added, and dependencies from
699 * `pyproject.toml` (or a prebuilt `python_modules/` directory) are
700 * vendored alongside. See the Python Workers section above.
701 */
702 main?: string;
703 /**
704 * Raw module source for the Worker. When provided, bundling is bypassed
705 * entirely and this string is uploaded as a single ESM module
706 * (`main.js`). Useful for tiny inline workers (tests, fixtures,
707 * one-offs) and any case where you've already produced the final
708 * bundle elsewhere. Mutually exclusive with {@link main}.
709 */
710 script?: string;
711 compatibility?: {
712 date?: string;
713 /**
714 * Cloudflare runtime compatibility flags.
715 *
716 * For external Workers with `bundle: false`, an explicitly provided array
717 * is used exactly as declared, including `[]`. Omit this field to apply
718 * Alchemy's defaults.
719 *
720 * For all other Workers, supplied flags extend Alchemy's defaults:
721 * - `new_module_registry` is added unless `legacy_module_registry` is set.
722 * - `nodejs_compat` is added for dates before `2026-08-04` unless
723 * `no_nodejs_compat` is set. External Workers also require a date on or
724 * after `2024-09-23` for this default.
725 * - Effect Workers get `handle_cross_request_promise_resolution` for dates
726 * before `2024-10-14`; explicitly disabling it is rejected.
727 * - Python Workers get `python_workers` instead of the JavaScript defaults.
728 *
729 * Duplicate flags are removed when defaults are applied.
730 */
731 flags?: (
732 | "nodejs_compat"
733 | "nodejs_compat_v2"
734 | "no_nodejs_compat"
735 | "nodejs_als"
736 | "new_module_registry"
737 | "legacy_module_registry"
738 | "handle_cross_request_promise_resolution"
739 | "no_handle_cross_request_promise_resolution"
740 | "python_workers"
741 | (string & {})
742 )[];
743 };
744 limits?: WorkerLimits;
745 placement?: WorkerPlacement;
746 /**
747 * Tracks Durable Object and Workflow exports and captured SQL migrations
748 * for Effect-native Workers only.
749 * Populated automatically from bindings; do not set manually.
750 * @internal
751 */
752 exports?: Record<string, WorkerExport>;
753 /**
754 * Environment variables and native Cloudflare Bindings to bind to
755 * the Worker. Accepts:
756 *
757 * - Resource references (R2 bucket, KV namespace, D1 database,
758 * another Worker, Durable Object, etc.) — emitted as the
759 * corresponding native binding.
760 * - `effect/Config` values (`Config.Redacted`, `Config.String`,
761 * `Config.Number`, …) — resolved at deploy time and bound as
762 * `secret_text` on Cloudflare regardless of the `Config`
763 * constructor used. See
764 * [Secrets & env](/cloudflare/security/secrets-env).
765 * - Literal values — routed by shape: `Redacted<string>` →
766 * `secret_text`, `string` → `plain_text`, anything else → `json`.
767 * - `Output` values that resolve to a plain env value (e.g.
768 * `Alchemy.makeRandom`) — classified at deploy time by their
769 * resolved value using the literal rules above. Whole-resource
770 * Outputs (`Output.of(bucket)`) are rejected at deploy time; bind
771 * the resource itself instead.
772 *
773 * In Effect-native Workers you can alternatively `yield*` a
774 * `Config` in the Init phase to register the binding implicitly;
775 * `env` is the only option for async (non-Effect) Workers.
776 */
777 env?: Bindings;
778 /**
779 * Cron expressions that trigger the Worker's scheduled handler.
780 *
781 * This is how async (non-Effect) Workers configure Cron Triggers — the
782 * entry module exports its own `scheduled` handler and this prop attaches
783 * the schedules at deploy time. Effect-native Workers usually skip this
784 * prop and call `Cloudflare.Workers.cron(expression, handler)` in the Init
785 * phase instead, which attaches the expression and registers the runtime
786 * listener in one step.
787 *
788 * Pass an empty array to remove all Cron Triggers.
789 */
790 crons?: string[];
791 /**
792 * Tail Workers that consume this Worker's execution traces. Each entry is
793 * another {@link Worker} (or a literal script name) that exports a `tail()`
794 * handler; after each invocation of this Worker, Cloudflare delivers the
795 * invocation's trace events (console logs, exceptions, event metadata) to
796 * every listed consumer.
797 *
798 * Pass the consumer Worker resource directly — Alchemy resolves it to its
799 * deployed script name and deploys the consumer before this Worker — or a
800 * plain script name string for a tail Worker managed outside this stack.
801 *
802 * Changing the list is an in-place update. Omitting the prop (or passing
803 * `[]`) deploys this Worker with no tail consumers attached.
804 *
805 * @see https://developers.cloudflare.com/workers/observability/logs/tail-workers/
806 */
807 tailConsumers?: (string | Worker)[];
808 /**
809 * Streaming Tail Workers that consume this Worker's execution events as
810 * they happen. Each entry is another {@link Worker} (or a literal script
811 * name) that exports a `tailStream()` handler; Cloudflare invokes it with
812 * the invocation's `onset` event *while this Worker is still executing*,
813 * and the returned handler receives every subsequent event of the session
814 * (`log`, `spanOpen`, ...) ending with the terminal `outcome`.
815 *
816 * This differs from {@link tailConsumers}: a plain tail consumer's `tail()`
817 * handler receives the completed `TraceItem`s only after the producer's
818 * invocation finishes, while a streaming tail consumer observes events
819 * live, per-session, during execution.
820 *
821 * Pass the consumer Worker resource directly — Alchemy resolves it to its
822 * deployed script name and deploys the consumer before this Worker — or a
823 * plain script name string for a streaming tail Worker managed outside
824 * this stack.
825 *
826 * Changing the list is an in-place update. Omitting the prop (or passing
827 * `[]`) deploys this Worker with no streaming tail consumers attached.
828 *
829 * Streaming tail workers are experimental on Cloudflare's cloud: the
830 * configuration deploys, but production does not yet deliver events —
831 * Cloudflare rejects the `streaming_tail_worker` compatibility flag as
832 * "experimental and cannot yet be used in Workers deployed to
833 * Cloudflare", so a deployed consumer cannot enable its `tailStream()`
834 * handler. Under `alchemy dev`, local delivery is fully emulated.
835 *
836 * @see https://developers.cloudflare.com/workers/observability/logs/tail-workers/
837 */
838 streamingTailConsumers?: (string | Worker)[];
839 /**
840 * The Worker's custom domain: one canonical hostname, plus optional
841 * `aliases` that also serve the Worker and `redirects` that 301 to the
842 * canonical name. A bare string is shorthand for `{ name }`. Pin the
843 * zone with `zoneId` / `zone` / `zoneName` (same as {@link routes});
844 * otherwise each hostname is looked up by name. The zone must already
845 * exist in the account.
846 *
847 * When set, `https://<name>` becomes the Worker's primary `url` output,
848 * ranking above the `workers.dev` URL. See {@link WorkerDomainConfig}.
849 *
850 * Omitting the prop leaves custom domains unmanaged — attachments made
851 * outside Alchemy are preserved. Pass `null` to explicitly detach every
852 * custom domain (and remove their redirect rules).
853 */
854 domain?: string | WorkerDomainConfig | null;
855 /**
856 * Zone routes that map URL patterns to this Worker. Equivalent to Wrangler's
857 * `routes` array — provide `zoneName` or `zoneId` (or `zone`) alongside each
858 * `pattern`. When the zone is omitted, it is inferred from the pattern's
859 * hostname.
860 */
861 routes?: WorkerRouteConfig[];
862 /**
863 * Extra bundler options applied on top of the standard rolldown
864 * input/output options used to build this Worker. Includes the generic
865 * bundle extras (pure-annotation packages, bundle analyzer) plus
866 * `input` and `output` overrides. Input plugins run before Alchemy's
867 * plugins; `input.resolve.alias` takes precedence over Node compatibility
868 * shims. The entry remains {@link main}. Ignored when {@link bundle} is
869 * `false`. See {@link WorkerBuildOptions}.
870 */
871 build?: WorkerBuildOptions;
872 /**
873 * Whether to bundle {@link main} with rolldown before upload.
874 *
875 * Set to `false` when `main` already points at a complete,
876 * runtime-ready ESM Worker produced by an external tool (OpenNext,
877 * a separate rolldown/esbuild pipeline, etc.). The entry and every
878 * file around it matching {@link rules} are uploaded byte-for-byte —
879 * no bundling, no minification, no transformation. Module names are
880 * the files' POSIX paths relative to the entry's directory, matching
881 * Wrangler's `no_bundle` contract.
882 *
883 * Re-bundling such artifacts is unsafe: dynamic `import()` calls the
884 * upstream tool relies on can be rewritten in ways that break runtime
885 * behavior.
886 *
887 * Durable Object and Workflow classes must be exported by the prebuilt
888 * entry itself — {@link exports} is not applied when `bundle` is
889 * `false`.
890 *
891 * @default true
892 */
893 bundle?: boolean;
894 /**
895 * Module rules selecting which files in the directory containing
896 * {@link main} are uploaded as additional modules when {@link bundle}
897 * is `false`. Each rule's globs are matched against POSIX-style paths
898 * relative to that directory, mirroring Wrangler's `rules`
899 * configuration. When provided, these rules replace
900 * {@link defaultModuleRules}.
901 *
902 * @default defaultModuleRules — ESModule (`**\/*.js`, `**\/*.mjs`), CompiledWasm (`**\/*.wasm`), Text (`**\/*.txt`, `**\/*.html`, `**\/*.sql`), Data (`**\/*.bin`)
903 */
904 rules?: ModuleRule[];
905 /**
906 * Options for the local dev server that runs this Worker under `alchemy dev`.
907 * Each Worker is served on its own port.
908 *
909 * Use `{ mode: "external" }` to skip starting a local Worker entirely —
910 * useful when an external dev server (e.g. one spawned via `Command.Dev`)
911 * is serving the content this Worker would otherwise host.
912 */
913 dev?:
914 | {
915 /**
916 * Run this Worker in `workerd` locally (the default).
917 * @default "worker"
918 */
919 mode?: "worker";
920 /**
921 * Host the local dev server binds to.
922 * @default "localhost"
923 */
924 host?: string;
925 /**
926 * Port the local dev server listens on. If the port is unavailable,
927 * the next free port is used unless {@link strictPort} is `true`.
928 * @default 1337
929 */
930 port?: number;
931 /**
932 * When `true`, fail instead of falling back to another port if
933 * {@link port} is already in use.
934 * @default false
935 */
936 strictPort?: boolean;
937 /**
938 * Whether the local runtime's Cache API simulator
939 * (`caches.default` / `caches.open()`) stores responses. Set
940 * `false` to make every cache operation a no-op — matching
941 * production behaviour on `workers.dev` subdomains, where the
942 * Cache API silently does nothing.
943 * @default true
944 */
945 cache?: boolean;
946 /**
947 * Override the `request.cf` blob served to this Worker locally.
948 * Defaults to a static placeholder (Miniflare's Austin/DFW blob);
949 * pass e.g. `{ colo: "LHR", country: "GB" }` to simulate a
950 * different edge location.
951 */
952 cf?: Record<string, unknown>;
953 /**
954 * Stub the authenticated Access state in local dev. In production,
955 * `ctx.access` is populated by Cloudflare's edge after a request
956 * passes the Access login wall — locally there is no edge and no
957 * login, so without this stub `ctx.access` is always `undefined`
958 * and identity-dependent code paths can't run. When set, every
959 * request served by `alchemy dev` behaves as if this one user had
960 * logged in: `ctx.access` carries the given audience and
961 * `getIdentity()` resolves the given identity. Omit to simulate
962 * unauthenticated requests. Inert on deploy — the deployed Worker
963 * always gets the real edge-populated `ctx.access`.
964 */
965 access?: {
966 /**
967 * Simulated Access application audience (AUD) tag.
968 * @default "dev"
969 */
970 aud?: string;
971 /**
972 * Simulated identity returned by `ctx.access.getIdentity()`,
973 * e.g. `{ email: "dev@example.com" }`.
974 */
975 identity?: WorkerAccessIdentity;
976 };
977 }
978 | {
979 /**
980 * Don't start a local Worker; an external dev server is running instead.
981 */
982 mode: "external";
983 /**
984 * URL the external dev server is reachable at, if applicable.
985 * This will be returned as the `url` attribute of the Worker resource.
986 */
987 url?: string;
988 };
989}
990
991/**
992 * A serializable reference to an external Worker source provider.
993 * Persists in state (`olds`) and crosses the local-provider RPC
994 * boundary, so it must stay plain JSON data — the implementation is
995 * resolved by dynamically importing {@link provider}.
996 */
997export interface WorkerSourceDescriptor {
998 /**
999 * Module specifier resolved with `import()`, e.g.
1000 * `"@alchemy.run/cloudflare-next"`. The module's default export must
1001 * satisfy the `WorkerSourceModule` contract.
1002 */
1003 readonly provider: string;
1004 /**
1005 * How the source serves local development. Server-mode sources run in an
1006 * isolated child process; bundle-mode sources stream rebuilds back to the
1007 * local Worker host.
1008 */
1009 readonly devMode: "server" | "bundle";
1010 /**
1011 * The framework project root. Server-mode dev children are spawned with
1012 * this directory as their working directory — some framework dev servers
1013 * (e.g. `next dev`, which 404s every route when `process.cwd()` differs
1014 * from its `dir`) require cwd to be the project root. Relative paths
1015 * resolve against the engine's working directory. Defaults to the
1016 * engine's working directory.
1017 */
1018 readonly rootDir?: string;
1019 /**
1020 * The JS runtime the server-mode dev child must run under. Some framework
1021 * dev servers are runtime-sensitive — `next dev` (Turbopack) silently 404s
1022 * every route when cold-started under bun — so the provider can pin the
1023 * child to `"node"`. When the requested runtime is not installed, the
1024 * engine falls back to its own runtime. Defaults to the engine's runtime.
1025 */
1026 readonly runtime?: "node";
1027 /**
1028 * Provider-specific options (rootDir, memo, framework config, ...).
1029 * Must be JSON-serializable AND JSON-stable: the descriptor persists
1030 * in state and participates in the metadata hash, so non-deterministic
1031 * values here cause perpetual redeploys.
1032 */
1033 readonly options?: unknown;
1034}
1035
1036export interface ViteOptions {
1037 /**
1038 * Overrides the module that becomes the deployed Worker entry, forwarded
1039 * to the Cloudflare Vite plugin's `main` option. Relative paths resolve
1040 * from the Vite root (`rootDir`).
1041 *
1042 * By default the entry environment's own entry (the server bundle the
1043 * framework produces) is deployed. Point `main` at a custom module when
1044 * the deployed Worker must export more than the framework's fetch
1045 * handler — e.g. Durable Object classes or additional handlers wrapping
1046 * the framework handler.
1047 *
1048 * @example
1049 * ```typescript
1050 * vite: { main: "worker/index.ts" }
1051 * ```
1052 */
1053 main?: string;
1054 /**
1055 * Root directory passed to Vite's `root` option.
1056 * Defaults to the current working directory (`process.cwd()`).
1057 */
1058 rootDir?: string;
1059 /**
1060 * Controls which files are hashed to decide whether a rebuild is needed.
1061 * By default every non-gitignored file in `cwd` is hashed, plus the nearest
1062 * lockfile. Provide explicit globs to narrow the scope.
1063 *
1064 * @see {@link MemoOptions}
1065 */
1066 memo?: MemoOptions & {
1067 /**
1068 * Configure additional workspaces to hash.
1069 * By default, auto-detects workspaces during Vite build, then hashes all
1070 * non-gitignored files in the workspace plus the nearest lockfile.
1071 * @default "auto"
1072 */
1073 workspaces?:
1074 | "auto"
1075 | Array<
1076 MemoOptions & {
1077 /**
1078 * The working directory to hash, relative to the Vite root.
1079 */
1080 cwd: string;
1081 }
1082 >;
1083 };
1084 /**
1085 * Selects which Vite environments make up the deployed Worker, for
1086 * frameworks that build more than one (e.g. React Server Components).
1087 *
1088 * A single-environment SSR build needs no configuration. For a
1089 * multi-environment build, point `entry` at the environment that
1090 * produces the server entry chunk and list the remaining server-side
1091 * environments in `children` so their chunks are bundled alongside it.
1092 * The `client` environment is always treated as static assets.
1093 *
1094 * @example React Router / React Server Components
1095 * ```typescript
1096 * viteEnvironments: { entry: "rsc", children: ["ssr"] }
1097 * ```
1098 *
1099 * @default { entry: "ssr", children: [] }
1100 */
1101 viteEnvironments?: {
1102 entry?: string;
1103 children?: string[];
1104 };
1105}
1106
1107// PERF: deliberately NOT `Bindings extends WorkerBindings`. The constraint
1108// forced the checker to prove the generic `NormalizedBindings<...>` mapped
1109// type assignable to the ~30-member `WorkerBindingResource` union at every
1110// `Worker<...>` instantiation — a single 28s structural relation that was 45%
1111// of the whole program's check time. Input is already constrained at the
1112// call boundary (`Bindings extends WorkerBindingProps`), so this type
1113// argument is only ever produced from validated shapes.
1114export type Worker<Bindings = any> = Resource<
1115 WorkerTypeId,
1116 WorkerProps<Bindings>,
1117 {
1118 /**
1119 * The immutable ID Cloudflare assigns to this Worker's script — a hex
1120 * value like `c81a2d22c29840ed9d61681a3270dbff`, shown as the Worker ID
1121 * in the dashboard and the identifier Access `worker` destinations key
1122 * on. Stable across every update; changes only when the script is
1123 * replaced. For the script *name*, use {@link workerName}. A version
1124 * worker carries its parent script's ID. Under `alchemy dev` there is
1125 * no cloud script, so the local provider generates a `dev:`-prefixed
1126 * ID instead (switching between dev and deploy replaces the resource,
1127 * so the two identities never mix).
1128 */
1129 workerId: string;
1130 /** The script name, e.g. `"api"` — the classic API's identifier. */
1131 workerName: string;
1132 /**
1133 * The Workers for Platforms dispatch namespace this Worker was deployed
1134 * into, or `undefined` for a regular account-level Worker.
1135 */
1136 namespace: string | undefined;
1137 logpush: boolean | undefined;
1138 /**
1139 * The most significant URL the Worker is reachable at — always
1140 * `urls[0]`, or `undefined` when the Worker is not reachable at any
1141 * URL (e.g. a dispatch-namespace user worker). Ranking: the canonical
1142 * custom domain, then aliases, then the stable `workers.dev` URL; a
1143 * version worker's `url` is its aliased preview URL; under
1144 * `alchemy dev` it is the local dev server's URL.
1145 */
1146 url: string | undefined;
1147 /**
1148 * Every URL that serves this Worker, most significant first —
1149 * `[https://<domain.name>?, ...aliases, <workers.dev URL>?,
1150 * <version preview URLs>?]`, or the local dev server's
1151 * `[localhost, ...LAN]` URLs under `alchemy dev`. Redirect hostnames
1152 * never appear (they don't serve the Worker). Useful wholesale, e.g.
1153 * as a CORS allow-list.
1154 */
1155 urls: string[];
1156 /**
1157 * The Worker's resolved custom-domain configuration — canonical
1158 * `name`, `aliases`, and `redirects` as deployed — or `undefined`
1159 * when no custom domain is configured.
1160 */
1161 domain:
1162 | {
1163 name: string;
1164 aliases: string[];
1165 redirects: string[];
1166 zone?: ZoneReference;
1167 previews?: boolean;
1168 }
1169 | undefined;
1170 tags: string[] | undefined;
1171 durableObjectNamespaces: Record<string, string>;
1172 accountId: string;
1173 routes: { id: string; pattern: string; zoneId: string }[];
1174 crons: string[];
1175 /**
1176 * The tail consumers attached to this Worker's script — each entry the
1177 * consuming Worker's script name — or `undefined` when none are
1178 * attached. Local emulation (`RuntimeWorker.tails`) lowers this same
1179 * list into workerd tail-service designators.
1180 */
1181 tailConsumers?: { service: string }[] | undefined;
1182 /**
1183 * The streaming tail consumers attached to this Worker's script — each
1184 * entry the consuming Worker's script name — or `undefined` when none
1185 * are attached. Recorded from the uploaded metadata: the script-settings
1186 * read endpoint does not expose `streaming_tail_consumers`, so this is
1187 * the deployed value, not an observed one. Local emulation
1188 * (`RuntimeWorker.streamingTails`) lowers this same list into workerd
1189 * streaming-tail service designators.
1190 */
1191 streamingTailConsumers?: { service: string }[] | undefined;
1192 /**
1193 * The parent script name this Worker uploads versions to, when this
1194 * resource is a version worker (`version.parent` set). `undefined` for
1195 * a Worker that owns its own script — including one deploying with a
1196 * gradual rollout. This is the discriminator `read`/`delete` use to
1197 * avoid treating the parent's script as this resource's own.
1198 */
1199 versionOf?: string | undefined;
1200 /**
1201 * The parent script name this Worker is a Preview of, when this
1202 * resource is a Preview Worker (`preview.of` set). `undefined` for
1203 * a Worker that owns its own script. Discriminator `read`/`delete`
1204 * use so they never treat the parent's script as this resource's own.
1205 */
1206 previewOf?: string | undefined;
1207 /**
1208 * Cloudflare's immutable Preview id. Set when this resource is a
1209 * Preview Worker (`preview.of`).
1210 */
1211 previewId?: string | undefined;
1212 /**
1213 * The Preview name as created — the user-provided `preview.name`, or
1214 * the auto-derived name from the stack stage.
1215 */
1216 previewName?: string | undefined;
1217 /**
1218 * DNS-safe slug Cloudflare assigned to this Preview. Used in Preview
1219 * URLs (`<slug>-<worker>.<subdomain>.workers.dev` and
1220 * `<slug>.<domain>` when the parent has custom-domain Previews).
1221 */
1222 previewSlug?: string | undefined;
1223 /**
1224 * Same-Worker Durable Object class names hosted on the last Preview
1225 * deploy — the baseline for the next Preview's class migrations.
1226 */
1227 previewDoClasses?: string[] | undefined;
1228 /**
1229 * The id of the version uploaded by the most recent deploy. Only set
1230 * when versioning is in play: always for a version worker
1231 * (`version.parent`), and for a self-owned Worker when a gradual
1232 * rollout (`version.traffic` < 100) deployed via the versions API.
1233 */
1234 versionId?: string | undefined;
1235 /**
1236 * The preview-URL alias attached to the uploaded version — the
1237 * user-provided `version.alias`, or the auto-derived stable alias for
1238 * a version worker. The aliased preview URL
1239 * (`<alias>-<name>.<subdomain>.workers.dev`) is stable across
1240 * deploys and is the version worker's primary `url`.
1241 */
1242 versionAlias?: string | undefined;
1243 /**
1244 * The id of the deployment created by the most recent deploy, when the
1245 * deploy created one through the deployments API (a version with
1246 * `traffic > 0`). Preview-only versions (`traffic: 0`) have no
1247 * deployment.
1248 */
1249 deploymentId?: string | undefined;
1250 /**
1251 * The zone ids currently holding this resource's version-affinity
1252 * Transform Rules (`version.affinity`) — the cleanup list consulted
1253 * when affinity is removed or the resource is deleted. For a version
1254 * worker these are the *parent's* zones. `undefined` when no affinity
1255 * rules are deployed.
1256 */
1257 affinityZoneIds?: string[] | undefined;
1258 hash?: {
1259 assets: string | undefined;
1260 bundle: string | undefined;
1261 input: string | undefined;
1262 additionalWorkspaces: string[] | undefined;
1263 // Hash of the deploy-time metadata surface (compatibility, env,
1264 // bindings, asset config, limits, observability, ...) so metadata-only
1265 // edits trigger an update (#745). Optional: state written before this
1266 // field existed has no `metadata`, which reads as a one-time update on
1267 // the first diff after upgrading (the apply backfills it).
1268 metadata?: string | undefined;
1269 };
1270 },
1271 {
1272 bindings?: WorkerBinding[];
1273 /**
1274 * Extra env vars merged into the Worker at reconcile. `Redacted`
1275 * values deploy as `secret_text`. Used by later resources (e.g. a
1276 * Stripe webhook signing secret) to attach env without the Worker
1277 * init depending on that resource.
1278 */
1279 env?: Record<string, any>;
1280 /**
1281 * Workers Cache settings contributed by `yield* Cloudflare.cache()`.
1282 * Merged into the upload metadata's `cache_options`; an explicit
1283 * `WorkerProps.cache` takes precedence.
1284 */
1285 cache?: WorkerCache;
1286 /**
1287 * Workers Observability traces settings contributed by
1288 * `Cloudflare.Telemetry()`. Deep-merged into upload metadata: bound
1289 * traces fill in when `WorkerProps.observability.traces` is omitted,
1290 * and never clobber the default logs config. An explicit
1291 * `observability.traces` object wins.
1292 */
1293 observability?: Pick<WorkerObservability, "traces">;
1294 containers?: {
1295 className: string;
1296 dev: DevContainerImage | undefined;
1297 /**
1298 * Content hash of the image (bundle/Dockerfile/context). Part of the
1299 * local worker's restart config: `dev` only carries stable PATHS, so
1300 * without the hash an image content change would never restart the
1301 * instance and the running container would serve stale code.
1302 */
1303 hash: string | undefined;
1304 }[];
1305 crons?: string[];
1306 hyperdrives?: Record<string, Required<DevOrigin>>;
1307 /**
1308 * Dev-only channel (like `hyperdrives`): binding name → opt-out of local
1309 * emulation in `alchemy dev` (the binding was piped through `Alchemy.remote()`
1310 * constructor). Contributed alongside the pure wire binding instead of
1311 * being embedded in it — wire descriptors stay exactly what Cloudflare
1312 * accepts. Records from multiple bind calls merge by key; the local
1313 * worker provider reads it when lowering `browser` / `images` / `stream`
1314 * / `send_email` bindings to their local or remote runtime hooks. The
1315 * live provider ignores it.
1316 */
1317 devRemote?: Record<string, boolean>;
1318 },
1319 Providers
1320>;
1321
1322/** An external/async Worker declared without an Effect implementation. */
1323export type ExternalWorker<Bindings = {}> = Worker<Bindings> & {
1324 /**
1325 * The external Worker's declared `env`. Not available on persisted references
1326 * or Effect-native Worker construction results.
1327 * A Workflow binding is exposed as a {@link WorkflowBinding} whose
1328 * `workflowName` is an `Output` resolved in the same deploy, so a sibling
1329 * resource (e.g. a Queue subscription to the Workflow's events) can
1330 * consume the Workflow's physical name on its first deployment:
1331 *
1332 * ```typescript
1333 * source: {
1334 * type: "workflows.workflow",
1335 * workflowName: worker.env.MY_WORKFLOW.workflowName,
1336 * }
1337 * ```
1338 */
1339 readonly env: WorkerEnvBindings<Bindings>;
1340};
1341
1342/** The env key the resolved URL is injected under when `yield*`-ed. */
1343const SELF_URL_BINDING_NAME = "WORKER_URL";
1344
1345/**
1346 * Effect-native accessor for the Worker's own URL. The value is injected as an
1347 * env binding that only exists at the *exec* phase on the deployed Worker, so
1348 * reading it is deferred behind an Effect that requires {@link RuntimeContext}.
1349 * Yield it inside a handler to obtain the URL string.
1350 */
1351export type URLAccessor = Effect.Effect<string, never, RuntimeContext>;
1352
1353/**
1354 * The type of {@link URL} (`Worker.URL`).
1355 *
1356 * It is a real `Effect` — `yield* Worker.URL` inside a Worker init attaches
1357 * the binding and resolves the deferred {@link URLAccessor} — but it also
1358 * carries the `~alchemy/Kind` marker statically, so when it is declared on a
1359 * Worker's `env` the binding machinery recognises it as a `self_url` binding
1360 * (`isSelfUrl`) instead of running it.
1361 *
1362 * Defined in this module (not its own file): the effect closes over the
1363 * `Worker` tag and `WorkerEnvironment`, and a separate module would form a
1364 * value cycle with Worker.ts that the deploy bundler's scope hoisting turns
1365 * into a startup crash.
1366 */
1367export interface URLEffect extends Effect.Effect<
1368 URLAccessor,
1369 never,
1370 WorkerEnvironment | Worker
1371> {
1372 "~alchemy/Kind": "Cloudflare.Workers.URL";
1373}
1374
1375/**
1376 * A Worker's own public URL, injected as a binding on that same Worker. At
1377 * deploy time Alchemy resolves the URL the Worker will be served at (its first
1378 * custom domain if any, otherwise its `workers.dev` URL) and injects it as a
1379 * plain-text env binding, so the running Worker knows its own public address.
1380 *
1381 * Declare it on a Worker's `env` (it flows through `InferEnv` → `string`) or
1382 * `yield*` it inside an Effect-native Worker to attach the binding and obtain
1383 * a deferred {@link URLAccessor}. It is also exposed as
1384 * `Cloudflare.Worker.URL`.
1385 *
1386 * Because the URL is resolved *before* the bundle is built, a `VITE_`-prefixed
1387 * env key holding `Worker.URL` is inlined into the client bundle as
1388 * `import.meta.env.VITE_*` — the canonical way to give a Vite frontend its own
1389 * public URL.
1390 */
1391export const URL: URLEffect = Object.assign(
1392 Effect.gen(function* () {
1393 // Deploy-time only: register the binding on the host Worker. The provider
1394 // lowers the `self_url` sentinel into a plain-text binding holding the
1395 // resolved URL just before upload.
1396 if (!globalThis.__ALCHEMY_RUNTIME__) {
1397 yield* (yield* Worker).bind`${SELF_URL_BINDING_NAME}`({
1398 bindings: [{ type: "self_url", name: SELF_URL_BINDING_NAME }],
1399 });
1400 }
1401 // Captured at init; the deferred read only runs at exec phase (the
1402 // accessor is colored with RuntimeContext), where env is populated.
1403 const env = yield* WorkerEnvironment;
1404 return Effect.sync(
1405 () => (env as Record<string, string>)[SELF_URL_BINDING_NAME]!,
1406 ) as URLAccessor;
1407 }),
1408 { "~alchemy/Kind": "Cloudflare.Workers.URL" as const },
1409);
1410
1411/**
1412 * Returns true when the value is the `Worker.URL` binding (keyed on the
1413 * static `~alchemy/Kind` marker — the value is a real Effect, so every
1414 * env-resolution site must check this before `Effect.isEffect`).
1415 */
1416export const isSelfUrl = (value: unknown): value is URLEffect =>
1417 typeof value === "object" &&
1418 value !== null &&
1419 "~alchemy/Kind" in value &&
1420 (value as URLEffect)["~alchemy/Kind"] === "Cloudflare.Workers.URL";
1421
1422/**
1423 * A service binding that points at this Worker ITSELF. Declare it on `env`
1424 * to give the Worker a self-referencing service binding — the provider
1425 * lowers it into a `service` binding targeting the Worker's own physical
1426 * name at upload, and local dev serves it with the runtime's in-process
1427 * self service.
1428 *
1429 * The canonical consumer is OpenNext's `WORKER_SELF_REFERENCE` (the ISR
1430 * revalidation queue re-fetches the worker through it):
1431 *
1432 * ```typescript
1433 * const site = yield* Cloudflare.Website.Nextjs("Site", {
1434 * env: {
1435 * WORKER_SELF_REFERENCE: Cloudflare.Workers.Self,
1436 * },
1437 * });
1438 * ```
1439 */
1440export const Self = {
1441 "~alchemy/Kind": "Cloudflare.Workers.Self",
1442} as const;
1443export type Self = typeof Self;
1444
1445/** Returns true when the value is the {@link Self} marker. */
1446export const isSelf = (value: unknown): value is Self =>
1447 typeof value === "object" &&
1448 value !== null &&
1449 "~alchemy/Kind" in value &&
1450 (value as Self)["~alchemy/Kind"] === "Cloudflare.Workers.Self";
1451
1452/**
1453 * A Cloudflare Worker host with deploy-time binding support and runtime export
1454 * collection.
1455 *
1456 * A Worker follows a two-phase pattern. The outer `Effect.gen` runs at
1457 * deploy time to bind resources (KV, R2, Durable Objects, etc.). It returns
1458 * an object whose properties are the Worker's runtime handlers — `fetch` for
1459 * HTTP requests and any additional RPC methods.
1460 *
1461 * ```typescript
1462 * Effect.gen(function* () {
1463 * // Construction: bind resources (deploy time and cold start)
1464 * const kv = yield* Cloudflare.KV.ReadWriteNamespace(MyKV);
1465 *
1466 * return {
1467 * // Runtime: handlers, once per request
1468 * fetch: Effect.gen(function* () {
1469 * const value = yield* kv.get("key");
1470 * return HttpServerResponse.text(value ?? "not found");
1471 * }),
1472 * };
1473 * })
1474 * ```
1475 *
1476 * There are three ways to define a Worker, from simplest to most
1477 * flexible. See the [Runtime](/infrastructure-as-effects/runtime)
1478 * page for the full explanation.
1479 *
1480 * - **Async** — plain `async fetch` handler, no Effect runtime in the bundle.
1481 * - **Effect** — Effect implementation passed directly, single file.
1482 * - **Layer** — class and `.make()` in a single file; Rolldown tree-shakes `.make()` from consumers.
1483 * ### Protect with Access
1484 * Put Cloudflare Access in front of the Worker with the `access` prop —
1485 * unauthenticated requests are redirected to your team's login page, and
1486 * handlers read the authenticated identity from `ctx.access` via
1487 * `Cloudflare.Access.Context`. See the
1488 * [Protect a Worker with Access](/cloudflare/security/access) guide.
1489 *
1490 * **Example:** Dedicated application — per-Worker policies
1491 * The `{ policies }` form declares an Access application owned by this
1492 * Worker (namespaced under it as `<Worker>/Access`), created, updated,
1493 * and deleted with it:
1494 * ```typescript
1495 * export default Cloudflare.Worker(
1496 * "Api",
1497 * {
1498 * main: import.meta.url,
1499 * access: {
1500 * policies: [
1501 * { decision: "allow", include: [{ emailDomain: "example.com" }] },
1502 * ],
1503 * },
1504 * // simulate the authenticated state under `alchemy dev`
1505 * dev: { access: { aud: "dev", identity: { email: "dev@example.com" } } },
1506 * },
1507 * Effect.gen(function* () {
1508 * return {
1509 * fetch: Effect.gen(function* () {
1510 * const access = yield* Cloudflare.Access.Context;
1511 * const identity = yield* access!.getIdentity();
1512 * return yield* HttpServerResponse.json({ email: identity?.email });
1513 * }),
1514 * };
1515 * }),
1516 * );
1517 * ```
1518 *
1519 * **Example:** Shared application — one policy set, many Workers
1520 * Pass a `Cloudflare.Access.Application` directly to enroll into it.
1521 * Access policies are application-wide: every enrolled Worker is gated
1522 * by the same policy set.
1523 * ```typescript
1524 * const TeamOnly = Cloudflare.Access.Application("TeamOnly", {
1525 * type: "self_hosted",
1526 * policies: [
1527 * { decision: "allow", include: [{ emailDomain: "example.com" }] },
1528 * ],
1529 * });
1530 *
1531 * export default Cloudflare.Worker(
1532 * "Api",
1533 * { main: import.meta.url, access: TeamOnly },
1534 * /* ... *​/
1535 * );
1536 * ```
1537 *
1538 * ### Async Workers
1539 * You don't have to use Effect for your runtime code. If you create
1540 * a Worker resource with `main` pointing at a file but provide no
1541 * `Effect.gen` implementation, Alchemy bundles and deploys that file
1542 * as-is. Your handler is a plain `async fetch` — no Effect runtime
1543 * is included in the bundle.
1544 *
1545 * Use the `env` prop to declare which resources, `Config` values,
1546 * and literal env vars are available at runtime, and
1547 * `Cloudflare.InferEnv` to extract a fully typed `env` object from
1548 * them.
1549 *
1550 * See the [Workers guide](/cloudflare/compute/workers)
1551 * for a comprehensive walkthrough of all binding types (R2, D1,
1552 * Durable Objects, Assets, and more).
1553 *
1554 * **Example:** Defining an async Worker in your stack
1555 * ```typescript
1556 * // alchemy.run.ts
1557 * const db = yield* Cloudflare.D1.Database("DB");
1558 * const bucket = yield* Cloudflare.R2.Bucket("Bucket");
1559 *
1560 * export type WorkerEnv = Cloudflare.InferEnv<typeof Worker>;
1561 *
1562 * export const Worker = Cloudflare.Worker("Worker", {
1563 * main: "./src/worker.ts",
1564 * env: { db, bucket },
1565 * });
1566 * ```
1567 *
1568 * **Example:** Writing the async handler
1569 * ```typescript
1570 * // src/worker.ts
1571 * import type { WorkerEnv } from "../alchemy.run.ts";
1572 *
1573 * export default {
1574 * async fetch(request: Request, env: WorkerEnv) {
1575 * if (request.method === "GET") {
1576 * const object = await env.bucket.get("key");
1577 * return new Response(object?.body ?? null);
1578 * }
1579 * return new Response("Not Found", { status: 404 });
1580 * },
1581 * };
1582 * ```
1583 *
1584 * **Example:** Binding a named entrypoint
1585 *
1586 * `env: { TARGET: worker }` targets the Worker's default entrypoint.
1587 * `Cloudflare.WorkerEntrypoint` binds one of its named
1588 * `WorkerEntrypoint` class exports instead; `InferEnv` types the entry
1589 * as a `Fetcher` stub whose RPC methods are called directly.
1590 * ```typescript
1591 * const caller = yield* Cloudflare.Worker("Caller", {
1592 * main: "./src/caller.ts",
1593 * env: {
1594 * API: Cloudflare.WorkerEntrypoint(target, "Api"), // env.API.greet("alice")
1595 * },
1596 * });
1597 * ```
1598 *
1599 * **Example:** Entrypoint props
1600 *
1601 * The options form attaches properties the target entrypoint reads from
1602 * `this.ctx.props`. `Output` values resolve at deploy time. `alchemy dev`
1603 * delivers `props` to the local workerd; the deploy API's binding schema
1604 * does not carry the field yet, so `props` are dropped at upload while the
1605 * binding itself deploys correctly.
1606 * ```typescript
1607 * env: {
1608 * VENDOR: Cloudflare.WorkerEntrypoint(vendorWorker, {
1609 * entrypoint: "Vendor",
1610 * props: { baseUrl: site.url },
1611 * }),
1612 * }
1613 * ```
1614 *
1615 * ### Python Workers
1616 * Point `main` at a `.py` file to deploy a
1617 * [Python Worker](https://developers.cloudflare.com/workers/languages/python/)
1618 * (open beta). There is no bundling step — the entry and every sibling
1619 * `.py` module upload as-is and are interpreted by Pyodide, and the
1620 * `python_workers` compatibility flag is added automatically. Like async
1621 * Workers, Python Workers take no inline Effect implementation; declare
1622 * bindings with the `env` prop and read them from `self.env` in Python.
1623 *
1624 * Dependencies come from `pyproject.toml` next to the entry: Alchemy
1625 * vendors `[project.dependencies]` with [uv](https://docs.astral.sh/uv/)
1626 * against the Pyodide wheel index and uploads them under
1627 * `python_modules/`. If a `python_modules/` directory already exists
1628 * (e.g. produced by `pywrangler sync`), it is uploaded as-is and uv is
1629 * not invoked.
1630 *
1631 * See the [Python Workers guide](/cloudflare/compute/python-workers)
1632 * for the full walkthrough.
1633 *
1634 * **Example:** Defining a Python Worker in your stack
1635 * ```typescript
1636 * // alchemy.run.ts
1637 * const kv = yield* Cloudflare.KV.Namespace("Cache");
1638 *
1639 * export const Worker = Cloudflare.Worker("Worker", {
1640 * main: "./src/worker.py",
1641 * env: { CACHE: kv },
1642 * });
1643 * ```
1644 *
1645 * **Example:** Writing the Python handler
1646 * ```python
1647 * # src/worker.py
1648 * from workers import Response, WorkerEntrypoint
1649 *
1650 * class Default(WorkerEntrypoint):
1651 * async def fetch(self, request):
1652 * cached = await self.env.CACHE.get("greeting")
1653 * return Response(cached or "Hello from Python!")
1654 * ```
1655 *
1656 * **Example:** Vendoring dependencies with pyproject.toml
1657 * ```toml
1658 * # src/pyproject.toml — vendored with uv on deploy
1659 * [project]
1660 * name = "my-worker"
1661 * version = "0.1.0"
1662 * requires-python = ">=3.13"
1663 * dependencies = ["humanize"]
1664 * ```
1665 *
1666 * ### Effect Workers
1667 * Pass the Effect implementation as the third argument. This is the
1668 * simplest Effect-based approach — everything lives in one file.
1669 * Convenient for standalone Workers that don't need to be referenced
1670 * by other Workers.
1671 *
1672 * **Example:** Worker Effect
1673 * ```typescript
1674 * export default class MyWorker extends Cloudflare.Worker<MyWorker>()(
1675 * "MyWorker",
1676 * { main: import.meta.url },
1677 * Effect.gen(function* () {
1678 * // Construction: bind resources
1679 * const kv = yield* Cloudflare.KV.ReadWriteNamespace(MyKV);
1680 *
1681 * return {
1682 * // Runtime: use them
1683 * fetch: Effect.gen(function* () {
1684 * const value = yield* kv.get("key");
1685 * return HttpServerResponse.text(value ?? "not found");
1686 * }),
1687 * };
1688 * }),
1689 * ) {}
1690 * ```
1691 *
1692 * ### Worker Layer
1693 * When two Workers need to reference each other (e.g. WorkerA calls
1694 * WorkerB and vice versa), or you simply want optimal tree-shaking,
1695 * define the Worker class separately from its `.make()` call. The
1696 * class is a lightweight identifier; `.make()` provides the runtime
1697 * implementation as an `export default`. Rolldown treats `.make()`
1698 * as pure, so any Worker that imports the class to bind it will not
1699 * pull in the `.make()` dependencies — the bundler tree-shakes
1700 * them away entirely.
1701 *
1702 * The class and `.make()` can live in the same file. This is the
1703 * same pattern used by `Container` and `DurableObject`,
1704 * and is recommended for any cross-Worker or cross-DO bindings.
1705 *
1706 * **Example:** Worker Layer (class + .make() in one file)
1707 * ```typescript
1708 * // src/WorkerB.ts — the tag carries the name + RPC shape; props live
1709 * // on `.make()`.
1710 * export class WorkerB extends Cloudflare.Worker<
1711 * WorkerB,
1712 * { greet: (name: string) => Effect.Effect<string> }
1713 * >()("WorkerB") {}
1714 *
1715 * export default WorkerB.make(
1716 * { main: import.meta.url },
1717 * Effect.gen(function* () {
1718 * // Construction: bind resources
1719 * const kv = yield* Cloudflare.KV.ReadWriteNamespace(MyKV);
1720 *
1721 * return {
1722 * // Runtime: use them
1723 * greet: (name: string) =>
1724 * Effect.gen(function* () {
1725 * yield* kv.put("last-greeted", name);
1726 * return `Hello ${name}`;
1727 * }),
1728 * };
1729 * }),
1730 * );
1731 * ```
1732 *
1733 * **Example:** Binding a Worker Layer from another Worker
1734 * ```typescript
1735 * // src/WorkerA.ts — imports WorkerB; bundler tree-shakes .make()
1736 * import WorkerB from "./WorkerB.ts";
1737 *
1738 * export default class WorkerA extends Cloudflare.Worker<WorkerA>()(
1739 * "WorkerA",
1740 * { main: import.meta.url },
1741 * Effect.gen(function* () {
1742 * const b = yield* Cloudflare.Workers.bindWorker(WorkerB);
1743 * return {
1744 * fetch: Effect.gen(function* () {
1745 * return yield* b.greet("world");
1746 * }),
1747 * };
1748 * }),
1749 * ) {}
1750 * ```
1751 *
1752 * ### Configuration
1753 * The props object controls compatibility flags, static assets, and
1754 * build options. These are evaluated at deploy time.
1755 *
1756 * **Example:** Enabling Node.js compatibility
1757 * ```typescript
1758 * {
1759 * main: import.meta.url,
1760 * compatibility: {
1761 * flags: ["nodejs_compat"],
1762 * date: "2026-08-31",
1763 * },
1764 * }
1765 * ```
1766 *
1767 * **Example:** Serving static assets
1768 * ```typescript
1769 * {
1770 * main: import.meta.url,
1771 * assets: "./public",
1772 * }
1773 * ```
1774 *
1775 * **Example:** Run the Worker before the asset layer
1776 *
1777 * A path that matches no asset already falls through to the Worker.
1778 * `runWorkerFirst` routes the listed paths (or, with `true`, every
1779 * request) through the Worker ahead of asset matching, for an asset
1780 * that would otherwise shadow a route or an SPA fallback that would
1781 * answer an API call. A `_headers` or `_redirects` file in the
1782 * directory is applied automatically and `.assetsignore` excludes
1783 * files from the upload.
1784 * ```typescript
1785 * {
1786 * main: import.meta.url,
1787 * assets: {
1788 * directory: "./public",
1789 * runWorkerFirst: ["/api/*", "/admin/*"],
1790 * },
1791 * }
1792 * ```
1793 *
1794 * **Example:** Assets-only Worker (static site)
1795 * Omit `main` and `script` entirely to deploy a static site: no Worker
1796 * code is uploaded — Cloudflare's asset layer serves every request and
1797 * applies `htmlHandling` / `notFoundHandling` (including SPA fallback)
1798 * itself, exactly like an assets-only `wrangler deploy`.
1799 * ```typescript
1800 * const site = yield* Cloudflare.Worker("Site", {
1801 * assets: {
1802 * directory: "./public",
1803 * htmlHandling: "drop-trailing-slash",
1804 * notFoundHandling: "404-page",
1805 * },
1806 * domain: "static.example.com",
1807 * });
1808 * ```
1809 *
1810 * **Example:** Zone routes
1811 * ```typescript
1812 * {
1813 * main: import.meta.filename,
1814 * routes: [
1815 * { pattern: "api.example.com/*", zoneName: "example.com" },
1816 * { pattern: "example.com/api/*", zoneId: "<YOUR_ZONE_ID>" },
1817 * ],
1818 * }
1819 * ```
1820 *
1821 * **Example:** Deploying a prebuilt Worker without bundling
1822 * When `main` already points at a complete, runtime-ready ESM bundle
1823 * produced by an external tool (e.g. OpenNext), set `bundle: false` to
1824 * upload it byte-for-byte. The entry's directory is walked recursively
1825 * and every file matching the module rules (by default `.js`, `.mjs`,
1826 * `.wasm`, `.txt`, `.html`, `.sql`, and `.bin`) is uploaded as an
1827 * additional module named by its path relative to that directory.
1828 * ```typescript
1829 * {
1830 * main: "./.open-next/worker.js",
1831 * bundle: false,
1832 * assets: "./.open-next/assets",
1833 * }
1834 * ```
1835 *
1836 * ### Bundling & Tree-shaking
1837 * `main` is bundled with rolldown at deploy time. Unused code is
1838 * tree-shaken. `effect`, alchemy, and `@distilled.cloud` are marked
1839 * pure so unused parts prune more aggressively. Your app is not
1840 * marked pure.
1841 *
1842 * **Example:** Mark additional packages as pure
1843 * Only list packages with no top-level side effects.
1844 * ```typescript
1845 * {
1846 * main: "./src/worker.ts",
1847 * build: {
1848 * pure: { packages: ["my-lib", "@my-scope/*"] },
1849 * },
1850 * }
1851 * ```
1852 *
1853 * **Example:** Replace Node modules with Worker-compatible stubs
1854 * Use Rolldown's `build.input.resolve.alias` for module replacements.
1855 * Aliases apply to imports and static `require()` calls before Node
1856 * compatibility shims. Use absolute paths for file replacements.
1857 * Keep bundling enabled: `bundle: false` uploads files unchanged and
1858 * does not apply aliases. Alternatively, apply aliases in your external
1859 * build before uploading its output with `bundle: false`.
1860 * ```typescript
1861 * import * as Path from "effect/Path";
1862 *
1863 * const path = yield* Path.Path;
1864 * const stub = yield* path.fromFileUrl(
1865 * new URL("./.mastra/output/module-stub.mjs", import.meta.url),
1866 * );
1867 * const worker = yield* Cloudflare.Worker("Worker", {
1868 * main: "./.mastra/output/index.mjs",
1869 * compatibility: {
1870 * date: "2025-04-01",
1871 * flags: ["nodejs_compat", "nodejs_compat_populate_process_env"],
1872 * },
1873 * build: {
1874 * input: { resolve: { alias: { module: stub, "node:module": stub } } },
1875 * },
1876 * });
1877 * ```
1878 *
1879 * **Example:** Turn it off
1880 * ```typescript
1881 * {
1882 * main: "./src/worker.ts",
1883 * build: { pure: false },
1884 * }
1885 * ```
1886 *
1887 * ### URLs & Domains
1888 * Every URL that serves the Worker is collected in `worker.urls`, most
1889 * significant first, and `worker.url` is always `urls[0]`. The ranking:
1890 * the canonical custom domain (`domain.name`), then aliases in declared
1891 * order, then the stable `workers.dev` URL, then version preview URLs.
1892 * Under `alchemy dev`, `urls` is the local dev server's
1893 * `[localhost, ...LAN]` addresses instead. Redirect hostnames never
1894 * appear in `urls` — they serve no content.
1895 *
1896 * The `workersDev` prop controls the `workers.dev` surface (`true` by
1897 * default = stable URL + version previews; `false` = neither; object form
1898 * toggles independently), and the `domain` prop attaches custom domains —
1899 * DNS records and edge certificates are managed automatically.
1900 *
1901 * **Example:** Custom domain with aliases and redirects
1902 * ```typescript
1903 * const worker = yield* Cloudflare.Worker("Api", {
1904 * main: "./src/api.ts",
1905 * domain: {
1906 * name: "example.com",
1907 * zoneId: "<YOUR_ZONE_ID>",
1908 * aliases: ["www.example.com"],
1909 * redirects: ["old.example.com"], // 301 → https://example.com
1910 * },
1911 * });
1912 * // worker.url === "https://example.com"
1913 * // worker.urls === ["https://example.com", "https://www.example.com",
1914 * // "https://<name>.<account>.workers.dev"]
1915 * ```
1916 *
1917 * **Example:** workers.dev toggles
1918 * ```typescript
1919 * // No workers.dev URLs at all:
1920 * { main: "./src/api.ts", workersDev: false, domain: "api.example.com" }
1921 *
1922 * // Previews only — each deploy's version preview URL becomes worker.url:
1923 * { main: "./src/api.ts", workersDev: { enabled: false, previewsEnabled: true } }
1924 * ```
1925 *
1926 * **Example:** All URLs as a CORS allow-list
1927 * ```typescript
1928 * const site = yield* Cloudflare.Worker("Site", {
1929 * main: "./src/site.ts",
1930 * domain: { name: "example.com", aliases: ["www.example.com"] },
1931 * });
1932 * const api = yield* Cloudflare.Worker("Api", {
1933 * main: "./src/api.ts",
1934 * env: { ALLOWED_ORIGINS: site.urls },
1935 * });
1936 * ```
1937 *
1938 * ### Worker Previews
1939 * The `preview` prop maps Cloudflare's
1940 * [Worker Previews](https://developers.cloudflare.com/workers/previews/) —
1941 * a named copy of a Worker with its own URL, variables, secrets, bindings,
1942 * and isolated same-Worker Durable Object state. Use it for branch and
1943 * pull-request testing. Distinct from {@link version}: a version is an
1944 * immutable upload onto the parent script (gradual rollouts, canaries);
1945 * a Preview does not take production traffic.
1946 *
1947 * A Preview Worker's `url` is its stable Preview URL
1948 * (`<name>-<parent>.<subdomain>.workers.dev`, or
1949 * `<name>.<domain>` when the parent has `domain.previews`). The name
1950 * defaults to the stack stage (override with `preview.name`). Destroying
1951 * the Preview Worker deletes the Preview; the parent is untouched.
1952 *
1953 * **Example:** PR preview of another stage's Worker
1954 * ```typescript
1955 * const parent = yield* Cloudflare.Worker.ref("Api", { stage: "prod" });
1956 * const preview = yield* Cloudflare.Worker("Api", {
1957 * main: "./src/api.ts",
1958 * preview: { of: parent, message: `PR #${process.env.PR_NUMBER}` },
1959 * });
1960 * // preview.url -> https://<stage>-<name>.<subdomain>.workers.dev
1961 * ```
1962 *
1963 * **Example:** Custom-domain Preview URLs
1964 * ```typescript
1965 * // On the production Worker:
1966 * yield* Cloudflare.Worker("Api", {
1967 * main: "./src/api.ts",
1968 * domain: { name: "app.example.com", previews: true },
1969 * });
1970 * // A Preview of that Worker is then at https://<preview-name>.app.example.com
1971 * ```
1972 *
1973 * ### Versions & Gradual Deployments
1974 * The `version` prop maps Cloudflare's
1975 * [versions and gradual deployments](https://developers.cloudflare.com/workers/configuration/versions-and-deployments/)
1976 * onto Alchemy stages. A Worker with `version.parent` set uploads an
1977 * immutable *version* to the parent Worker's script instead of creating its
1978 * own — give it `traffic` to run it as a canary, or use `version.traffic`
1979 * on a normal Worker to roll out its own deploys gradually. For branch
1980 * and pull-request testing, use {@link preview} instead.
1981 *
1982 * A version worker's `url` is its *aliased* preview URL
1983 * (`<alias>-<name>.<subdomain>.workers.dev`) — the alias is derived from
1984 * the stack, stage, and logical id (override with `version.alias`), so the
1985 * URL is stable across deploys and always points at the latest uploaded
1986 * version. The per-version URL (`<version-prefix>-...`) is also returned
1987 * in `domains`. Because the aliased URL is known before the version
1988 * exists, `Worker.URL` works on version workers and resolves to it.
1989 *
1990 * A version carries code, static assets, bindings, and compatibility
1991 * settings. Script-level settings (routes, domains, crons, tags,
1992 * observability, …) belong to the parent and are rejected on version workers,
1993 * as are locally-hosted Durable Object or Workflow classes. Preview URLs
1994 * require the parent's workers.dev subdomain to be enabled (the default).
1995 *
1996 * **Example:** Upload a version without routing traffic
1997 * ```typescript
1998 * // Inspect this upload at its Version URL before a gradual rollout.
1999 * // For branch/PR testing, use `preview.of` instead.
2000 * yield* Cloudflare.Worker("MyWorker", {
2001 * main: "./src/worker.ts",
2002 * version: { traffic: 0, tag: process.env.GITHUB_SHA },
2003 * });
2004 * ```
2005 *
2006 * **Example:** Canary: send 10% of the parent's traffic to a version
2007 * ```typescript
2008 * const parent = yield* Cloudflare.Worker.ref("MyWorker", { stage: "prod" });
2009 * yield* Cloudflare.Worker("MyWorker", {
2010 * main: "./src/worker.ts",
2011 * version: { parent, traffic: 10 },
2012 * });
2013 * ```
2014 *
2015 * **Example:** Gradual rollout of a Worker's own deploy
2016 * ```typescript
2017 * // The new version takes 25% of traffic; the previously-live version
2018 * // keeps 75%. Bump traffic (or remove the prop) and re-deploy to promote.
2019 * yield* Cloudflare.Worker("MyWorker", {
2020 * main: "./src/worker.ts",
2021 * version: { traffic: 25 },
2022 * });
2023 * ```
2024 *
2025 * **Example:** Keep users on one version during the rollout
2026 * ```typescript
2027 * // Percentages route each request independently; affinity pins users by
2028 * // filling the Cloudflare-Workers-Version-Key header on zone traffic —
2029 * // here from the session cookie, falling back to the client IP. Requires
2030 * // a `domain` or `routes` (with `parent`, the parent's).
2031 * yield* Cloudflare.Worker("MyWorker", {
2032 * main: "./src/worker.ts",
2033 * domain: "api.example.com",
2034 * version: {
2035 * traffic: 25,
2036 * affinity: { cookie: "session_id", ip: true },
2037 * },
2038 * });
2039 * ```
2040 *
2041 * ### The Worker's own URL
2042 * `Worker.URL` injects the URL a Worker is served at as a binding on that
2043 * same Worker — the first custom `domain` if one is configured, otherwise
2044 * its `workers.dev` URL, always equal to the resource's `url` attribute.
2045 * Under `alchemy dev` it resolves to the local dev server's URL.
2046 *
2047 * **Example:** Read the Worker's own URL inside a handler
2048 * ```typescript
2049 * Cloudflare.Worker(
2050 * "Api",
2051 * { main: import.meta.url },
2052 * Effect.gen(function* () {
2053 * // Attaches the binding and returns a deferred accessor.
2054 * const url = yield* Cloudflare.Worker.URL;
2055 *
2056 * return {
2057 * fetch: Effect.gen(function* () {
2058 * const publicUrl = yield* url;
2059 * return yield* HttpServerResponse.json({ url: publicUrl });
2060 * }),
2061 * };
2062 * }),
2063 * );
2064 * ```
2065 *
2066 * **Example:** Inject the URL into an async Worker's env
2067 * `InferEnv` types the entry as `string`. A `VITE_`-prefixed key on a
2068 * vite-built Worker is additionally inlined into the client bundle as
2069 * `import.meta.env.VITE_PUBLIC_URL` at build time.
2070 * ```typescript
2071 * export const Worker = Cloudflare.Worker("Worker", {
2072 * main: "./src/worker.ts",
2073 * env: { PUBLIC_URL: Cloudflare.Worker.URL },
2074 * });
2075 *
2076 * export type WorkerEnv = Cloudflare.InferEnv<typeof Worker>;
2077 * // { PUBLIC_URL: string }
2078 * ```
2079 *
2080 * ### Observability
2081 * Cloudflare Workers Observability is on by default — `logs.enabled` and
2082 * `logs.invocationLogs` are turned on if you don't pass an `observability`
2083 * prop. Pass the prop yourself to tune sampling, enable persistence, or
2084 * turn on the new `traces` channel (the same toggle the dashboard's
2085 * Observability tab writes).
2086 *
2087 * Effect-native Workers should prefer `Cloudflare.Telemetry()` over
2088 * setting `observability.traces` by hand: providing the Layer enables
2089 * traces on this Worker and mirrors `Effect.withSpan` into the Cloudflare
2090 * waterfall. Pin `compatibility: { date: "2026-08-25" }` (or later) until
2091 * the global default date is raised past 2026-07-28.
2092 *
2093 * Field names match the Cloudflare API (camelCased): `headSamplingRate`,
2094 * `invocationLogs`, etc.
2095 *
2096 * **Example:** Enabling logs and traces
2097 * ```typescript
2098 * {
2099 * main: import.meta.url,
2100 * observability: {
2101 * enabled: true,
2102 * headSamplingRate: 1,
2103 * logs: {
2104 * enabled: true,
2105 * invocationLogs: true,
2106 * headSamplingRate: 1,
2107 * persist: true,
2108 * },
2109 * traces: {
2110 * enabled: true,
2111 * headSamplingRate: 1,
2112 * persist: true,
2113 * },
2114 * },
2115 * }
2116 * ```
2117 *
2118 * ### Tail Workers
2119 * A [Tail Worker](https://developers.cloudflare.com/workers/observability/logs/tail-workers/)
2120 * receives execution traces (console logs, exceptions, event metadata) from
2121 * other Workers. List it in a producer's `tailConsumers` and export a
2122 * `tail()` handler from the consumer; Cloudflare delivers each invocation's
2123 * trace events to every listed consumer after the invocation completes.
2124 *
2125 * **Example:** Sending a Worker's traces to a Tail Worker
2126 * ```typescript
2127 * const tailWorker = yield* Cloudflare.Worker("TailWorker", {
2128 * // exports: export default { async tail(events, env, ctx) { ... } }
2129 * main: "./src/tail.ts",
2130 * });
2131 *
2132 * const api = yield* Cloudflare.Worker("Api", {
2133 * main: "./src/api.ts",
2134 * tailConsumers: [tailWorker],
2135 * });
2136 * ```
2137 *
2138 * A *streaming* Tail Worker receives the same invocation's events live,
2139 * while the producer is still executing: list it in
2140 * `streamingTailConsumers` and export a `tailStream()` handler that is
2141 * invoked with the invocation's `onset` event and returns a handler for
2142 * every subsequent event of the session, ending with the terminal
2143 * `outcome`.
2144 *
2145 * **Example:** Streaming a Worker's events to a streaming Tail Worker
2146 * ```typescript
2147 * const streamTailWorker = yield* Cloudflare.Worker("StreamTailWorker", {
2148 * // exports: export default {
2149 * // tailStream(onset, env, ctx) {
2150 * // return (event) => { ... }; // log, spanOpen, ..., outcome
2151 * // },
2152 * // }
2153 * main: "./src/stream-tail.ts",
2154 * });
2155 *
2156 * const api = yield* Cloudflare.Worker("Api", {
2157 * main: "./src/api.ts",
2158 * streamingTailConsumers: [streamTailWorker],
2159 * });
2160 * ```
2161 *
2162 * ### Workers Cache
2163 * Workers Cache puts a regionally tiered cache in front of the Worker —
2164 * cache hits are served from the edge without invoking the Worker (and
2165 * without billing CPU time). In an Effect-native Worker, enable it by
2166 * yielding `Cloudflare.cache()` in the Construction phase, which also returns the
2167 * runtime purge client; async Workers use the `cache` prop instead. Control
2168 * what gets cached from your handlers via standard response headers:
2169 * `Cache-Control` (including `stale-while-revalidate`), `Cache-Tag` for
2170 * tag-based purging, and `Vary` for content negotiation.
2171 *
2172 * The cache is scoped to a single Worker version by default, so every
2173 * deploy starts cold. Set `crossVersionCache: true` to share cached
2174 * responses across versions.
2175 *
2176 * **Example:** Enabling and purging the cache in an Effect Worker
2177 * ```typescript
2178 * Effect.gen(function* () {
2179 * // Construction: enable Workers Cache on this Worker
2180 * const { purge } = yield* Cloudflare.cache({ crossVersionCache: true });
2181 *
2182 * return {
2183 * fetch: Effect.gen(function* () {
2184 * const request = yield* HttpServerRequest;
2185 * if (request.url.startsWith("/invalidate")) {
2186 * yield* purge({ tags: ["products"] });
2187 * return HttpServerResponse.text("purged");
2188 * }
2189 * return HttpServerResponse.text("hello", {
2190 * headers: {
2191 * "Cache-Control": "public, max-age=300, stale-while-revalidate=3600",
2192 * "Cache-Tag": "products,product:123",
2193 * },
2194 * });
2195 * }),
2196 * };
2197 * })
2198 * ```
2199 *
2200 * **Example:** Enabling Workers Cache on an async Worker
2201 * ```typescript
2202 * {
2203 * main: "./src/worker.ts",
2204 * cache: {
2205 * enabled: true,
2206 * crossVersionCache: true,
2207 * },
2208 * }
2209 * ```
2210 *
2211 * ### Background Work & Scopes
2212 * Each incoming event (fetch, RPC call, scheduled run) gets its own Effect
2213 * `Scope`. When the handler finishes, the bridge closes that scope and
2214 * registers the close promise with workerd's `ctx.waitUntil` — so
2215 * finalizers added with `Effect.addFinalizer` inside a handler run *after*
2216 * the response is sent, without blocking it, and the Worker stays alive
2217 * until they settle. Streaming responses transfer the scope to the stream,
2218 * so those finalizers run when the stream completes instead.
2219 *
2220 * For ad-hoc background work, `WorkerExecutionContext.waitUntil(effect)`
2221 * forks an Effect with the caller's full context and keeps the invocation
2222 * alive until it settles. The context can be yielded once in the
2223 * constructor and used from any handler; its methods are `RuntimeContext`-
2224 * colored, so they can only run inside a handler.
2225 *
2226 * The constructor is evaluated once per isolate: the bridge builds the
2227 * Worker's layer stack on the first event and every later event reuses the
2228 * built services. Resolve services, bind resources, build handlers there —
2229 * one-shot I/O that caches a plain value (e.g. fetching a secret for a
2230 * client) is fine, but nothing disposable: the build scope is never closed
2231 * (workerd has no isolate-teardown hook), so a finalizer added in the
2232 * constructor never runs, and I/O-backed objects (sockets, response bodies) are
2233 * pinned to the request that created them. Anything that needs cleanup
2234 * belongs in a handler, where `Effect.addFinalizer` attaches to the
2235 * per-event scope.
2236 *
2237 * **Example:** Post-response cleanup with a scope finalizer
2238 * ```typescript
2239 * return {
2240 * fetch: Effect.gen(function* () {
2241 * // runs after this response is sent, kept alive by waitUntil
2242 * yield* Effect.addFinalizer(() => flushMetrics().pipe(Effect.ignore));
2243 * return HttpServerResponse.text("ok");
2244 * }),
2245 * };
2246 * ```
2247 *
2248 * **Example:** Background work with waitUntil
2249 * ```typescript
2250 * // Construction
2251 * const exec = yield* Cloudflare.WorkerExecutionContext;
2252 *
2253 * return {
2254 * fetch: Effect.gen(function* () {
2255 * // respond now; the audit write completes in the background
2256 * yield* exec.waitUntil(writeAuditLog(event));
2257 * return HttpServerResponse.text("accepted", { status: 202 });
2258 * }),
2259 * };
2260 * ```
2261 *
2262 * ### R2 Bucket
2263 * Bind an R2 bucket in the Construction phase with `Cloudflare.R2.ReadWriteBucket`.
2264 * The returned handle exposes `get`, `put`, `delete`, and `list`
2265 * methods you can call in your runtime handlers.
2266 *
2267 * **Example:** Binding and using R2
2268 * ```typescript
2269 * // Construction
2270 * const bucket = yield* Cloudflare.R2.ReadWriteBucket(MyBucket);
2271 *
2272 * return {
2273 * fetch: Effect.gen(function* () {
2274 * const request = yield* HttpServerRequest;
2275 * const key = request.url.split("/").pop()!;
2276 *
2277 * if (request.method === "GET") {
2278 * const object = yield* bucket.get(key);
2279 * return object
2280 * ? HttpServerResponse.text(yield* object.text())
2281 * : HttpServerResponse.empty({ status: 404 });
2282 * }
2283 *
2284 * yield* bucket.put(key, request.stream);
2285 * return HttpServerResponse.empty({ status: 201 });
2286 * }),
2287 * };
2288 * ```
2289 *
2290 * ### KV Namespace
2291 * Bind a KV namespace with `Cloudflare.KV.ReadWriteNamespace`. KV provides
2292 * eventually-consistent, low-latency key-value reads replicated
2293 * globally across Cloudflare's edge.
2294 *
2295 * **Example:** Binding and using KV
2296 * ```typescript
2297 * // Construction
2298 * const kv = yield* Cloudflare.KV.ReadWriteNamespace(MyKV);
2299 *
2300 * return {
2301 * fetch: Effect.gen(function* () {
2302 * const value = yield* kv.get("my-key");
2303 * return HttpServerResponse.text(value ?? "not found");
2304 * }),
2305 * };
2306 * ```
2307 *
2308 * ### D1 Database
2309 * Bind a D1 database with `Cloudflare.D1.QueryDatabase`. D1 is a
2310 * serverless SQLite database — use `prepare` to build parameterized
2311 * queries and `all`, `first`, or `run` to execute them.
2312 *
2313 * **Example:** Binding and querying D1
2314 * ```typescript
2315 * // Construction
2316 * const db = yield* Cloudflare.D1.QueryDatabase(MyDatabase);
2317 *
2318 * return {
2319 * fetch: Effect.gen(function* () {
2320 * const results = yield* db
2321 * .prepare("SELECT * FROM users WHERE id = ?")
2322 * .bind(userId)
2323 * .all();
2324 * return yield* HttpServerResponse.json(results);
2325 * }),
2326 * };
2327 * ```
2328 *
2329 * ### Durable Objects
2330 * Yield a `DurableObject` class in the Construction phase to get a
2331 * namespace handle. Call `getByName` or `getById` to get a typed RPC
2332 * stub, then call its methods from your runtime handlers.
2333 *
2334 * **Example:** Using a Durable Object
2335 * ```typescript
2336 * // Construction
2337 * const counters = yield* Counter;
2338 *
2339 * return {
2340 * fetch: Effect.gen(function* () {
2341 * const counter = counters.getByName("user-123");
2342 * const value = yield* counter.increment();
2343 * return HttpServerResponse.text(String(value));
2344 * }),
2345 * };
2346 * ```
2347 *
2348 * ### Containers
2349 * Containers run long-lived processes alongside Durable Objects.
2350 * Provide `Cloudflare.Containers.layer(Sandbox, …)` on a DO's constructor to
2351 * bind, start, and monitor the container; then `yield* Sandbox`
2352 * resolves the **running** instance. Call its typed methods or use
2353 * `getTcpPort` to make HTTP requests to its exposed ports.
2354 *
2355 * **Example:** Running a Container from a Durable Object
2356 * ```typescript
2357 * export default class Agent extends Cloudflare.DurableObject<Agent>()(
2358 * "Agents",
2359 * Effect.gen(function* () {
2360 * const sandbox = yield* Sandbox;
2361 *
2362 * return Effect.gen(function* () {
2363 * return {
2364 * exec: (cmd: string) => sandbox.exec(cmd),
2365 * health: () =>
2366 * Effect.gen(function* () {
2367 * const { fetch } = yield* sandbox.getTcpPort(3000);
2368 * const res = yield* fetch(
2369 * HttpClientRequest.get("http://container/health"),
2370 * );
2371 * return yield* res.text;
2372 * }),
2373 * };
2374 * });
2375 * }).pipe(
2376 * Effect.provide(
2377 * Cloudflare.Containers.layer(Sandbox, { enableInternet: true }),
2378 * ),
2379 * ),
2380 * ) {}
2381 * ```
2382 *
2383 * ### Dynamic Workers
2384 * `WorkerLoader` lets you spin up ephemeral Workers at runtime
2385 * from inline JavaScript modules. This is useful for sandboxing
2386 * user-provided code or running untrusted scripts in isolation.
2387 *
2388 * **Example:** Loading a dynamic Worker
2389 * ```typescript
2390 * // Construction
2391 * const loader = yield* Cloudflare.WorkerLoader("Loader");
2392 *
2393 * return {
2394 * fetch: Effect.gen(function* () {
2395 * const worker = yield* loader.load({
2396 * compatibilityDate: "2026-08-31",
2397 * mainModule: "worker.js",
2398 * modules: {
2399 * "worker.js": `export default {
2400 * async fetch(req) { return new Response("sandboxed"); }
2401 * }`,
2402 * },
2403 * });
2404 *
2405 * const res = yield* worker.fetch(
2406 * HttpClientRequest.get("https://worker/"),
2407 * );
2408 * return HttpServerResponse.fromClientResponse(res);
2409 * }),
2410 * };
2411 * ```
2412 *
2413 * @resource
2414 * @product Workers
2415 * @category Workers & Compute
2416 */
2417export const Worker: ResourceClassLike<Worker> &
2418 Pick<ResourceClass<Worker>, "ref"> &
2419 Effect.Effect<
2420 Worker & WorkerRuntimeContext & RuntimeContext,
2421 never,
2422 Worker
2423 > & {
2424 <Self, Shape extends WorkerShape, Deps = never>(): {
2425 <const Id extends string>(
2426 id: Id,
2427 ): Effect.Effect<
2428 Worker & Rpc<Self> & Dependencies<Deps>,
2429 never,
2430 Self | Extract<Deps, Container.Application<any>> | Providers
2431 > &
2432 Named<Id> &
2433 PlatformIdentity<Id> & {
2434 new (
2435 _: never,
2436 ): MakeShape<Shape, WorkerShape> & Named<Id> & Tag<WorkerTypeId>;
2437 of(shape: Shape & WorkerShape): MakeShape<Shape, WorkerShape>;
2438 make<PropsReq = never, InitReq = never>(
2439 props:
2440 | InputProps<WorkerProps>
2441 | Effect.Effect<InputProps<WorkerProps>, ConfigError, PropsReq>,
2442 impl: Effect.Effect<Shape, ConfigError, InitReq>,
2443 ): Layer.Layer<
2444 Self,
2445 never,
2446 | Extract<Deps, Container.Application<any>>
2447 | Providers
2448 | Exclude<
2449 PropsReq | InitReq,
2450 Self | WorkerServices | Tag<WorkerTypeId>
2451 >
2452 >;
2453 };
2454 };
2455 <Self>(): {
2456 <
2457 const Id extends string,
2458 Shape extends WorkerShape,
2459 Req extends
2460 | WorkerServices
2461 | Container.Application<any>
2462 | PlatformServices
2463 | Tag,
2464 PropsReq = never,
2465 >(
2466 id: Id,
2467 props:
2468 | InputProps<WorkerProps>
2469 | Effect.Effect<InputProps<WorkerProps>, ConfigError, PropsReq>,
2470 impl: Effect.Effect<Shape, ConfigError, Req>,
2471 ): Effect.Effect<
2472 Worker & Rpc<Self>,
2473 never,
2474 Extract<Req, Container.Application<any>> | Providers | PropsReq
2475 > &
2476 Named<Id> &
2477 PlatformIdentity<Id> & {
2478 new (): MakeShape<Shape, WorkerShape> & Named<Id> & Tag<WorkerTypeId>;
2479 };
2480 /**
2481 * Class form without an implementation — an external Worker (a plain
2482 * bundled `main`, a raw `script`, or an assets-only Worker with no
2483 * script at all):
2484 *
2485 * ```typescript
2486 * export class Site extends Cloudflare.Worker<Site>()("Site", {
2487 * assets: { directory: "./public" },
2488 * }) {}
2489 * ```
2490 */
2491 <
2492 const Id extends string,
2493 const Bindings extends WorkerBindingProps = {},
2494 const Assets extends WorkerAssetsConfig | undefined = undefined,
2495 Req = never,
2496 >(
2497 id: Id,
2498 props:
2499 | InputProps<WorkerProps<Bindings, Assets>>
2500 | Effect.Effect<
2501 InputProps<WorkerProps<Bindings, Assets>>,
2502 ConfigError,
2503 Req
2504 >,
2505 ): Effect.Effect<
2506 ExternalWorker<NormalizedBindings<Bindings, Assets>> & Rpc<{}>,
2507 never,
2508 Req | Providers
2509 > &
2510 Named<Id> &
2511 PlatformIdentity<Id> & {
2512 new (): Named<Id> &
2513 Tag<WorkerTypeId> & {
2514 /** @internal phantom */
2515 readonly "~alchemy/WorkerEnv": NormalizedBindings<
2516 Bindings,
2517 Assets
2518 >;
2519 };
2520 };
2521 };
2522 <
2523 const Bindings extends WorkerBindingProps = {},
2524 const Assets extends WorkerAssetsConfig | undefined = undefined,
2525 Req = never,
2526 const Id extends string = string,
2527 >(
2528 id: Id,
2529 props:
2530 | InputProps<WorkerProps<Bindings, Assets>>
2531 | Effect.Effect<
2532 InputProps<WorkerProps<Bindings, Assets>>,
2533 ConfigError,
2534 Req
2535 >,
2536 ): Effect.Effect<
2537 ExternalWorker<{
2538 [
2539 binding in keyof NormalizedBindings<Bindings, Assets>
2540 ]: NormalizedBindings<Bindings, Assets>[binding];
2541 }> &
2542 Rpc<{}>,
2543 never,
2544 Req | Providers
2545 > &
2546 PlatformIdentity<Id>;
2547 <
2548 const Id extends string,
2549 Shape extends WorkerShape,
2550 Req extends
2551 | WorkerServices
2552 | Container.Application<any>
2553 | PlatformServices,
2554 >(
2555 id: Id,
2556 props: InputProps<WorkerProps>,
2557 impl: Effect.Effect<Shape, ConfigError, Req>,
2558 ): Effect.Effect<
2559 Worker & Rpc<Shape>,
2560 never,
2561 Extract<Req, Container.Application<any>> | Providers
2562 > &
2563 Named<Id> &
2564 PlatformIdentity<Id>;
2565 /**
2566 * The Worker's own public URL, injected as a binding on that same Worker.
2567 * Declare it on `env` (`env: { VITE_PUBLIC_URL: Worker.URL }`) or
2568 * `yield*` it inside an Effect-native Worker to obtain a deferred
2569 * accessor. See {@link URLEffect}.
2570 */
2571 readonly URL: URLEffect;
2572 } = Platform(
2573 WorkerTypeId,
2574 {
2575 // WorkerAsyncBindings imports isWorker; defer access until module initialization completes.
2576 onCreate: (resource, props) =>
2577 bindWorkerAsyncBindings(resource as Worker, props),
2578 createRuntimeContext: (id) => makeWorkerRuntimeContext(id),
2579 },
2580 { URL },
2581);