| 1 | import type * as cf from "@cloudflare/workers-types"; |
| 2 | import * as workers from "@distilled.cloud/cloudflare/workers"; |
| 3 | import type { ConfigError } from "effect/Config"; |
| 4 | import * as Effect from "effect/Effect"; |
| 5 | import type * as Layer from "effect/Layer"; |
| 6 | import * as Redacted from "effect/Redacted"; |
| 7 | import { type MemoOptions } from "../../Command/Memo.ts"; |
| 8 | import type { Dependencies } from "../../Dependencies.ts"; |
| 9 | import type { InputProps } from "../../Input.ts"; |
| 10 | import type { Named, Tag } from "../../Named.ts"; |
| 11 | import type * as Output from "../../Output.ts"; |
| 12 | import { |
| 13 | Platform, |
| 14 | type Main, |
| 15 | type MainRpc, |
| 16 | type MakeShape, |
| 17 | type PlatformIdentity, |
| 18 | type PlatformProps, |
| 19 | type PlatformServices, |
| 20 | } from "../../Platform.ts"; |
| 21 | import { |
| 22 | isResourceOfType, |
| 23 | Resource, |
| 24 | type ResourceClass, |
| 25 | type ResourceClassLike, |
| 26 | } from "../../Resource.ts"; |
| 27 | import type { Rpc } from "../../Rpc.ts"; |
| 28 | import type { RuntimeContext } from "../../RuntimeContext.ts"; |
| 29 | import type { Self as SelfService } from "../../Self.ts"; |
| 30 | import { CloudflareEnvironment } from "../CloudflareEnvironment.ts"; |
| 31 | import type { Container } from "../Containers/Container.ts"; |
| 32 | import type { DevContainerImage } from "../Containers/ContainerApplication.ts"; |
| 33 | import type { DevOrigin } from "../Hyperdrive/Connection.ts"; |
| 34 | import type { Providers } from "../Providers.ts"; |
| 35 | import type { DispatchNamespace } from "../WorkersForPlatforms/DispatchNamespace.ts"; |
| 36 | import type { WorkflowBinding, WorkflowLike } from "../Workflows/Workflow.ts"; |
| 37 | import type { Reference as ZoneReference } from "../Zone/lookup.ts"; |
| 38 | import { type Assets, type AssetsConfig, type AssetsProps } from "./Assets.ts"; |
| 39 | import type { |
| 40 | WorkerAccessConfig, |
| 41 | WorkerAccessIdentity, |
| 42 | } from "./WorkerAccess.ts"; |
| 43 | import { |
| 44 | WorkerEnvironment, |
| 45 | WorkerExecutionContext, |
| 46 | WorkerTypeId, |
| 47 | } from "./WorkerRuntime.ts"; |
| 48 | import { Request } from "./Request.ts"; |
| 49 | import type { ModuleRule } from "./Sources/Prebuilt.ts"; |
| 50 | import type { WorkerBuildOptions } from "./Sources/Rolldown.ts"; |
| 51 | import { bindWorkerAsyncBindings } from "./WorkerAsyncBindings.ts"; |
| 52 | import type { |
| 53 | WorkerBinding, |
| 54 | WorkerBindingResource, |
| 55 | WorkerBindings, |
| 56 | } from "./WorkerBinding.ts"; |
| 57 | import { |
| 58 | makeWorkerRuntimeContext, |
| 59 | type WorkerExport, |
| 60 | type WorkerRuntimeContext, |
| 61 | } from "./WorkerRuntimeContext.ts"; |
| 62 | |
| 63 | export * from "./WorkerRuntime.ts"; |
| 64 | |
| 65 | export 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 | */ |
| 73 | export 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 | |
| 81 | export interface WorkerObservability extends Exclude< |
| 82 | workers.PutScriptRequest["metadata"]["observability"], |
| 83 | undefined |
| 84 | > {} |
| 85 | |
| 86 | export interface WorkerLimits extends Exclude< |
| 87 | workers.PutScriptRequest["metadata"]["limits"], |
| 88 | undefined |
| 89 | > {} |
| 90 | |
| 91 | export interface WorkerCache extends Exclude< |
| 92 | workers.PutScriptRequest["metadata"]["cacheOptions"], |
| 93 | undefined |
| 94 | > {} |
| 95 | |
| 96 | export type WorkerPlacement = Exclude< |
| 97 | workers.PutScriptRequest["metadata"]["placement"], |
| 98 | undefined |
| 99 | >; |
| 100 | |
| 101 | export type WorkerServices = |
| 102 | | Worker |
| 103 | | Request |
| 104 | | WorkerExecutionContext |
| 105 | | WorkerEnvironment |
| 106 | | CloudflareEnvironment |
| 107 | | Container.Application<any> |
| 108 | | SelfService; |
| 109 | |
| 110 | export type WorkerShape<Req = never> = Main<WorkerServices | Req> & |
| 111 | MainRpc<WorkerServices | Req>; |
| 112 | |
| 113 | export 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 | |
| 124 | export type WorkerBindingProps = { |
| 125 | [bindingName in string]: |
| 126 | | WorkerBindingResource |
| 127 | | Effect.Effect<WorkerBindingResource, any, any>; |
| 128 | }; |
| 129 | |
| 130 | export 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 | */ |
| 155 | export type WorkerEnvBindings<Bindings> = { |
| 156 | readonly [B in keyof Bindings]: Bindings[B] extends WorkflowLike<infer Params> |
| 157 | ? WorkflowBinding<Params> |
| 158 | : Bindings[B]; |
| 159 | }; |
| 160 | |
| 161 | export 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 | */ |
| 171 | export 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 | */ |
| 196 | export 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 | |
| 258 | export 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 | */ |
| 309 | export 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 | */ |
| 365 | export 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 | */ |
| 473 | export 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 | |
| 516 | export 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 | */ |
| 997 | export 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 | |
| 1036 | export 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. |
| 1114 | export 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. */ |
| 1323 | export 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. */ |
| 1343 | const 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 | */ |
| 1351 | export 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 | */ |
| 1367 | export 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 | */ |
| 1391 | export 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 | */ |
| 1416 | export 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 | */ |
| 1440 | export const Self = { |
| 1441 | "~alchemy/Kind": "Cloudflare.Workers.Self", |
| 1442 | } as const; |
| 1443 | export type Self = typeof Self; |
| 1444 | |
| 1445 | /** Returns true when the value is the {@link Self} marker. */ |
| 1446 | export 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 | */ |
| 2417 | export 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 | ); |