diff --git a/website/src/content/docs/blog/2026-07-06-beta-60.md b/website/src/content/docs/blog/2026-07-06-beta-60.md new file mode 100644 index 0000000000..7c4f0e40e4 --- /dev/null +++ b/website/src/content/docs/blog/2026-07-06-beta-60.md @@ -0,0 +1,292 @@ +--- +title: 2.0.0-beta.60 - Docker, MicroVMs & RSC +date: 2026-07-06T22:00:00Z +category: release +excerpt: v2.0.0-beta.60 adds a Docker provider, AWS Lambda MicroVMs, Workers for Platforms, React Server Components on Vite, class-based Workflows on async Workers, dramatically faster Cloudflare Container starts, and a ground-up docs overhaul. +--- + +beta.60 is a big one: a new **Docker provider**, **AWS Lambda +MicroVMs**, **Workers for Platforms**, **React Server Components** +on the Vite pipeline, class-based **Workflows on async Workers**, +Cloudflare **Containers** that start fast and stay up, and a +ground-up overhaul of the docs. No breaking changes this release. + +## Docker provider + +`alchemy/Docker` brings `Image`, `RemoteImage`, `Container`, +`Volume`, and `Network` as Stack resources +([#649](https://github.com/alchemy-run/alchemy-effect/pull/649)). +They drive the `docker` CLI's active context — Docker Desktop, a +remote/SSH context, a CI daemon — and live in the same Stack as +your cloud resources. Thanks **Austin**! + +```typescript +const image = yield* Docker.RemoteImage("postgres-image", { + name: "postgres", + tag: "18-alpine", +}); +const network = yield* Docker.Network("app-network"); +const data = yield* Docker.Volume("postgres-data"); + +const postgres = yield* Docker.Container("postgres", { + image, + environment: { POSTGRES_PASSWORD: password }, // Redacted-safe + ports: [{ external: 15432, internal: 5432 }], + volumes: [{ hostPath: data.name, containerPath: "/var/lib/postgresql/data" }], + networks: [{ name: network.name, aliases: ["postgres"] }], + start: true, +}); +``` + +Secrets are passed via process env (never on the CLI), pulled tags +are pinned so re-deploys are no-ops, and containers created outside +alchemy follow the standard adoption rules. See the new +[Docker hub](/docker) and the +[local services guide](/docker/local-services). + +## AWS Lambda MicroVMs + +`AWS.Lambda.MicrovmImage` is a new Platform for snapshot-booted +microVMs ([#712](https://github.com/alchemy-run/alchemy-effect/pull/712)). +Author the in-VM server in TypeScript — alchemy bundles it, +generates the Dockerfile, and builds the snapshot server-side — or +bring your own Dockerfile or a prebuilt artifact: + +```typescript +export class Sandbox extends AWS.Lambda.MicrovmImage< + Sandbox, + { hello: (message: string) => Effect.Effect } +>()("Sandbox") {} + +export default Sandbox.make( + { main: import.meta.filename, buildRole }, + Effect.gen(function* () { + return { + hello: (message) => Effect.succeed(`hello, ${message}!`), + fetch: Effect.gen(function* () { /* raw HTTP route */ }), + }; + }), +); +``` + +Every instance operation — `RunMicrovm`, `GetMicrovm`, +suspend/resume/terminate, auth tokens, image builds — is a +`Binding.Service` with least-privilege IAM scoping, and +`connectMicrovm` gives you typed RPC into the running VM: + +```typescript +const sandbox = yield* AWS.Lambda.connectMicrovm(Sandbox, { endpoint, authToken }); +const reply = yield* sandbox.hello("world"); +``` + +It works cross-cloud too: binding a MicroVM operation from a +`Cloudflare.Worker` mints an IAM user + assume-role Role so the +Worker can drive AWS MicroVMs at runtime. + +We also benchmarked MicroVMs against Cloudflare Containers — 700 +isolated cold boots. MicroVMs boot in 2–4.5s regardless of what's +inside; full results in +[Benchmarking Cloudflare Containers vs AWS MicroVMs](/blog/2026-07-01-microvm-cold-starts). +Docs: [MicroVMs](/aws/compute/microvms). + +## Workers for Platforms + +Deploy user Workers into a dispatch namespace and route to them +dynamically ([#715](https://github.com/alchemy-run/alchemy-effect/pull/715)). +A Worker deploys *into* a namespace with the new `namespace` prop — +no duplicate resource type: + +```typescript +const ns = yield* Cloudflare.WorkersForPlatforms.DispatchNamespace("Customers", {}); + +const userWorker = yield* Cloudflare.Worker("CustomerA", { + namespace: ns.name, + script: `export default { fetch() { return new Response("hi") } }`, +}); +``` + +The platform Worker binds the namespace Effect-natively, or via +`env` for async Workers: + +```typescript +// Effect-native — yield the binding, get a typed client +const dispatch = yield* Cloudflare.WorkersForPlatforms.Get(namespace); +const userWorker = yield* dispatch.get("customer-a"); +``` + +```typescript +// async — DispatchNamespace is a valid env binding +const platform = Cloudflare.Worker("Platform", { + main: "./handler.ts", + env: { DISPATCH: namespace }, +}); +// handler.ts: env.DISPATCH.get("customer-a").fetch(request) +``` + +Docs: [Workers for Platforms](/cloudflare/compute/workers-for-platforms). + +## React Server Components on Vite + +`Cloudflare.Vite` now supports builds that emit more than one +server environment — most notably RSC +([#685](https://github.com/alchemy-run/alchemy-effect/pull/685)). +`viteEnvironments` selects which environment produces the deployed +Worker entry and which additional server environments are bundled +alongside it: + +```typescript +const app = yield* Cloudflare.Vite("ReactRouterRSC", { + compatibility: { flags: ["nodejs_compat"] }, + viteEnvironments: { entry: "rsc", children: ["ssr"] }, +}); +``` + +A single-environment SSR build needs no configuration — the +default is `{ entry: "ssr", children: [] }` — and the `client` +environment always deploys as static assets. The same PR replaces +the dev-server restart fingerprint with an exact structural +signature, so config changes are never silently missed. Docs: +[Frontends on Cloudflare](/cloudflare/frontend/frontends). + +## Workflows on async Workers + +Class-based Cloudflare Workflows now bind to async (non-Effect) +Workers via `env`, mirroring class-based Durable Objects +([#707](https://github.com/alchemy-run/alchemy-effect/pull/707)): + +```typescript +export const AsyncWorker = Cloudflare.Worker("Async", { + main: "./src/worker.ts", + env: { + MY_WORKFLOW: Cloudflare.Workflow<{ value: string }>("MyWorkflow", { + className: "MyWorkflow", + }), + }, +}); +export type Env = Cloudflare.InferEnv; +// env.MY_WORKFLOW is the native Workflow<{ value: string }> +``` + +Your `WorkflowEntrypoint` class stays plain `cloudflare:workers` +code; alchemy provisions the workflow and emits the binding. +Cross-script references work with `scriptName`, same as async DOs. +Docs: [Workflows](/cloudflare/compute/workflows). + +## Containers start fast and stay up + +Cloudflare Containers deployed with alchemy behaved much worse +than the same image deployed with wrangler — at 100 concurrent +cold starts, ~2/100 succeeded. Two root causes, both fixed +([#708](https://github.com/alchemy-run/alchemy-effect/pull/708)): + +- **Wrangler-parity defaults** — `maxInstances` defaulted to `1`, + serializing every Durable Object through one container slot. Now + `maxInstances: 20`, scale-from-zero, and `instanceType: "lite"` + when no custom limits are set, matching wrangler. +- **Readiness polling** — probes had no per-probe timeout, so a + not-yet-listening port could hang for minutes. The loop now + mirrors `@cloudflare/containers` exactly. + +Result: 100/100 cold starts, on par with wrangler. And a container +that stops or crashes is now transparently restarted on the next +request, while a crash-looping container fails fast instead of +burning the readiness budget +([#711](https://github.com/alchemy-run/alchemy-effect/pull/711)). +Docs: [Containers](/cloudflare/compute/containers). + +## Effectful compute on ECS and EC2 + +The `ServerHost` + `{ fetch }` pattern now works end to end on +`AWS.ECS.Task` +([#713](https://github.com/alchemy-run/alchemy-effect/pull/713)): +the container entry bundles a Bun HTTP server, ships every chunk +into the image, and builds for the architecture the task declares +— verified by a smoke test that deploys a real Fargate service +behind an ALB. + +The hosted `AWS.EC2.Instance` serves HTTP the same way, with a +reboot-safe systemd unit that self-heals transient boot failures +([#714](https://github.com/alchemy-run/alchemy-effect/pull/714)). +And there's a new `AWS.EC2.KeyPair` resource — the generated +private key is captured once as a `Redacted` secret: + +```typescript +const keyPair = yield* AWS.EC2.KeyPair("DeployKey", { keyType: "ed25519" }); +// keyPair.keyName -> AWS.EC2.Instance({ keyName }) +// keyPair.privateKey -> Redacted +``` + +## The docs got a ground-up overhaul + +The website is restructured into per-provider hubs behind a +horizontal tab bar — `Core · CLI · Cloudflare · AWS · PlanetScale · +Neon · More ▾` — with ~100 new or reworked pages +([#721](https://github.com/alchemy-run/alchemy-effect/pull/721)): + +- **Core** — Infrastructure as Code, Infrastructure as Effects, + APIs, Environments, State Store, Project structure, Testing. +- **CLI** — every command documented, including the first-ever + docs for `alchemy unsafe nuke` and adopting resources. +- **Cloudflare / AWS hubs** — setup, five-part tutorials, block + pages and guides grouped by role (Compute, Frontend, APIs, Data, + Messaging, AI, …). +- Every page written against source, tests, and fixtures; every + moved URL 301s. + +Start at [the docs](/) and pick your provider. + +## Also in this release + +- **`--yes` is fully non-interactive** + ([#728](https://github.com/alchemy-run/alchemy-effect/pull/728)) — + `alchemy deploy --yes` now auto-accepts the Cloudflare state + store upgrade and deploy prompts instead of hanging on a non-TTY, + so CI needs no workarounds. +- **Partial state-store deploys are detected and recovered** + ([#700](https://github.com/alchemy-run/alchemy-effect/pull/700)) — + the store health check now verifies a real store is serving, not + just that a pre-create stub exists. +- **`Command.Build` memo includes command + env** + ([#738](https://github.com/alchemy-run/alchemy-effect/pull/738)) — + two `StaticSite` builds differing only in env (per-stage + `EXPO_PUBLIC_*`, API ids) no longer reuse each other's output. +- **Retained resources report as `retained`** + ([#739](https://github.com/alchemy-run/alchemy-effect/pull/739)) — + destroy no longer lists `RemovalPolicy: "retain"` resources as + deleted. Thanks **bjorntechTobbe**! +- **The CLI works on Windows** + ([#696](https://github.com/alchemy-run/alchemy-effect/pull/696)) — + the stack entry loads via a `file://` URL. Thanks **d3lay**! +- **Durable Object classes created outside alchemy adopt cleanly** + ([#495](https://github.com/alchemy-run/alchemy-effect/pull/495)) — + class migrations fall back to matching the observed binding, so + adopting a wrangler/dashboard Worker reuses its DO classes + instead of failing to re-create them. +- **Access Application survives state loss** + ([#742](https://github.com/alchemy-run/alchemy-effect/pull/742)) — + `read` falls back to a domain scan and gates takeover behind + `--adopt`, instead of blindly creating a duplicate app with a + fresh `aud`. Thanks **Andy Jefferson**! +- **Pooled connection origins** — Neon `Project`/`Branch` + ([#718](https://github.com/alchemy-run/alchemy-effect/pull/718)) + and PlanetScale `PostgresRole` + ([#717](https://github.com/alchemy-run/alchemy-effect/pull/717)) + expose `pooledOrigin`, and PlanetScale branch replica intent + persists so `replicas: 0` converges + ([#719](https://github.com/alchemy-run/alchemy-effect/pull/719)). + Thanks **Alex**! +- **AWS env-auth fixes** — `Region` is provided to STS during login + ([#709](https://github.com/alchemy-run/alchemy-effect/pull/709)) + and the `Region`/`AWSEnvironment` layer cycle in the account-id + lookup is broken + ([#710](https://github.com/alchemy-run/alchemy-effect/pull/710)). + +## Where to go next + +- [Docker](/docker) +- [MicroVMs](/aws/compute/microvms) +- [Workers for Platforms](/cloudflare/compute/workers-for-platforms) +- [Workflows](/cloudflare/compute/workflows) +- [Benchmarking Cloudflare Containers vs AWS MicroVMs](/blog/2026-07-01-microvm-cold-starts) +- [CHANGELOG](https://github.com/alchemy-run/alchemy-effect/blob/main/CHANGELOG.md#v200-beta60) +- [Compare v2.0.0-beta.59 → v2.0.0-beta.60](https://github.com/alchemy-run/alchemy-effect/compare/v2.0.0-beta.59...v2.0.0-beta.60) diff --git a/website/src/content/docs/blog/2026-07-08-beta-61.md b/website/src/content/docs/blog/2026-07-08-beta-61.md new file mode 100644 index 0000000000..0a51314da5 --- /dev/null +++ b/website/src/content/docs/blog/2026-07-08-beta-61.md @@ -0,0 +1,353 @@ +--- +title: 2.0.0-beta.61 - Workers Cache, Routes & Sync +date: 2026-07-08T06:00:00Z +category: release +excerpt: v2.0.0-beta.61 adds Workers Cache with an Effect-native ExecutionContext, Wrangler-style zone routes, alchemy sync for repairing state drift, a full Workflows API (retries, rollback, events), resource type aliases, and a long list of reliability fixes. +--- + +beta.61 adds **Workers Cache** with an Effect-native +`ExecutionContext`, Wrangler-style **zone routes**, **`alchemy +sync`** for repairing out-of-band drift, a much fuller +**Workflows** API (retries, rollback, events), **resource type +aliases** that heal the beta.59 renames, and a long list of +reliability fixes. + +:::caution +**Breaking changes** — migration for each is below. + +- **`workflow.create` takes the native options object** — + `create(input)` → `create({ params: input })` + ([#611](https://github.com/alchemy-run/alchemy-effect/pull/611)). +- **`WorkerExecutionContext` is now an Effect wrapper** — it's no + longer the raw `cf.ExecutionContext`; `waitUntil` takes an + `Effect` and is `yield*`-ed + ([#752](https://github.com/alchemy-run/alchemy-effect/pull/752)). +- **effect `>=4.0.0-beta.93` is required** — effect moved + `UrlParams.makeUrl` to `Url.make`; the peer range floor is raised + to match + ([#748](https://github.com/alchemy-run/alchemy-effect/pull/748)). +::: + +## Workers Cache + +[Workers Cache](https://blog.cloudflare.com/workers-cache/) — +Cloudflare's regionally tiered cache in front of Worker +entrypoints — lands as both a binding and a prop +([#752](https://github.com/alchemy-run/alchemy-effect/pull/752)). +Effect-native Workers enable it with `Cloudflare.cache()`, which +also returns the typed purge client: + +```typescript +Effect.gen(function* () { + // init: enables Workers Cache on this Worker at deploy time + const { purge } = yield* Cloudflare.cache({ crossVersionCache: true }); + + return { + fetch: Effect.gen(function* () { + const request = yield* HttpServerRequest; + if (request.url.startsWith("/invalidate")) { + yield* purge({ tags: ["products"] }); // typed CachePurgeError + return HttpServerResponse.text("purged"); + } + return HttpServerResponse.text("hello", { + headers: { + "Cache-Control": "public, max-age=300, stale-while-revalidate=3600", + "Cache-Tag": "products", + }, + }); + }), + }; +}) +``` + +Async Workers use the prop: + +```typescript +const worker = yield* Cloudflare.Worker("Api", { + main: "./src/api.ts", + cache: { enabled: true, crossVersionCache: true }, +}); +``` + +## Effect-native `ExecutionContext` + +`WorkerExecutionContext` used to hand you the raw +`cf.ExecutionContext`. It's now an Effect wrapper — and like +`DurableObjectState`, it can be yielded from the Worker's **init +closure** (or any Layer); its methods resolve the live per-event +context: + +```typescript +Effect.gen(function* () { + const exec = yield* Cloudflare.WorkerExecutionContext; // init + + return { + fetch: Effect.gen(function* () { + // respond now, finish work in the background + yield* exec.waitUntil(journal.record(entry).pipe(Effect.delay("5 seconds"))); + return HttpServerResponse.text("ok"); + }), + }; +}) +``` + +Migrating is mechanical — `ctx.waitUntil(promise)` becomes +`yield* exec.waitUntil(effect)`. + +## Zone Routes on Workers + +`Cloudflare.Worker` accepts Wrangler-style zone routes via a new +`routes` prop +([#438](https://github.com/alchemy-run/alchemy-effect/pull/438)). +Thanks **utopy**! + +```typescript +yield* Cloudflare.Worker("Api", { + main: import.meta.filename, + routes: [ + { pattern: "api.example.com/*", zoneName: "example.com" }, + { pattern: "example.com/api/*", zoneId: "" }, + ], +}); +``` + +Each entry takes `zoneName` / `zoneId` (the Wrangler equivalents) +or a `zone` reference — and when the zone is omitted, it's inferred +from the pattern hostname. Routes are reconciled on deploy and +cleaned up on destroy. + +## `alchemy sync` + +Cloud state drifts: someone edits a resource in the dashboard, a +bucket gets deleted, tags get mangled. `alchemy sync` converges it +back to the last-deployed state — without re-running your stack +program +([#766](https://github.com/alchemy-run/alchemy-effect/pull/766)): + +```sh +alchemy sync ./alchemy.run.ts --stage prod # detect + repair +alchemy sync ./alchemy.run.ts --stage prod --dry-run # detect only +``` + +Per resource it runs observe → compare → converge: `read` observes +the live cloud state, a deep-compare against the persisted +attributes decides `unchanged` vs drifted, and drift is repaired by +`reconcile` with the persisted props as the desired state. A +resource deleted out-of-band is recreated under the **same +instance id**, so deterministic physical names regenerate +identically. Resources sync concurrently, and failures are +aggregated after every resource has been attempted. + +## Workflows: retries, rollback, and events + +The Effect-native Workflow wrapper now covers the full Workers API +surface, 1:1 with the native binding +([#611](https://github.com/alchemy-run/alchemy-effect/pull/611)). +Thanks **Gerben Mulder**! + +`create` takes the native options object — the breaking change +above — which also unlocks `id` and `retention`: + +```diff lang="typescript" +- const instance = yield* workflow.create({ orderId: "abc" }); ++ const instance = yield* workflow.create({ ++ id: "order-abc", ++ params: { orderId: "abc" }, ++ retention: { successRetention: "1 day", errorRetention: "7 days" }, ++ }); +``` + +`task` gains retries, timeout, and rollback, and +`WorkflowStepContext` exposes the current attempt: + +```typescript +const result = yield* Cloudflare.Workflows.task( + "call-api", + Effect.gen(function* () { + const context = yield* Cloudflare.Workflows.WorkflowStepContext; + return { attempt: context.attempt }; + }), + { + retries: { limit: 3, delay: "5 seconds", backoff: "linear" }, + rollback: ({ output }) => (output ? cleanup(output.id) : Effect.void), + }, +); +``` + +`waitForEvent` parks the instance until a matching `sendEvent`: + +```typescript +// inside the workflow +const approval = yield* Cloudflare.Workflows.waitForEvent<{ approved: boolean }>( + "approval", + { type: "approval", timeout: "1 day" }, +); + +// from outside +yield* instance.sendEvent({ type: "approval", payload: { approved: true } }); +``` + +`createBatch`, `restart`, rollback status, and extended event +metadata round out the surface. Docs: +[Workflows](/cloudflare/compute/workflows). + +## Resource type aliases + +Renaming a resource's type string used to strand state persisted +under the old name — provider lookup died on the legacy type. +Resources now declare their former names +([#765](https://github.com/alchemy-run/alchemy-effect/pull/765)): + +```typescript +export const Queue = Resource("Cloudflare.Queues.Queue", { + aliases: ["Cloudflare.Queue"], +}); +``` + +Plan, apply, destroy, `logs`, and `tail` all resolve providers +through aliases, and a noop deploy migrates state rows to the +canonical name. All **74 resources** renamed in the beta.59 +namespace alignment are annotated with their pre-rename alias — so +state written by beta.58 or earlier now deploys, destroys, and +replaces cleanly instead of erroring on the legacy type. + +## AI Gateway BYOK: `AI.ProviderKey` + +A bring-your-own-key provider on an AI Gateway needs two coordinated +resources — a Secrets Store `Secret` with an exact +`{gatewayId}_{providerSlug}_{alias}` name, and a `GatewayProvider` +that references it. `Cloudflare.AI.ProviderKey` owns that contract +as one resource +([#586](https://github.com/alchemy-run/alchemy-effect/pull/586)). +Thanks **Alex**! + +```typescript +const { secret, gatewayProvider } = yield* Cloudflare.AI.ProviderKey("OpenAiKey", { + store, + gatewayId: gateway.gatewayId, + providerSlug: "openai", + value: yield* Config.redacted("OPENAI_API_KEY"), +}); +``` + +Docs: [AI Gateway](/cloudflare/ai/ai-gateway). + +## Lambda async invocation config + +Lambda's asynchronous invocation settings — retries, event age, +success/failure destinations — land as an `eventInvokeConfig` prop +on `Function` and `Alias` +([#627](https://github.com/alchemy-run/alchemy-effect/pull/627)). +Thanks **José Netto**! + +```typescript +const fn = yield* AWS.Lambda.Function("AsyncFn", { + main: "./src/handler.ts", + eventInvokeConfig: { + maximumRetryAttempts: 0, + maximumEventAgeInSeconds: 60, + destinationConfig: { + OnFailure: { Destination: queue.queueArn }, + }, + }, +}); +``` + +## R2 Bucket `cors` + +The `cors` prop that `Cloudflare.R2.Bucket` had in v1 is restored +([#771](https://github.com/alchemy-run/alchemy-effect/pull/771)) — +public buckets serving browser range-reads (e.g. PMTiles) declare +CORS in the stack again instead of out-of-band: + +```typescript +const bucket = yield* Cloudflare.R2.Bucket("Tiles", { + domains: [{ name: "tiles.example.com" }], + cors: [ + { + allowedMethods: ["GET", "HEAD"], + allowedOrigins: ["https://map.example.com"], + allowedHeaders: ["range"], + exposeHeaders: ["etag", "content-range"], + maxAgeSeconds: 3600, + }, + ], +}); +``` + +Rules use the flat S3-style shape and reconcile like +`lifecycleRules` — observed vs desired, so out-of-band drift and +adoption converge correctly. + +## Also in this release + +- **Worker metadata-only changes now deploy** + ([#747](https://github.com/alchemy-run/alchemy-effect/pull/747)) — + compatibility flags/date, observability, placement, limits, and + binding changes are folded into the update diff via a metadata + hash; previously they planned as noops and silently never + shipped. The first deploy after upgrading plans a one-time update + per Worker to backfill the hash. Thanks **Alex**! +- **Wedged stacks recover** + ([#767](https://github.com/alchemy-run/alchemy-effect/pull/767), + [#770](https://github.com/alchemy-run/alchemy-effect/pull/770)) — + a deploy interrupted mid-create used to persist a state row whose + Output-valued props can't round-trip, crashing every subsequent + `plan`/`deploy`/`destroy`. Every provider's `read`/`diff` is + audited so the engine re-drives the create and reconcile + converges on the half-created resource. +- **Script uploads retry every binding-target-not-found error** + ([#753](https://github.com/alchemy-run/alchemy-effect/pull/753)) — + KV, R2, D1, Queues, DO classes, Hyperdrive, Vectorize, and more, + covering Cloudflare's deploy-time propagation lag. +- **`Resource.ref` values bind natively in Worker `env`** + ([#756](https://github.com/alchemy-run/alchemy-effect/pull/756)) — + a ref passed as an env binding used to degrade to a plain JSON + env var and break at runtime; refs now classify exactly like + locally-declared resources. +- **Multiple queue consumers on one Worker** + ([#466](https://github.com/alchemy-run/alchemy-effect/pull/466)) — + event dispatch runs every listener for an event type instead of + letting the first queue subscription swallow the rest. Thanks + **Leonardo E. Dominguez**! +- **State-store errors are actionable** + ([#737](https://github.com/alchemy-run/alchemy-effect/pull/737)) — + 30 days of production traces triaged; empty `StateStoreError:` + messages, opaque decode errors, and JSON-parse crashes now + surface real messages (an unauthorized store tells you to run + `alchemy login`). +- **The test suite passes on Windows** + ([#735](https://github.com/alchemy-run/alchemy-effect/pull/735)) — + including two real product fixes: `Drizzle.Schema` passed + OS-native paths to drizzle-kit (backslashes are glob escapes), + and `Bundle.PurePlugin` could be hijacked by a stray + `package.json` above `node_modules`. +- **Drizzle query chains are real Effects** + ([#750](https://github.com/alchemy-run/alchemy-effect/pull/750)) — + `db.select()...` chains now expose the full Effect protocol, so + they compose with `Effect.all` and friends instead of spinning + the run loop. +- **PlanetScale inherited roles compare by membership** + ([#761](https://github.com/alchemy-run/alchemy-effect/pull/761)) — + API-returned ordering no longer forces a `PostgresRole` + replacement. Thanks **Gerben Mulder**! +- **`globalOutbound: null` is preserved in `WorkerLoader`** + ([#746](https://github.com/alchemy-run/alchemy-effect/pull/746)) — + the documented "block all outbound network access" signal no + longer silently coerces to default access. Thanks **Alex**! +- **Binding-hosted DO classes join the precreate stub** + ([#764](https://github.com/alchemy-run/alchemy-effect/pull/764)) — + fixes `Worker did not expose Durable Object namespace` on fresh + deploys of worker↔container cycles. Thanks **Daniel Gangl**! +- **`alchemy dev` no longer prints bun's benign tsconfig fd + warning** + ([#768](https://github.com/alchemy-run/alchemy-effect/pull/768)). + +## Where to go next + +- [Workflows](/cloudflare/compute/workflows) +- [Workers](/cloudflare/compute/workers) +- [Custom Domains & Routes](/cloudflare/networking/custom-domains) +- [AI Gateway](/cloudflare/ai/ai-gateway) +- [CHANGELOG](https://github.com/alchemy-run/alchemy-effect/blob/main/CHANGELOG.md#v200-beta61) +- [Compare v2.0.0-beta.60 → v2.0.0-beta.61](https://github.com/alchemy-run/alchemy-effect/compare/v2.0.0-beta.60...v2.0.0-beta.61)