1---
2title: 2.0.0-beta.78 - Git Hosting, Stripe & Worker Previews
3date: 2026-09-17T09:00:00Z
4category: release
5excerpt: v2.0.0-beta.78 ships alchemy/Git, an embeddable, pluggable git server you mount in your own Effect HTTP API, plus a Stripe provider, Worker Previews, ten new GitHub resources, an alchemy dev that uses half the memory, and a round of drift and default-handling fixes across RDS, S3, and security groups.
6---
7
8beta.78 ships **`alchemy/Git`**, an embeddable, pluggable git server
9built from Effect Layers. Mount it inside any Effect HTTP API and
10pick where refs and bytes live — Durable Objects and R2 on
11Cloudflare, S3 and Lambda on AWS, or a store of your own. A new
12**Stripe** provider provisions Stripe resources and binds them to
13your Workers and Functions. **Worker Previews** give every branch its
14own URL and isolated Durable Objects. The GitHub provider gains ten
15resources, `alchemy dev` runs on half the memory, and a round of
16fixes to RDS, S3, and security groups repairs drift and restores
17defaults when props are removed.
18
19:::caution
20**Breaking changes**
21
22- **effect `>=4.0.0-rc.115` is the new minimum peer**
23 ([#1562](https://github.com/alchemy-run/alchemy/pull/1562),
24 [#1601](https://github.com/alchemy-run/alchemy/pull/1601)). rc.113
25 renamed the `Config` and `Flag` constructors to PascalCase — see
26 [below](#effect-rc115).
27- **`SecurityGroup` `egress: []` now means no outbound traffic**
28 ([#1623](https://github.com/alchemy-run/alchemy/pull/1623)). It
29 used to fall back to AWS's allow-all default, the same as omitting
30 `egress`. Omit the prop to keep the default. Rules that exist in
31 AWS but not in your program are now removed on the next deploy
32 ([#1592](https://github.com/alchemy-run/alchemy/pull/1592),
33 [#1664](https://github.com/alchemy-run/alchemy/pull/1664)) —
34 `SecurityGroupRule` resources in the same stack are preserved.
35- **RDS parameter groups reset undeclared overrides**
36 ([#1589](https://github.com/alchemy-run/alchemy/pull/1589)). An
37 omitted `parameters` is `{}`: values edited in the console converge
38 back to engine defaults.
39- **`Alchemist` leaves the root barrel**
40 ([#1614](https://github.com/alchemy-run/alchemy/pull/1614)) so that
41 importing `alchemy` no longer loads every cloud:
42
43 ```diff lang="typescript"
44 -import * as Alchemy from "alchemy";
45 -Alchemy.Alchemist
46 +import * as Alchemist from "alchemy/Alchemist";
47
48 -import { S3BucketEventSource } from "alchemy/Server";
49 +import { S3BucketEventSource } from "alchemy/Server/S3BucketEventSource";
50 ```
51
52- **`Prisma.Database` branch props are no longer nullable**
53 ([#1582](https://github.com/alchemy-run/alchemy/pull/1582)) —
54 Prisma's API now attaches every database to a Branch. Omit both
55 `branchId` and `branchGitName` to use the project default.
56- **`Test.make` defaults to stage `test_$USER`**
57 ([#1579](https://github.com/alchemy-run/alchemy/pull/1579)),
58 matching `live_$USER` / `dev_$USER`. Pass `stage` to keep `test`.
59:::
60
61## effect rc.115
62
63alchemy now requires effect `>=4.0.0-rc.115`
64([#1562](https://github.com/alchemy-run/alchemy/pull/1562),
65[#1601](https://github.com/alchemy-run/alchemy/pull/1601)):
66
67```sh
68bun add alchemy@latest effect@rc @effect/platform-bun@rc @effect/platform-node@rc
69```
70
71rc.113 renamed the `Config` and `Flag` constructors to PascalCase:
72
73```diff lang="typescript"
74-Config.string("HOST")
75+Config.String("HOST")
76-Config.mapOrFail(...)
77+Config.mapEffect(...)
78-Flag.boolean("yes")
79+Flag.Boolean("yes")
80-Flag.choice("backend", …)
81+Flag.Literals("backend", …)
82```
83
84rc.113 also replaced `@effect/sql-pg`'s `pg` dependency with a native
85wire client, which sent no SNI and rejected `sslmode=prefer`. Both
86broke Aurora DSQL, Neon, and local Hyperdrive; alchemy's
87`SQL.Postgres`, `Drizzle.Postgres`, and `postgresState` now derive the
88TLS config from the URL
89([#1580](https://github.com/alchemy-run/alchemy/pull/1580),
90[#1582](https://github.com/alchemy-run/alchemy/pull/1582)), and rc.115
91fixes the SNI half upstream
92([Effect-TS/effect#8174](https://github.com/Effect-TS/effect/pull/8174)).
93
94## An embeddable, pluggable Git server
95
96`alchemy/Git` is a pluggable, embeddable git server
97([#1187](https://github.com/alchemy-run/alchemy/pull/1187),
98[#1621](https://github.com/alchemy-run/alchemy/pull/1621)). It speaks
99smart HTTP to any `git` client, serves a typed REST plane with pull
100requests, and answers the GitHub REST v3 API so `gh api` and Octokit
101work against it unchanged. Nothing ships a Worker — every part is an
102Effect service with swappable Layers, assembled into a Worker you own:
103
104```typescript
105import * as Git from "alchemy/Git";
106import * as Http from "alchemy/Http";
107
108export const GitObjects = Cloudflare.R2.Bucket("GitObjects");
109
110const GitLive = Git.ApiLive.pipe(
111 Layer.provide(Git.ApiHandlersLive), // the engine's route handlers
112 Layer.provide(Authentication.layer), // yours: who may call what
113 Layer.provide(Git.ReposDurableObject), // refs, objects, pull requests
114 Layer.provide(Git.RegistryDurableObject), // owner/name → repo
115 Layer.provide(Git.HasherInline), // push verification
116 Layer.provide(Git.BlobStoreR2(GitObjects)), // packs, bundles, large pushes
117);
118
119export default Cloudflare.Worker(
120 "Git",
121 { main: import.meta.url, ...Git.GIT_WORKER_OPTIONS },
122 Effect.gen(function* () {
123 const fetch = yield* HttpRouter.toHttpEffect(
124 GitLive.pipe(Layer.provide(Http.Platform)),
125 );
126 return { fetch };
127 }),
128);
129```
130
131Each line is one decision with its own implementations. Change a
132line and only that decision changes — here the bytes move to S3:
133
134```diff lang="typescript"
135-const GitObjects = Cloudflare.R2.Bucket("GitObjects");
136+const GitObjects = AWS.S3.Bucket("GitObjects");
137
138 const GitLive = Git.ApiLive.pipe(
139 Layer.provide(Git.ApiHandlersLive),
140 Layer.provide(Authentication.layer),
141 Layer.provide(Git.ReposDurableObject),
142 Layer.provide(Git.RegistryDurableObject),
143 Layer.provide(Git.HasherInline),
144- Layer.provide(Git.BlobStoreR2(GitObjects)),
145+ Layer.provide(Git.BlobStoreS3(GitObjects)),
146 );
147```
148
149### Embedded by design
150
151You build the HTTP server and mount `alchemy/Git`'s routes into it.
152`Git.Api` is an Effect `HttpApi` group and `Git.Handlers` supplies
153the default implementation of every route as a plain function. Add
154the group to your own API and put your own `HttpApiMiddleware` in
155front of it, and every request — `git clone` and `git push`, the REST
156API, and the GitHub-compatible API `gh` uses — goes through your
157authentication and your user management. The engine itself holds no
158users, no credentials, and no policy.
159
160The middleware is native `HttpApiMiddleware`. Declare what it
161provides, add the Git groups to your API, and put it in front:
162
163```typescript
164class Authentication extends HttpApiMiddleware.Service<
165 Authentication,
166 { provides: CurrentUser }
167>()("app/Authentication", { error: Unauthorized }) {}
168
169class AppApi extends HttpApi.make("app")
170 .addHttpApi(Git.Api) // repos, refs, objects, pulls, protocol, github
171 .add(AppRoutes) // your own routes beside them
172 .middleware(Authentication) {}
173```
174
175Its implementation is an ordinary Layer — resolve a session cookie,
176an API key, or the password `git` sends over HTTP Basic, and provide
177`CurrentUser` to the endpoint. `examples/cloudflare-git-service` does
178this with Better Auth.
179
180Because the handlers are yours to call, a route can be overridden
181with a few lines of application code. Here `receivePack` reuses the
182defaults for the read routes and wraps the push with a branch rule
183that reads the `CurrentUser` the middleware provided:
184
185```typescript
186const ProtocolLive = HttpApiBuilder.group(AppApi, "protocol", (h) =>
187 Effect.gen(function* () {
188 const git = yield* Git.Engine;
189 const defaults = yield* Git.Handlers;
190 return h
191 .handleRaw("infoRefs", defaults.protocol.infoRefs)
192 .handleRaw("uploadPack", defaults.protocol.uploadPack)
193 .handleRaw("receivePack", ({ params, request }) =>
194 Effect.gen(function* () {
195 const repo = yield* git.repositories.get(params);
196 const push = yield* GitHttp.ReceivePack.decode(request);
197 const user = yield* CurrentUser; // provided by Authentication
198
199 if (push.updates.some((u) => u.ref === "refs/heads/main" && user.id !== repo.owner)) {
200 return GitHttp.ReceivePack.reject(push, "only the owner moves main");
201 }
202
203 const prepared = yield* git.preparePush(repo, push.input);
204 return GitHttp.ReceivePack.response(push, yield* git.commitPush(prepared));
205 }),
206 );
207 }),
208);
209```
210
211`preparePush` verifies the pack without moving refs; `commitPush`
212moves them atomically. The same host runs behind a shared secret,
213Better Auth, or anything else — the tutorial walks from one to the
214other, ending with the full version of this handler in
215[Protect a branch](/git/tutorial/part-6).
216
217### Performance
218
219We benchmarked it against GitHub with real `git` from the same
220client and edge: a 36 MiB, 15.6k-object repository, a fresh
221repository on each host.
222
223| Operation | `alchemy/Git` | GitHub |
224| --- | --- | --- |
225| Full `git clone` | 0.7 s best, 1.3 s typical | 1.3 s best, 2.1 s typical |
226| Clone throughput | 80 MiB/s best, 33 MiB/s typical | 28 MiB/s best, 17 MiB/s typical |
227| Incremental fetch | 0.4–0.6 s | 0.3–0.5 s |
228| Incremental push | 0.2–0.4 s | 1.2 s |
229| 44 MiB push, whole blobs | 4.8–6.4 s | 4.6 s |
230| 40 MiB push, delta-heavy | 5.9–6.5 s | 3.3 s |
231
232GitHub's throughput is the pack size divided by its clone time; the
233incremental fetch is a `git fetch` with one new commit to pick up.
234Clones are fast because the work is done ahead of time. After every
235push the repository writes a ready-to-serve bundle to R2, and a
236`git clone` is the Worker streaming that file to the client. The
237Durable Object is not involved, so the number of concurrent clones a
238repository can serve is limited by R2 and Workers, not by one object:
239four clones at once reached 93 MiB/s aggregate. Everyday pushes,
240which move a few commits, are faster than GitHub; incremental fetches
241are about even. Large pushes are the one place GitHub is ahead. The
242largest real push so far is the alchemy monorepo, 44k objects in a
24367 MiB pack, hosted on [git.alchemy.run](https://git.alchemy.run)
244and cloned back byte-identical under `git fsck --strict`.
245
246### Where the hashing runs
247
248Before a ref moves, every object in a push is inflated, SHA-1 hashed,
249and delta-resolved. For a kilobyte push that is nothing; for a 40 MiB
250push it is a lot of CPU, and Workers are a poor place for it. A
251Worker's CPU is slow for exactly this kind of work — tight loops over
252bytes, inflate, and hashing run several times slower than on a
253laptop — and there is one thread to run it on: a service binding call
254executes on the caller's thread, so fanning out to another Worker
255buys nothing. The first version hashed on the request thread and took
25610–13 seconds for the 40 MiB push:
257
258```typescript
259Layer.provide(Git.HasherInline)
260```
261
262`Git.Hasher` is the Layer where a chunk gets hashed, so the fix was a
263second implementation, not a change to the engine.
264`HasherWorkerLoader` bundles the hasher as a module inside your
265Worker and loads it into four fresh isolates per push, each with its
266own 128 MB and its own thread. Nothing new is deployed, nothing
267leaves Cloudflare, and the same push drops to 5.9–6.5 seconds:
268
269```diff lang="typescript"
270+import * as GitHasher from "alchemy/Git/Hasher";
271
272 const GitLive = Git.ApiLive.pipe(
273 Layer.provide(Git.ApiHandlersLive),
274 Layer.provide(Authentication.layer),
275 Layer.provide(Git.ReposDurableObject),
276 Layer.provide(Git.RegistryDurableObject),
277- Layer.provide(Git.HasherInline),
278+ Layer.provide(GitHasher.HasherWorkerLoader()),
279 Layer.provide(Git.BlobStoreR2(GitObjects)),
280 );
281```
282
283`HasherLambda` is the third implementation — one Lambda invocation
284per chunk, all at once, on a 3 GB function — for pushes wider than
285four chunks when an AWS account is already in the stack. If a hasher
286fails, the Worker hashes the chunk inline; a push never fails because
287its hasher did. Incremental pushes take 0.2–0.4 s on every one of
288them, so start with `HasherInline` and swap when large pushes start
289to matter.
290
291[Scaling](/git/recipes/scaling) and [Hasher](/git/blocks/hasher)
292have the full numbers; the design notes in
293[`DESIGN.md`](https://github.com/alchemy-run/alchemy/blob/main/packages/alchemy/src/Git/DESIGN.md)
294record every step of the sweep.
295
296It is a git remote you own, with an API, that you can build on — not
297a GitHub replacement. No issues, reviews, or Actions.
298
299Docs: [Git](/git) · [Getting Started](/git/getting-started) ·
300[Tutorial](/git/tutorial/part-1) · [Building blocks](/git/blocks) ·
301[Scaling](/git/recipes/scaling).
302
303## Stripe
304
305A new **Stripe provider** for managing Stripe with alchemy
306([#1298](https://github.com/alchemy-run/alchemy/pull/1298)) — thanks
307[Michael K](https://github.com/Mkassabov)! It comes with resources
308(`Product`, `Price`, `Coupon`, `PromotionCode`, `PaymentLink`,
309`BillingPortalConfiguration`, `WebhookEndpoint`, and Connect's
310`Account`), bindings for calling Stripe from a Worker
311(`CreateCustomer`, `CreateCheckoutSession`, `Retrieve*`, …) that
312wire the API key into the Worker for you — each one registers the
313permission it needs, so there is no key to copy into `env` — and
314`consumeEvents`, an event source that provisions the webhook endpoint
315from the Worker's URL and hands your handler a typed event:
316
317```typescript
318export default class Api extends Cloudflare.Worker<Api>()(
319 "Api",
320 { main: import.meta.url },
321 Effect.gen(function* () {
322 const product = yield* Stripe.Product("Pro", { name: "Pro" });
323 const price = yield* Stripe.Price("ProMonthly", {
324 product,
325 currency: "usd",
326 unitAmount: 2000,
327 recurring: { interval: "month" },
328 });
329 const priceId = yield* price.id;
330
331 const createCustomer = yield* Stripe.CreateCustomer();
332 const createCheckout = yield* Stripe.CreateCheckoutSession();
333
334 yield* Stripe.consumeEvents(
335 "Events",
336 { events: [Stripe.CheckoutSessionCompleted, Stripe.InvoicePaymentFailed] },
337 Effect.fn(function* (event) {
338 yield* Effect.log(event.type);
339 }),
340 );
341
342 return {
343 fetch: Effect.gen(function* () {
344 const customer = yield* createCustomer({ email: "a@example.com" });
345 const session = yield* createCheckout({
346 mode: "subscription",
347 customer: customer.id,
348 line_items: [{ price: yield* priceId, quantity: 1 }],
349 success_url: "https://example.com/welcome",
350 cancel_url: "https://example.com/pricing",
351 });
352 return HttpServerResponse.redirect(session.url!);
353 }).pipe(Effect.orDie),
354 };
355 }).pipe(
356 Effect.provide([
357 Stripe.CreateCustomerHttp,
358 Stripe.CreateCheckoutSessionHttp,
359 Stripe.ConsumeEventsLive,
360 ]),
361 ),
362) {}
363```
364
365`consumeEvents` verifies signatures (a bad one is a 401) and creates
366the `WebhookEndpoint` for you; declare one directly only when you own
367the delivery URL.
368
369Two runnable examples: `examples/stripe-billing` (Checkout, Billing
370Portal, and an entitlement record in KV kept current by webhooks) and
371`examples/stripe-connect` (Express accounts, hosted onboarding, and
372`account.updated` driving a D1 merchant table).
373
374Docs: [Stripe](/stripe) · [Sell a subscription](/stripe/guides/subscriptions)
375· [Onboard merchants with Connect](/stripe/guides/connect) ·
376[React to Stripe events](/stripe/guides/webhooks).
377
378## Worker Previews
379
380`preview` is the sibling of `version`
381([#1563](https://github.com/alchemy-run/alchemy/pull/1563)): a named
382copy of another Worker with its own URL, its own bindings, and
383isolated same-Worker Durable Objects. Production traffic is untouched.
384
385```typescript
386const parent = yield* Cloudflare.Worker.ref("Api", { stage: "prod" });
387
388const api = yield* Cloudflare.Worker("Api", {
389 main: "./src/api.ts",
390 preview: { of: parent },
391});
392// api.url → https://<stage>-<worker>.<account>.workers.dev
393```
394
395The Preview name defaults to the stack stage (`pr-123`). Destroying
396the Preview Worker deletes the Preview; the parent stays up. Enable
397`domain: { name: "app.example.com", previews: true }` on the parent
398and Previews serve at `https://<preview-name>.app.example.com`.
399
400```typescript
401preview.of // branch / PR — own URL, isolated DOs
402version.parent // canary of another stage
403version.traffic // gradual rollout of this Worker
404version.traffic: 0 // upload without routing
405```
406
407Docs: [Worker Previews](/cloudflare/compute/previews).
408
409## GitHub: rulesets, protection, releases, and more
410
411Ten new resources on the GitHub provider.
412[`BranchProtection`](/providers/github/branchprotection)
413([#1514](https://github.com/alchemy-run/alchemy/pull/1514)) covers the
414classic protection API — thanks
415[99andytang](https://github.com/99andytang)! — and
416[Agusti F.](https://github.com/agustif) contributed the other nine:
417[`Ruleset`](/providers/github/ruleset),
418[`Label`](/providers/github/label),
419[`Milestone`](/providers/github/milestone),
420[`Issue`](/providers/github/issue),
421[`PullRequest`](/providers/github/pullrequest),
422[`Release`](/providers/github/release),
423[`Collaborator`](/providers/github/collaborator),
424[`TeamAccess`](/providers/github/teamaccess), and
425[`WikiPage`](/providers/github/wikipage)
426([#1565](https://github.com/alchemy-run/alchemy/pull/1565)–[#1578](https://github.com/alchemy-run/alchemy/pull/1578)).
427Thanks both!
428
429```typescript
430yield* GitHub.Ruleset("main-protection", {
431 owner: "my-org",
432 repository: "my-repo",
433 name: "main protection",
434 target: "branch",
435 conditions: { include: ["refs/heads/main", "refs/heads/release/*"] },
436 rules: {
437 nonFastForward: true,
438 deletion: true,
439 requiredLinearHistory: true,
440 pullRequest: {
441 requiredApprovingReviewCount: 2,
442 requireCodeOwnerReview: true,
443 dismissStaleReviewsOnPush: true,
444 },
445 },
446});
447
448yield* GitHub.TeamAccess("platform", {
449 owner: "my-org",
450 repository: "my-repo",
451 teamSlug: "platform",
452 permission: "push",
453});
454```
455
456Docs: [GitHub](/github).
457
458## `alchemy dev` runs lighter
459
460The `alchemy dev` process tree drops from **3.6 GB to 1.9 GB** on the
461Vite example under Node, and a cold import of `alchemy/Cloudflare`
462from 523 MB to 214 MB.
463
464- **One sidecar, not one per provider group**
465 ([#1613](https://github.com/alchemy-run/alchemy/pull/1613)). A
466 group is imported the first time a stack uses one of its types, so
467 a Cloudflare-only stack never loads the AWS emulator.
468- **Less engine in every process**
469 ([#1614](https://github.com/alchemy-run/alchemy/pull/1614)).
470 `ProviderLayer.dual` stops building its default variant at
471 registration, the CLI bins skip the `alchemy/Cli` barrel, and the
472 Cloudflare provider no longer imports all 122 distilled services
473 for one helper. Sidecar heap: 193 MB → 98 MB.
474- **Transforms are cached on disk**
475 ([#1615](https://github.com/alchemy-run/alchemy/pull/1615)). The
476 Oxc loader writes output to a per-user cache keyed by path, size,
477 mtime, and tsconfig instead of re-transpiling the source graph
478 (with inline source maps) in every Node process.
479 `ALCHEMY_TRANSFORM_CACHE=0` disables it.
480- **The workerd proxy is a `node:net` byte pipe**
481 ([#1612](https://github.com/alchemy-run/alchemy/pull/1612)) instead
482 of a second workerd per Worker. HTTP/1.1, streaming, and WebSockets
483 pass through untouched; the Worker sees the real `Host`, so
484 `request.url` is the public URL
485 ([#1573](https://github.com/alchemy-run/alchemy/pull/1573)); a
486 request during a broken build gets a 502 with the build error.
487
488Two beta.77 crashes are fixed too: `alchemy dev` under Bun could not
489import any stack because the dev probe intercepted the root's own
490`node_modules`
491([#1617](https://github.com/alchemy-run/alchemy/pull/1617), thanks
492[Austin](https://github.com/austinm911)!), and the interactive CLI died
493with `ReactSharedInternals.H.useMemo` when a consumer's install hoisted
494a different React — Sigil now bundles its own
495([#1636](https://github.com/alchemy-run/alchemy/pull/1636)).
496
497## RDS, S3, and security group fixes
498
499A run of provider fixes from
500[Bjorn Pagen](https://github.com/bjornpagen) and
501[Henning Pokriefke](https://github.com/henningpokriefke) — thanks
502both! — closes gaps where RDS, S3, and EC2 security groups trusted
503state instead of the cloud: drift went unrepaired on unchanged
504deploys, and removing a prop left the old value in place instead of
505restoring the documented default.
506
507**Security groups** apply rule deltas instead of revoke-and-recreate
508([#1592](https://github.com/alchemy-run/alchemy/pull/1592),
509[#1664](https://github.com/alchemy-run/alchemy/pull/1664)), and an
510inline-rule update no longer deletes `SecurityGroupRule` resources
511attached to the same group
512([#1624](https://github.com/alchemy-run/alchemy/pull/1624)).
513`egress` has three distinct states
514([#1623](https://github.com/alchemy-run/alchemy/pull/1623)):
515
516```typescript
517// Omitted: AWS's default — allow all outbound IPv4.
518AWS.EC2.SecurityGroup("DefaultEgress", { vpcId });
519
520// Explicitly empty: no outbound traffic.
521AWS.EC2.SecurityGroup("NoEgress", { vpcId, egress: [] });
522
523// Non-empty: only what is listed.
524AWS.EC2.SecurityGroup("HttpsEgress", {
525 vpcId,
526 egress: [{ ipProtocol: "tcp", fromPort: 443, toPort: 443, cidrIpv4: "0.0.0.0/0" }],
527});
528```
529
530**RDS** `DBInstance` reconciles storage as one configuration —
531allocation, type, IOPS, throughput, and the autoscaling limit sent
532together when AWS requires it
533([#1593](https://github.com/alchemy-run/alchemy/pull/1593),
534[#1594](https://github.com/alchemy-run/alchemy/pull/1594)) — compares
535the observed endpoint port
536([#1595](https://github.com/alchemy-run/alchemy/pull/1595)), and
537restores the engine-default parameter group and the VPC default
538security group when their associations are removed
539([#1596](https://github.com/alchemy-run/alchemy/pull/1596)).
540`DBParameterGroup` converges to declared overrides and reads back
541settled values
542([#1589](https://github.com/alchemy-run/alchemy/pull/1589),
543[#1590](https://github.com/alchemy-run/alchemy/pull/1590)).
544
545```typescript
546yield* AWS.RDS.DBInstance("Db", {
547 engine: "postgres",
548 dbInstanceClass: "db.t3.micro",
549 masterUsername: "admin",
550 manageMasterUserPassword: true,
551 allocatedStorage: 20, // a minimum — RDS cannot shrink storage
552 maxAllocatedStorage: 100, // remove it to disable autoscaling
553});
554```
555
556**S3** buckets and `AWS.state()` gain
557`encryption.blockedEncryptionTypes: ["SSE-C"]`
558([#1588](https://github.com/alchemy-run/alchemy/pull/1588)), compare
559decoded KMS key values so matching keys skip `PutBucketEncryption`
560([#1587](https://github.com/alchemy-run/alchemy/pull/1587)), propagate
561configuration read failures instead of reconciling over them
562([#1586](https://github.com/alchemy-run/alchemy/pull/1586)), and check
563ownership with `GetBucketLocation` so configuration-only operators no
564longer need `s3:ListBucket`
565([#1585](https://github.com/alchemy-run/alchemy/pull/1585)).
566
567Docs: [RDS](/aws/data/rds) · [S3](/aws/data/s3).
568
569## Also in this release
570
571- **Preview packages at `pkg.alchemy.run`**
572 ([#1516](https://github.com/alchemy-run/alchemy/pull/1516)) — every
573 PR, branch, and commit publishes installable packages from a
574 Cloudflare Worker registry that verifies each publication against
575 the GitHub Actions run that built it, so fork PRs publish with no
576 secrets. Dependencies are content-addressed, so shared packages
577 deduplicate across PRs.
578
579 ```sh
580 pnpm install https://pkg.alchemy.run/alchemy/pr:1516
581 pnpm install https://pkg.alchemy.run/alchemy/branch:main
582 ```
583
584- **Typed named entrypoint bindings**
585 ([#1415](https://github.com/alchemy-run/alchemy/pull/1415)) —
586 `Cloudflare.WorkerEntrypoint<Api>(target, "Api")` types `env.API`
587 as `Service<Api>`, so `env.API.greet("alice")` typechecks. Thanks
588 [Michael K](https://github.com/Mkassabov)!
589- **Railway sandboxes: domains, sizing, forks, checkpoints**
590 ([#1661](https://github.com/alchemy-run/alchemy/pull/1661)) —
591 `publicDomains`, `resources: { cpu, memoryGB }`, and
592 `Railway.SandboxCheckpoint` to snapshot a prepared box and restore
593 it as a `template`. Railway reconcilers now select only the GraphQL
594 fields they need ([#1604](https://github.com/alchemy-run/alchemy/pull/1604)),
595 and `MountVolume` actually attaches the disk
596 ([#1584](https://github.com/alchemy-run/alchemy/pull/1584)).
597- **Prisma Compute serves static apps**
598 ([#1208](https://github.com/alchemy-run/alchemy/pull/1208)) —
599 `build: "auto"` recognizes Vite SPA output and adds the Bun
600 entrypoint Prisma requires; `build: { type: "static", spa: true }`
601 serves a prebuilt directory. Thanks
602 [Aman Varshney](https://github.com/AmanVarshney01)! The Prisma
603 provider also moves onto the distilled SDK
604 ([#1290](https://github.com/alchemy-run/alchemy/pull/1290),
605 [#1669](https://github.com/alchemy-run/alchemy/pull/1669)) — thanks
606 [Will Madden](https://github.com/wmadden)!
607- **Cloudflare Containers** — a batch from
608 [Dan van der Merwe](https://github.com/danieljvdm), thanks!
609 `memoryMib` is forwarded
610 ([#1521](https://github.com/alchemy-run/alchemy/pull/1521)),
611 Dockerfile builds export straight to the registry with
612 `buildx build --push`
613 ([#1524](https://github.com/alchemy-run/alchemy/pull/1524)),
614 identical images publish once per deploy
615 ([#1525](https://github.com/alchemy-run/alchemy/pull/1525)), a
616 cached identity that no longer matches Cloudflare plans a visible
617 replacement instead of erroring
618 ([#1523](https://github.com/alchemy-run/alchemy/pull/1523)),
619 interrupted creates recover by generated name
620 ([#1526](https://github.com/alchemy-run/alchemy/pull/1526)), and an
621 unresolved Worker namespace no longer detaches the container's DO
622 ([#1150](https://github.com/alchemy-run/alchemy/pull/1150)). The
623 dev egress interceptor pulls host-native, fixing containers on
624 Apple Silicon
625 ([#1459](https://github.com/alchemy-run/alchemy/pull/1459)) — thanks
626 [Erik Müller](https://github.com/asterikx)!
627- **R2 `put` forwards options on the stream path**
628 ([#1602](https://github.com/alchemy-run/alchemy/pull/1602)) — a
629 declared `sha256`/`md5` on an Effect `Stream` body is now verified
630 by R2 instead of silently dropped.
631- **Crons fire in `Website.Vite` dev workers**
632 ([#1641](https://github.com/alchemy-run/alchemy/pull/1641)). Thanks
633 [zawaki](https://github.com/zawaki)!
634- **`alchemy/Cloudflare/Bridge`**
635 ([#1643](https://github.com/alchemy-run/alchemy/pull/1643)) — the
636 Worker bridge generated wrappers import is a runtime-only entry, so
637 deploy tooling stays out of the bundle. Thanks
638 [Alex](https://github.com/agcty)!
639- **EC2 instances with a fixed private IP replace delete-first**
640 ([#1667](https://github.com/alchemy-run/alchemy/pull/1667)) — the
641 successor can't take the address while the predecessor holds it.
642 Thanks [dawson](https://github.com/Dawsson)!
643- **AWS Organizations rewrite missing ownership tags**
644 ([#1637](https://github.com/alchemy-run/alchemy/pull/1637)) instead
645 of reporting them restored. Thanks
646 [Henning Pokriefke](https://github.com/henningpokriefke)!
647- **Log group ARNs from state are normalized before tagging**
648 ([#1575](https://github.com/alchemy-run/alchemy/pull/1575)) — a
649 `:*` suffix persisted by an old adoption no longer fails every
650 converge with `Invalid resourceArn`. Thanks
651 [Evan Spaeder](https://github.com/evanspaeder)!
652- **ECR images skip updates when only the context directory moves**
653 ([#1591](https://github.com/alchemy-run/alchemy/pull/1591)).
654- **Destroy converges after drifted state** — `SNS.Subscription`
655 and `DynamoDB.Table` tolerate an already-deleted parent
656 ([#1576](https://github.com/alchemy-run/alchemy/pull/1576)), and
657 `ECS.Service` deletes past a draining service
658 ([#1580](https://github.com/alchemy-run/alchemy/pull/1580)).
659 Hetzner servers and their deploy keys recover after an interrupted
660 create ([#1662](https://github.com/alchemy-run/alchemy/pull/1662)).
661- **HTTP state-store errors carry no payloads**
662 ([#1522](https://github.com/alchemy-run/alchemy/pull/1522)) — a
663 failed write reports a failure kind and status, never the request
664 or response body. Thanks
665 [Dan van der Merwe](https://github.com/danieljvdm)!
666- **CLI fixes** — the published `alchemy dev` resolves its sidecar
667 through package exports
668 ([#1678](https://github.com/alchemy-run/alchemy/pull/1678)), colors
669 follow the terminal's detected profile instead of forcing truecolor
670 ([#1679](https://github.com/alchemy-run/alchemy/pull/1679)), and
671 the exec bundle keeps its `.js` extension
672 ([#1625](https://github.com/alchemy-run/alchemy/pull/1625)).
673- **Smaller dependency closure** — `jszip` → `fflate`
674 ([#1608](https://github.com/alchemy-run/alchemy/pull/1608)),
675 `fast-glob` → `tinyglobby`
676 ([#1609](https://github.com/alchemy-run/alchemy/pull/1609)), and
677 Foldkit `^0.160.0` for effect rc.115
678 ([#1618](https://github.com/alchemy-run/alchemy/pull/1618)).
679- **Core docs read as one narrative**
680 ([#1652](https://github.com/alchemy-run/alchemy/pull/1652)) — each
681 concept has one owning page; `/infrastructure-as-effects` redirects
682 to [What is Alchemy](/what-is-alchemy).
683
684## Where to go next
685
686- [Git](/git) · [Getting Started](/git/getting-started) · [Tutorial](/git/tutorial/part-1)
687- [Stripe](/stripe) · [Sell a subscription](/stripe/guides/subscriptions) · [Connect](/stripe/guides/connect)
688- [Worker Previews](/cloudflare/compute/previews)
689- [GitHub](/github)
690- [Local development](/environments/local-development)
691- [RDS](/aws/data/rds) · [S3](/aws/data/s3)
692- [CHANGELOG](https://github.com/alchemy-run/alchemy/blob/main/CHANGELOG.md#v200-beta78)
693- [Compare v2.0.0-beta.77 → v2.0.0-beta.78](https://github.com/alchemy-run/alchemy/compare/v2.0.0-beta.77...v2.0.0-beta.78)