| 1 | --- |
| 2 | title: 2.0.0-beta.70 - Web Frameworks & Full Local Dev |
| 3 | date: 2026-08-06T09:00:00Z |
| 4 | category: release |
| 5 | excerpt: v2.0.0-beta.70 deploys Next.js, Astro, Nuxt, SvelteKit, Waku, and Octane to Cloudflare Workers with zero Cloudflare-specific config, completes local emulation in alchemy dev (browser rendering, images, email, cron, secrets, Stream, tail consumers), adds renamedFrom() state migration for logical ID renames, plan-time data sources, cluster-agnostic Kubernetes workloads, MySQL clients, and migrates every SDK to distilled v1. |
| 6 | --- |
| 7 | |
| 8 | beta.70 deploys **Next.js, Astro, Nuxt, SvelteKit, Waku, and |
| 9 | Octane** to Cloudflare Workers — programmatic builds, no |
| 10 | `wrangler.json`, no adapter config. And `alchemy dev` now emulates |
| 11 | effectively the entire Cloudflare surface locally: browser |
| 12 | rendering, image transforms, email, cron triggers, secrets, |
| 13 | Stream, and tail consumers join KV, R2, D1, and Queues. The |
| 14 | release also adds plan-time data sources, moves Kubernetes |
| 15 | workloads out of EKS into a cluster-agnostic namespace, and |
| 16 | migrates every SDK to distilled v1. |
| 17 | |
| 18 | This post covers beta.67 through beta.70 — beta.67 was a small |
| 19 | fix release that didn't get its own notes, and beta.68/beta.69 |
| 20 | are superseded by beta.70. |
| 21 | |
| 22 | :::caution |
| 23 | **Behavior change** |
| 24 | |
| 25 | `Cloudflare.Website.StaticSite` no longer duplicates `env` |
| 26 | resources into its own namespace |
| 27 | ([#1053](https://github.com/alchemy-run/alchemy/pull/1053)) — a |
| 28 | shared KV namespace passed as `env` used to register a second |
| 29 | physical copy at `<site-id>/…`. As part of the fix, the site's |
| 30 | Worker moves from the `<id>/Worker` FQN to `<id>` — and thanks to |
| 31 | the new [`renamedFrom()`](#rename-resources-with-renamedfrom) |
| 32 | support, its state migrates in place on your next deploy: same |
| 33 | physical name, same `workers.dev` subdomain, nothing recreated. |
| 34 | Thanks [apostoli](https://github.com/apostolos-geyer)! |
| 35 | ::: |
| 36 | |
| 37 | ## The Website family: six new frameworks |
| 38 | |
| 39 | `Cloudflare.Website` grows from Vite and StaticSite to a full |
| 40 | framework family: |
| 41 | [`Nextjs`](/cloudflare/frontend/nextjs) |
| 42 | ([#923](https://github.com/alchemy-run/alchemy/pull/923)), |
| 43 | [`Astro`](/cloudflare/frontend/astro), |
| 44 | [`Nuxt`](/cloudflare/frontend/nuxt), |
| 45 | [`SvelteKit`](/cloudflare/frontend/sveltekit), |
| 46 | [`Waku`](/cloudflare/frontend/waku) |
| 47 | ([#1086](https://github.com/alchemy-run/alchemy/pull/1086)), and |
| 48 | [`Octane`](/cloudflare/frontend/octane) |
| 49 | ([#1093](https://github.com/alchemy-run/alchemy/pull/1093)). |
| 50 | |
| 51 | Every resource builds your project programmatically — no wrangler |
| 52 | binary, no `wrangler.json`, no adapter packages to install — and |
| 53 | deploys it as a Worker: server bundle plus static assets. Your |
| 54 | framework's own config file (`astro.config.*`, `vite.config.ts`, |
| 55 | `nuxt.config.ts`, ...) loads natively; Alchemy merges its |
| 56 | Cloudflare config over it. Builds are memoized by content-hashing |
| 57 | the input files, so an unchanged project skips the build and |
| 58 | deploy entirely. |
| 59 | |
| 60 | ```typescript |
| 61 | const site = yield* Cloudflare.Website.Nextjs("Site", { |
| 62 | env: { |
| 63 | GREETING: "Hello from Alchemy!", |
| 64 | UPLOADS: bucket, // any binding, via getCloudflareContext().env |
| 65 | }, |
| 66 | domain: "example.com", |
| 67 | }); |
| 68 | ``` |
| 69 | |
| 70 | Each resource returns a plain `Worker`, so the entire Worker |
| 71 | vocabulary applies: `env` takes any binding (KV, R2, Durable |
| 72 | Objects, secrets), plus `domain`, `compatibility`, and asset |
| 73 | routing. Framework specifics are handled per resource: |
| 74 | |
| 75 | **Next.js** builds through the OpenNext pipeline. With a KV |
| 76 | incremental cache and a Durable Object queue, **writable ISR** |
| 77 | fully works — time-based regeneration and on-demand |
| 78 | `revalidatePath`: |
| 79 | |
| 80 | ```typescript |
| 81 | const site = yield* Cloudflare.Website.Nextjs("Site", { |
| 82 | env: { |
| 83 | NEXT_INC_CACHE_KV: incCache, |
| 84 | NEXT_TAG_CACHE_KV: tagCache, |
| 85 | NEXT_CACHE_DO_QUEUE: Cloudflare.DurableObject("NEXT_CACHE_DO_QUEUE", { |
| 86 | className: "DOQueueHandler", // ships in the OpenNext bundle |
| 87 | }), |
| 88 | }, |
| 89 | }); |
| 90 | ``` |
| 91 | |
| 92 | **Astro** defaults to server output and auto-provisions a KV |
| 93 | namespace for zero-config |
| 94 | [sessions](https://docs.astro.build/en/guides/sessions/), bound |
| 95 | as `SESSION`: |
| 96 | |
| 97 | ```typescript |
| 98 | const site = yield* Cloudflare.Website.Astro("Site", { |
| 99 | env: { CACHE: kv, UPLOADS: bucket }, // via Astro.locals.runtime.env |
| 100 | }); |
| 101 | ``` |
| 102 | |
| 103 | **Nuxt** builds through nitro's `cloudflare_module` preset and |
| 104 | natively loads your `nuxt.config.ts` — the `nuxt` prop merges |
| 105 | over it: |
| 106 | |
| 107 | ```typescript |
| 108 | const site = yield* Cloudflare.Website.Nuxt("Site", { |
| 109 | nuxt: { routeRules: { "/about": { prerender: true } } }, |
| 110 | }); |
| 111 | ``` |
| 112 | |
| 113 | **SvelteKit** uses an in-memory Cloudflare adapter — no |
| 114 | `svelte.config.js` changes, no `@sveltejs/adapter-cloudflare`: |
| 115 | |
| 116 | ```typescript |
| 117 | const site = yield* Cloudflare.Website.SvelteKit("Site", { |
| 118 | env: { API_KEY: Alchemy.secret("API_KEY") }, // via platform.env |
| 119 | }); |
| 120 | ``` |
| 121 | |
| 122 | **Waku** serves RSC SSR at request time and prerenders SSG pages |
| 123 | at build time inside workerd. A custom entry can even host |
| 124 | Durable Objects alongside waku's fetch handler: |
| 125 | |
| 126 | ```typescript |
| 127 | const app = yield* Cloudflare.Website.Waku("App", { |
| 128 | main: "src/worker-entry.ts", // optional custom entry |
| 129 | env: { COUNTER: Cloudflare.DurableObject("Counter") }, |
| 130 | }); |
| 131 | ``` |
| 132 | |
| 133 | **Octane** deploys [OctaneJS](https://octanejs.dev) fullstack |
| 134 | apps through Octane's own Cloudflare adapter: |
| 135 | |
| 136 | ```typescript |
| 137 | const site = yield* Cloudflare.Website.Octane("Site", { |
| 138 | env: { CACHE: cache }, // via context.platform.env |
| 139 | }); |
| 140 | ``` |
| 141 | |
| 142 | All six run under `alchemy dev` on their framework's own dev |
| 143 | server — Turbopack HMR for Next.js (`nextjs: { devMode: "hmr" }`, |
| 144 | or the default preview parity under workerd), Vite HMR for the |
| 145 | rest — with real bindings resolved in-process. |
| 146 | |
| 147 | Docs: [Frontend frameworks](/cloudflare/frontend/frontends). |
| 148 | |
| 149 | ## `alchemy dev` covers the whole surface |
| 150 | |
| 151 | beta.66 emulated KV, R2, D1, and Queues locally. beta.70 finishes |
| 152 | the campaign |
| 153 | ([#1039](https://github.com/alchemy-run/alchemy/pull/1039)) — |
| 154 | miniflare parity and beyond: |
| 155 | |
| 156 | - **Cron triggers fire in dev** — `crons` schedule real timers, |
| 157 | and the miniflare-compatible `/cdn-cgi/handler/scheduled` route |
| 158 | triggers them manually. Previously `scheduled()` never ran under |
| 159 | `alchemy dev`. |
| 160 | - **Browser Rendering** — `env.BROWSER` drives a real local Chrome |
| 161 | over CDP; `@cloudflare/puppeteer` works unchanged. |
| 162 | - **Images** — `env.IMAGES` transforms run through sharp locally. |
| 163 | - **Email, both directions** — `send_email` validates and persists |
| 164 | sent mail as `.eml` files, and a `POST` to |
| 165 | `/cdn-cgi/handler/email` drives your `email()` handler with |
| 166 | accept, reject, and reply semantics. |
| 167 | - **Secrets Store** — a Worker binding a `secrets_store_secret` |
| 168 | now boots in dev, with the real (Redacted-sourced) value seeded |
| 169 | into the simulator. `secret_key` CryptoKey bindings boot too, |
| 170 | and gain a user-facing resource: `Cloudflare.Workers.SecretKey`. |
| 171 | - **Stream** — `Cloudflare.Stream.Stream("STREAM")` is a new |
| 172 | binding surface entirely (alchemy previously had none), working |
| 173 | live and against a local video-store simulator in dev. |
| 174 | - **Tail consumers** — the new `tailConsumers` prop uploads as |
| 175 | `tail_consumers` metadata live, and a local producer's trace |
| 176 | events reach a local tail Worker in dev. |
| 177 | - **Workflows and Hyperdrive** are now first-class dual providers, |
| 178 | so switching between dev and deploy is an engine-orchestrated |
| 179 | replacement instead of ad-hoc branching. |
| 180 | |
| 181 | `Alchemy.remote()` is now the single way to opt a binding onto the |
| 182 | real cloud during dev |
| 183 | ([#1065](https://github.com/alchemy-run/alchemy/pull/1065)). |
| 184 | Worker-only bindings gained `pipe()`, so the same aspect works on |
| 185 | plain binding values in an async Worker's `env`: |
| 186 | |
| 187 | ```typescript |
| 188 | // Effect-native Worker |
| 189 | const browser = yield* Cloudflare.Browser("BROWSER").pipe(Alchemy.remote()); |
| 190 | |
| 191 | // async Worker env |
| 192 | env: { IMAGES: Cloudflare.Images.Images("IMAGES").pipe(Alchemy.remote()) } |
| 193 | ``` |
| 194 | |
| 195 | And local and live compose into **hybrid topologies**. Pin a queue |
| 196 | live and everything around it stays local — the local Worker |
| 197 | produces real messages into the real queue, and its local consumer |
| 198 | drains the same queue through a pull loop, with the usual |
| 199 | batching, retry, and dead-letter semantics: |
| 200 | |
| 201 | ```typescript |
| 202 | // the queue is real, even in dev |
| 203 | const Jobs = Cloudflare.Queues.Queue("Jobs").pipe(Alchemy.remote()); |
| 204 | |
| 205 | // the worker is local: it produces into the real queue, and its |
| 206 | // consumer drains the real queue through a pull loop |
| 207 | export default Cloudflare.Worker("Processor", { main: import.meta.url }, |
| 208 | Effect.gen(function* () { |
| 209 | // subscribe and process messages from the real queue in the local worker |
| 210 | yield* Cloudflare.Queues.consumeQueueMessages(jobs, (stream) => |
| 211 | Stream.runForEach(stream, (msg) => processJob(msg.body)), |
| 212 | ); |
| 213 | }), |
| 214 | ) {} |
| 215 | ``` |
| 216 | |
| 217 | Your dev session sees the same traffic as staging — messages |
| 218 | produced by deployed services or a teammate's session land in |
| 219 | your local handler, and your local edits process them instantly. |
| 220 | |
| 221 | Docs: [Local development](/environments/local-development). |
| 222 | |
| 223 | ## Vite dev servers run in child processes |
| 224 | |
| 225 | Under `alchemy dev`, every Vite dev server now runs in its own |
| 226 | child process |
| 227 | ([#1076](https://github.com/alchemy-run/alchemy/pull/1076)) |
| 228 | because much of Vite's plugin ecosystem resolves paths against |
| 229 | `process.cwd()`. Config loaders, framework plugins, and tailwind |
| 230 | all broke when the stack ran from a different directory than the |
| 231 | site. Each server now gets its project root as its working |
| 232 | directory, so all of that just works, and multiple sites in one |
| 233 | stack no longer interfere with each other. |
| 234 | |
| 235 | Each server's output streams into the dev console prefixed with |
| 236 | its worker name, so interleaved logs from several sites stay |
| 237 | readable. The work also surfaced an upstream Vite bug in |
| 238 | `server.port` handling, fixed in |
| 239 | [vitejs/vite#23158](https://github.com/vitejs/vite/pull/23158). |
| 240 | |
| 241 | ## Rename resources with `renamedFrom()` |
| 242 | |
| 243 | Changing a resource's logical ID used to plan a **replacement** — |
| 244 | a new resource created under the new ID, the old one (and its |
| 245 | data) deleted. `Alchemy.renamedFrom` |
| 246 | ([#1108](https://github.com/alchemy-run/alchemy/pull/1108)) |
| 247 | declares the id(s) a resource was previously registered under, and |
| 248 | the engine migrates the persisted state row instead: |
| 249 | |
| 250 | ```diff lang="typescript" |
| 251 | -const bucket = yield* Cloudflare.R2.Bucket("Bucket"); |
| 252 | +const bucket = yield* Cloudflare.R2.Bucket("Assets").pipe( |
| 253 | + Alchemy.renamedFrom("Bucket"), |
| 254 | +); |
| 255 | ``` |
| 256 | |
| 257 | The next deploy plans a single re-branding update — same instance |
| 258 | ID, same physical name, nothing created or deleted in the cloud: |
| 259 | |
| 260 | ```text |
| 261 | Plan: 1 to update |
| 262 | [Assets] update (renamed from Bucket) |
| 263 | ``` |
| 264 | |
| 265 | A bare string resolves against the ambient namespace exactly like |
| 266 | the `id` argument; `{ fqn: "..." }` addresses a move between |
| 267 | namespaces; listing several former ids covers rename chains. The |
| 268 | migration is deliberately conservative — types must match, |
| 269 | ambiguous claims and rename cycles fail the plan, and a leftover |
| 270 | row from an interrupted move is dropped state-only, never touching |
| 271 | the cloud. This is what lets the StaticSite fix above migrate |
| 272 | `<id>/Worker` → `<id>` without replacing your Worker. |
| 273 | |
| 274 | Docs: [Renaming Resources](/infrastructure-as-code/renaming). |
| 275 | |
| 276 | ## Data sources: plan-time `execute` |
| 277 | |
| 278 | Every `Binding.Service` gains `execute(...)` |
| 279 | ([#1037](https://github.com/alchemy-run/alchemy/pull/1037)) — |
| 280 | invoke a capability at plan/deploy time and get an `Output`, the |
| 281 | Terraform data-source / Pulumi invoke shape. `AWS.EC2.getAmi` is |
| 282 | the first: |
| 283 | |
| 284 | ```typescript |
| 285 | const instance = yield* AWS.EC2.Instance("web", { |
| 286 | imageId: AWS.EC2.getAmi({ |
| 287 | owners: ["amazon"], |
| 288 | name: ["al2023-ami-2023.*"], |
| 289 | }).ImageId.as<string>(), |
| 290 | instanceType: "t3.micro", |
| 291 | subnetId: subnet.subnetId, |
| 292 | }); |
| 293 | ``` |
| 294 | |
| 295 | The same contract still works as a runtime binding — `yield* |
| 296 | AWS.EC2.GetAmi(opts)` inside a Function grants |
| 297 | `ec2:DescribeImages` and looks images up at runtime. One |
| 298 | capability, two execution times. |
| 299 | |
| 300 | Cloudflare VPC services use the same shape |
| 301 | ([#821](https://github.com/alchemy-run/alchemy/pull/821), thanks |
| 302 | [Filip Falcon](https://github.com/filipfalcon)!) — |
| 303 | `Cloudflare.VpcService.lookup({ name })` resolves a service |
| 304 | managed outside the stack, and binding it (or a managed |
| 305 | `VpcService`) to a Worker's `env` yields a `Fetcher` that tunnels |
| 306 | into your private network: |
| 307 | |
| 308 | ```typescript |
| 309 | const worker = yield* Cloudflare.Worker("Worker", { |
| 310 | main: "./src/worker.ts", |
| 311 | env: { |
| 312 | VPC: Cloudflare.VpcService.lookup({ name: "my-vpc-service" }) |
| 313 | }, |
| 314 | }); |
| 315 | ``` |
| 316 | |
| 317 | Docs: [Bindings — data sources](/infrastructure-as-effects/binding). |
| 318 | |
| 319 | ## Kubernetes goes cluster-agnostic |
| 320 | |
| 321 | The Kubernetes workloads from beta.64 move out of `AWS.EKS.*` |
| 322 | into a new `alchemy/Kubernetes` namespace |
| 323 | ([#986](https://github.com/alchemy-run/alchemy/pull/986)): |
| 324 | `Deployment`, `Job`, `Manifest`, and `HelmChart` reference a |
| 325 | cluster instead of being EKS resources. Pass an `AWS.EKS.Cluster` |
| 326 | and auth (SigV4), the image registry (ECR), and workload identity |
| 327 | (Pod Identity) resolve through its adapter — or point at anything |
| 328 | `kubectl` can reach: |
| 329 | |
| 330 | ```typescript |
| 331 | const local = Kubernetes.KubeConfig({ context: "kind-dev" }); |
| 332 | |
| 333 | yield* Kubernetes.Deployment("Api", { |
| 334 | cluster: local, |
| 335 | image: "ghcr.io/acme/api:v3", |
| 336 | }); |
| 337 | ``` |
| 338 | |
| 339 | Exec credential plugins (`aws eks get-token`, `kubelogin`, |
| 340 | `gke-gcloud-auth-plugin`) are honored, so EKS today and AKS/GKE |
| 341 | via kubeconfig immediately. Add `Kubernetes.providers()` to your |
| 342 | stack; the old `AWS.EKS.Deployment`-style exports keep compiling |
| 343 | for one release as deprecated aliases, and existing state migrates |
| 344 | in place. |
| 345 | |
| 346 | ## distilled v1 |
| 347 | |
| 348 | Every cloud API alchemy talks to goes through a |
| 349 | [distilled](https://github.com/alchemy-run/distilled) SDK — typed |
| 350 | Effect clients with every error in the type-level union. Until |
| 351 | now, each provider's SDK came out of its own hand-rolled |
| 352 | generator: one for Cloudflare, one for AWS, one for each |
| 353 | OpenAPI-shaped API, each with its own quirks and its own bugs. |
| 354 | |
| 355 | distilled v1 replaces all of that with **one compiler** |
| 356 | ([#823](https://github.com/alchemy-run/alchemy/pull/823)). Every |
| 357 | provider's native spec format — OpenAPI (shared by 13 providers), |
| 358 | Cloudflare's API docs, AWS's official Smithy models, Google |
| 359 | Discovery, Azure ARM, GraphQL introspection — is first converted |
| 360 | into a standard [Smithy](https://smithy.io) model, and a single |
| 361 | Smithy-to-SDK codegen compiles every model into its Effect client. |
| 362 | Each provider package is just data: a spec converter config, a |
| 363 | small trait/pagination/protocol vocabulary (Cloudflare's is ~115 |
| 364 | lines), and a thin runtime protocol seam (SigV4 for AWS, a |
| 365 | generic bearer-REST protocol shared by 12 providers). |
| 366 | |
| 367 | The result is 20 packages and ~45k operations — 429 AWS services, |
| 368 | all of Azure and GCP, Cloudflare, Kubernetes, Stripe, PostHog, and |
| 369 | the long tail — all built, patched, and regenerated through the |
| 370 | same pipeline. Fixing a mistyped error or a wrong response schema |
| 371 | is now one JSON Patch against a Smithy model, in the same dialect |
| 372 | regardless of provider, and adding a whole new provider is a spec |
| 373 | config rather than a new generator. |
| 374 | |
| 375 | The migration was verified name-for-name against the v0 surfaces |
| 376 | and by alchemy's full live test tree — nearly 6,000 tests against |
| 377 | real cloud APIs — with zero SDK regressions. |
| 378 | |
| 379 | ## MySQL for effect-sql and Drizzle |
| 380 | |
| 381 | `SQL.MySQL` and `Drizzle.MySQL` complete the SQL client matrix |
| 382 | ([#1063](https://github.com/alchemy-run/alchemy/pull/1063)) — the |
| 383 | missing siblings of the Postgres and D1 clients: |
| 384 | |
| 385 | ```typescript |
| 386 | import * as SQL from "alchemy/SQL/MySQL"; |
| 387 | |
| 388 | const hd = yield* Cloudflare.Hyperdrive.Connect(Hyperdrive); |
| 389 | const sql = yield* SQL.MySQL({ url: hd.connectionString }); |
| 390 | const users = yield* sql`SELECT * FROM users`; |
| 391 | ``` |
| 392 | |
| 393 | On workerd the client automatically switches to MySQL's text |
| 394 | protocol (Hyperdrive's proxy has no prepared statements) and |
| 395 | eval-free row parsers; Lambda and containers keep prepared |
| 396 | statements. Same lazy per-request pool lifecycle as Postgres. |
| 397 | Docs: [SQL](/sql). |
| 398 | |
| 399 | ## WorkerEntrypoint bindings |
| 400 | |
| 401 | Bind a specific `WorkerEntrypoint` class exported by another |
| 402 | Worker — with optional `ctx.props` riding along |
| 403 | ([#1097](https://github.com/alchemy-run/alchemy/pull/1097)): |
| 404 | |
| 405 | ```typescript |
| 406 | const caller = yield* Cloudflare.Worker("Caller", { |
| 407 | main: "./src/caller.ts", |
| 408 | env: { |
| 409 | API: Cloudflare.WorkerEntrypoint(target, "Api"), |
| 410 | VENDOR: Cloudflare.WorkerEntrypoint(vendor, { |
| 411 | entrypoint: "Vendor", |
| 412 | props: { baseUrl: site.url }, // → the target's ctx.props |
| 413 | }), |
| 414 | }, |
| 415 | }); |
| 416 | ``` |
| 417 | |
| 418 | `alchemy dev` delivers `ctx.props` to the local target today. |
| 419 | |
| 420 | ## Pure annotations are opt-in only |
| 421 | |
| 422 | The bundler annotates discarded-result calls as pure so |
| 423 | minification can tree-shake them — that's what keeps effect's |
| 424 | unused modules out of your Worker. It used to auto-extend that |
| 425 | treatment to your own package when it declared |
| 426 | `sideEffects: false`, which could delete top-level registrations |
| 427 | like `app.get("/", handler)` under minification: the deploy |
| 428 | succeeds, unbundled unit tests stay green, and every request |
| 429 | 404s. |
| 430 | |
| 431 | Auto-detection is removed entirely |
| 432 | ([#1021](https://github.com/alchemy-run/alchemy/pull/1021)) — |
| 433 | thanks [Daniel Gangl](https://github.com/danielgangl)! `effect`, |
| 434 | `@effect/*`, and alchemy's own packages stay annotated by |
| 435 | default; any other package is only touched when you list it |
| 436 | deliberately: |
| 437 | |
| 438 | ```typescript |
| 439 | build: { |
| 440 | pure: { packages: ["my-lib", "@my-scope/*"] }, |
| 441 | // or pure: false to disable annotation entirely |
| 442 | } |
| 443 | ``` |
| 444 | |
| 445 | ## Also in this release |
| 446 | |
| 447 | - **Access-managed OAuth applications** |
| 448 | ([#1023](https://github.com/alchemy-run/alchemy/pull/1023)) — |
| 449 | `oauthConfiguration` on `Access.Application`, including dynamic |
| 450 | client registration for MCP-style clients. Thanks |
| 451 | [Magoz](https://github.com/magoz)! |
| 452 | - **Typed Access IdP configs** |
| 453 | ([#1002](https://github.com/alchemy-run/alchemy/pull/1002)) — |
| 454 | `IdentityProvider` props are a discriminated union on `type`, so |
| 455 | an `azureAD` IdP missing `directoryId` is a compile error; plus |
| 456 | zone scope and SAML certificate sets. Adoption of IdPs with |
| 457 | empty display names is fixed and `getIdentityProvider` joins as |
| 458 | a data source |
| 459 | ([#1099](https://github.com/alchemy-run/alchemy/pull/1099)). |
| 460 | - **SESv2 sending coverage** |
| 461 | ([#976](https://github.com/alchemy-run/alchemy/pull/976)) — |
| 462 | identity policies, contact lists, tenants, dedicated IPs, and |
| 463 | account settings: 9 resources and 5 bindings. Thanks |
| 464 | [Arya Saatvik](https://github.com/aryasaatvik) (who also |
| 465 | repaired the AWS utility subpath exports in |
| 466 | [#1033](https://github.com/alchemy-run/alchemy/pull/1033))! |
| 467 | - **Declarative Lambda versions** |
| 468 | ([#993](https://github.com/alchemy-run/alchemy/pull/993)) — |
| 469 | `AWS.Lambda.Version` and `Alias`, retain-by-default for |
| 470 | production-safe Durable Function deployments. Thanks again |
| 471 | [Arya Saatvik](https://github.com/aryasaatvik)! The engine now |
| 472 | re-resolves whole-resource references at apply so downstream |
| 473 | resources see fresh upstream attributes |
| 474 | ([#1068](https://github.com/alchemy-run/alchemy/pull/1068), |
| 475 | [#1070](https://github.com/alchemy-run/alchemy/pull/1070)). |
| 476 | - **`alchemy state export`** |
| 477 | ([#1043](https://github.com/alchemy-run/alchemy/pull/1043)) — |
| 478 | every state record across stacks and stages as one JSON |
| 479 | document, deterministically ordered so exports diff cleanly. |
| 480 | - **Drizzle on Durable Object SQLite** |
| 481 | ([#1031](https://github.com/alchemy-run/alchemy/pull/1031)) — |
| 482 | `Drizzle.DurableObject` applies drizzle-kit migrations inside a |
| 483 | DO, and `.sql` imports bundle as text modules. |
| 484 | - **Lambda `build.install` pins from lockfiles** |
| 485 | ([#858](https://github.com/alchemy-run/alchemy/pull/858)) — the |
| 486 | installed dependency graph is resolved against your lockfile |
| 487 | (npm, pnpm, bun, yarn) via generated `overrides`, so deployed |
| 488 | artifacts can't drift. Thanks |
| 489 | [Joaquín Pérez](https://github.com/jperezrealini)! |
| 490 | - **Effect error responses reach the client** |
| 491 | ([#1011](https://github.com/alchemy-run/alchemy/pull/1011)) — a |
| 492 | failed handler's `HttpServerResponse` is preserved instead of |
| 493 | being flattened to a bare 500. Thanks |
| 494 | [Patrik Duksin](https://github.com/patrikduksin) (who also fixed |
| 495 | local D1 migrations in |
| 496 | [#1009](https://github.com/alchemy-run/alchemy/pull/1009))! |
| 497 | - **Engine reliability** — an interrupted first deploy no longer |
| 498 | bricks later plans with a `SchemaError` |
| 499 | ([#1005](https://github.com/alchemy-run/alchemy/pull/1005)), and |
| 500 | cyclic props and class instances no longer send the engine's |
| 501 | prop walkers into infinite recursion |
| 502 | ([#1094](https://github.com/alchemy-run/alchemy/pull/1094)). |
| 503 | - **OCI Helm charts parse correctly** |
| 504 | ([#1042](https://github.com/alchemy-run/alchemy/pull/1042)) — |
| 505 | thanks [Andy Jefferson](https://github.com/microagi-andy)! |
| 506 | - **PlanetScale migrations split same-line breakpoint markers** |
| 507 | ([#1062](https://github.com/alchemy-run/alchemy/pull/1062)) — |
| 508 | thanks [BEEIRL](https://github.com/beeirl)! |
| 509 | - **Website & Vite fixes** — the assets directory is hashed during |
| 510 | plan diff so asset-only changes deploy |
| 511 | ([#1045](https://github.com/alchemy-run/alchemy/pull/1045)), |
| 512 | Vite's `base` applies to the assets manifest |
| 513 | ([#1048](https://github.com/alchemy-run/alchemy/pull/1048)), |
| 514 | Website uses the proper domain object with redirects |
| 515 | ([#1088](https://github.com/alchemy-run/alchemy/pull/1088)), dev |
| 516 | child ports are allocated ephemerally |
| 517 | ([#1090](https://github.com/alchemy-run/alchemy/pull/1090)), and |
| 518 | relative `rootDir`s hash stably |
| 519 | ([#1079](https://github.com/alchemy-run/alchemy/pull/1079)) — |
| 520 | thanks [Rahul Mishra](https://github.com/BlankParticle), who |
| 521 | also fixed relative DNS record matching in |
| 522 | [#1017](https://github.com/alchemy-run/alchemy/pull/1017)! |
| 523 | - **Prisma provider aligned with the management API** |
| 524 | ([#1012](https://github.com/alchemy-run/alchemy/pull/1012)) — |
| 525 | thanks [Aman Varshney](https://github.com/AmanVarshney01)! |
| 526 | - **`pg`, `mysql2`, `mongodb`, and `@vercel/nft` are optional |
| 527 | peer dependencies** |
| 528 | ([#1069](https://github.com/alchemy-run/alchemy/pull/1069), |
| 529 | [#1071](https://github.com/alchemy-run/alchemy/pull/1071)) — |
| 530 | install only what you use. |
| 531 | |
| 532 | ## Where to go next |
| 533 | |
| 534 | - [Frontend frameworks](/cloudflare/frontend/frontends) |
| 535 | - [Local development](/environments/local-development) |
| 536 | - [Renaming Resources](/infrastructure-as-code/renaming) |
| 537 | - [Bindings](/infrastructure-as-effects/binding) |
| 538 | - [EKS & Kubernetes](/aws/compute/eks) |
| 539 | - [SQL](/sql) |
| 540 | - [CHANGELOG](https://github.com/alchemy-run/alchemy/blob/main/CHANGELOG.md#v200-beta70) |
| 541 | - [Compare v2.0.0-beta.66 → v2.0.0-beta.70](https://github.com/alchemy-run/alchemy/compare/v2.0.0-beta.66...v2.0.0-beta.70) |