Skip to content
Agentmart
Submit a tool
This entry duplicates learn-alchemy, which is the one we list in search.

learn-alchemy

Learn how Alchemy 2 turns an Effect program into a planned Cloudflare deployment.

Skill by joelhooks

IndexedMIT★ 27
Write a review

Install

Works with Claude Code, Claude apps, Codex, Cursor, Gemini CLI, GitHub Copilot, VS Code, OpenCode, Goose, Windsurf. Read the repository before you give any tool access to your data.

npx agentmarket install skill/joelhooks-learn-alchemy-edc775

Asks which of your tools to add it to (Claude Code, Codex, Cursor, VS Code, Windsurf, Gemini CLI, Claude Desktop) and shows the change first. See https://agentmart.dev/cli.

Get the weekly diff

The weekly diff: one email a week with new agent tools, notable updates and grade changes, plus security alerts for tools you follow. Double opt-in, one-click unsubscribe.

We use your email only for this newsletter. Privacy

About

Learn how Alchemy 2 turns an Effect program into a planned Cloudflare deployment.

Use cases: cloud

No reviews yet. Installed it? Reviews come from verified installers only. How reviews work

Permissions: not checked yet.

What's inside

Skill name
learn-alchemy
Description
Learn how Alchemy 2 turns an Effect program into a planned Cloudflare deployment.
License
MIT
Instructions
137 lines in SKILL.md · view on GitHub

Files (1)

  • SKILL.md

SKILL.md (first lines)

# Learn Alchemy

Alchemy 2 declares and deploys cloud resources through Effect programs. Effect describes work with typed failures and required dependencies. One program declares resources, bindings, deployment state, and the Worker that uses them.

A service is a named interface for one job. A Layer builds services and supplies their construction dependencies. A contract defines an action's name, input, output, failure, and metadata.

Start with this repo's Cloudflare resources. Alchemy also supports other providers. Learn the vocabulary to write infrastructure and check planned changes.

Sam Goodwin describes an Effect as a promise with more information: its return value, errors, and requirements. His Alchemy shorthand is "if it compiles, it should deploy and run." Use compilation for feedback. Check cloud API failures during deployment.

## The vocabulary

- **Alchemy Stack** groups one infrastructure program and its outputs. Rat-stack exports `Alchemy.Stack("RatStack", ...)` in `apps/infra/alchemy.run.ts`.
- **Provider** supplies the cloud API implementation. `Cloudflare.providers()` appears in the Stack options in `apps/infra/alchemy.run.ts`.
- **State** records the resources that a Stack already owns. Rat-stack uses `Cloudflare.state()` in `apps/infra/alchemy.run.ts`.
- **Resource** is a declared cloud object such as a Zone, DNS record, DNSSEC setting, or Worker. Each gets a stable logical name in `apps/infra/alchemy.run.ts`.
- **Binding** is a typed runtime value attached to a Worker. Rat-stack declares the `CODE_SANDBOX` WorkerLoader and four RateLimit bindings in `apps/site/src/worker.ts`.
- **Plan** shows differences between declared resources and stored deployment state before a provider changes anything. Read it with the `plan` script in `apps/infra/package.json`.
- **Stage** names an isolated deployment state. Rat-stack's production stage is `prod`; the unflagged default is `live_$USER`.
- **Profile** holds provider credentials for the Alchemy CLI. This repo uses an Alchemy profile, not environment variables, for Cloudflare authentication.
- **Adopt** means take ownership of an existing resource explicitly. The existing `ratstack.sh` Zone and its DNSSEC setting use `adopt(true)` in `apps/infra/alchemy.run.ts`.
- **Dev** is local workerd execution with `ALCHEMY_DEV=true`. Rat-stack uses it to skip the production Zone and DNS resources in `apps/infra/alchemy.run.ts`.

## Trace one deploy

Read `apps/infra/alchemy.run.ts` from top to bottom.

1. The file imports Alchemy, the Cloudflare provider, Effect, and the `Mischief` Worker from `apps/site/src/worker.ts`.
2. `Alchemy.Stack` creates the program. `Effect.gen` gives it a place to yield resources and return outputs.
3. `Cloudflare.providers()` supplies Cloudflare operations. `Cloudflare.state()` lets Alchemy compare this run with prior state.
4. `ALCHEMY_DEV` guards resources that should not be touched by local development.
5. The Zone is named `RatstackZone` and adopted because `ratstack.sh` already exists. A Zone normally retains on destroy, and this Stack does not opt into deleting it.
6. The DNS-AID SVCB and TXT records point agent discovery at the deployed site. They use the adopted Zone's `zoneId`.
7. `Cloudflare.DNS.Dnssec` keeps DNSSEC active. It is adopted for the same reason as the Zone.
8. `Mischief` declares the Worker with `ratstack.sh` as its custom domain, `www.ratstack.sh` as a redirect, and port 1337 for dev.
9. The Worker construction yields its WorkerLoader and four RateLimit bindings: `API_PER_IP`, `EXECUTE_GLOBAL`, `EXECUTE_PER_IP`, and `INTEREST_PER_IP`. `Effect.provide(Cloudflare.Workers.RateLimitBinding)` supplies the RateLimit client layer.
10. `apps/site/src/worker.ts` also yields the Interest and InterestIndex Durable Objects. `EVENTS_ENABLED` chooses `Basin` or `basinFoundation`: both keep the bucket, stream, and visitor salt; enabled adds the catalog, sink, and pipeline. The Worker supplies the subscriber delivery implementation when it starts, outside core.
11. The Stack yields `Website` from `apps/web/src/website.ts` and returns both `mischiefUrl` and `websiteUrl`.

The outputs omit the production DNS declarations:

```ts
export default Alchemy.Stack(
  "RatStack",
  { providers: Cloudflare.providers(), state: Cloudflare.state() },
  Effect.gen(function* stack() {
    const mischief = yield* Mischief;
    const website = yield* Website;
    return { mischiefUrl: mischief.url, websiteUrl: website.url };
  })
);
```

The Stack is an Effect. Importing it describes work. The CLI runs it during a plan or deploy.

### Follow the Worker bindings

`apps/site/src/worker.ts` exports `class Mischief extends Cloudflare.Worker<Mischief>()(...) {}` with `main: import.meta.url`, so Alchemy bundles the default Worker export. Its construction Effect yields bindings before it returns the `fetch` implementation. Alchemy populates `Cloudflare.WorkerEnvironment` dynamically; this repo keeps the expected binding shape in local types at that boundary.

```ts
const stageRateLimits = rateLimitDeclarations(yield * Stage);
yield * Cloudflare.WorkerLoader("CODE_SANDBOX");
yield * Cloudflare.RateLimit("API_PER_IP", stageRateLimits.API_PER_IP);
yield * Cloudflare.RateLimit("EXECUTE_GLOBAL", stageRateLimits.EXECUTE_GLOBAL);
yield * Cloudflare.RateLimit("EXECUTE_PER_IP", stageRateLimits.EXECUTE_PER_IP);
yield *
  Cloudflare.RateLimit("INTEREST_PER_IP", stageRateLimits.INTEREST_PER_IP);
const environment = yield * Cloudflare.WorkerEnvironment;
// SAFETY: the Worker construction declares every RateLimitBindings member and CODE_SANDBOX before reading this environment.
const bindings = environment as RateLimitBindings & {
  readonly CODE_SANDBOX: WorkerLoaderBinding;
};
```

`apps/site/src/rate-limits.ts` keeps the four native binding names and their numeric namespace IDs together. `rateLimitsFrom` calls the native `.limit({ key })` method and turns a failed runtime call into a defect instead of failing open.

`apps/site/src/sandbox-worker-loader.ts` consumes the `CODE_SANDBOX` binding. It loads a fresh Dynamic Worker with limits and `globalOutbound: null`, so the code-mode Worker can compute and call declared capabilities without reaching the network. A capability is one named action with shared value definitions and a server-side implementation.

Sam describes the constructor as a Layer: declare dependencies in the Effect, use them, then return the implementation. In his React analogy, dependencies are the hooks at the top. The returned Worker is the component.

Declarations and consumers share one construction Effect. The environment assertion relies on the declared bindings. Check missing bindings in the plan and at runtime; typecheck alone cannot prove they exist.

## Plan, then deploy

The scripts live in `apps/infra/package.json`:

```sh
pnpm infra:plan
pnpm infra:deploy
pnpm infra:dev
pnpm infra:destroy
```

Run the plan first. It shows `create`, `update`, `adopt`, and `noop` actions before a provider is changed. Review replacements, domains, DNS, bindings, and stage before approving a deploy.

Production is stage `prod`. Pass `--stage prod` to the Alchemy CLI. An unflagged deploy defaults to `live_$USER`, which can create a second Worker instead of updating production. Never run an indiscriminate stage destroy. Name the stage explicitly and read its plan first.

Alchemy v2 represents outputs as values whose contents become available during deployment. Resources can still reference one another. Sam compares this to Pulumi's output model. The engine sees the graph before it acts. The engine orders resource operations without hand-written await chains.

## Why not wrangler.toml or Terraform

Wrangler is a good Cloudflare-specific tool. This repo needs one Effect program that declares the Zone, DNS records, DNSSEC, Worker, and the bindings consumed by the Worker, so Alchemy is the chosen boundary.

Terraform would keep infrastructure and the Effect Worker in separate languages and graphs. Alchemy can infer the Worker binding wiring from the same construction code that consumes it, while still showing a plan first.

Keep an existing tool when it already owns the state and the team knows its review path. Choose Alchemy here because the typed application and the cloud graph are one lesson.

## Gotchas from this repo

- The Worker bundle uses Alchemy's Rolldown path, and Node's type stripping is outside the normal TypeScript program. A red `pnpm typecheck` does not stop Alchemy from producing a deploy plan or bundle. Run `pnpm turbo run check test build` first and inspect the failing task. Do not use a deploy to bypass a red gate.
- Cloudflare API changes have broken Alchemy releases before. Check the pinned version in `apps/infra/package.json` and the generated pins page before changing it.
- The repo pins Alchemy to a beta. Alpha and beta APIs drift. Read the current pins and vendored source before copying an example from elsewhere.
- Cloudflare rejected string rate-limit namespace IDs during a deploy even though the local type allowed them. `apps/site/src/rate-limits.ts` uses numeric IDs and documents the boundary.
- `adopt(true)` authorizes adoption. Without it, Alchemy refuses to take over an existing Zone. Keep adoption narrow and never assume a resource is safe to destroy.
- Profiles carry Cloudflare credentials. Do not add provider tokens to `.env`, source files, or Worker bindings for CLI convenience.

## Try it

1. Run `pnpm infra:plan` and read every action. Do not approve a deploy. Identify which resources are `noop` and which would change.

From joelhooks/potion-calendar, MIT.

Signals

Stars
27
Forks
1
Open issues
0
Last commit (this folder)
Oct 20, 2026 (8 days ago)

From GitHub (fetched Oct 24, 2026).

Details

License
MIT
First seen
Oct 24, 2026
Last checked
Oct 24, 2026

Same skill elsewhere

Copies of this SKILL.md published by others; search shows only one of them.

More from joelhooks

Cite this page

learn-alchemy skill. Agent Mart. https://agentmart.dev/skills/joelhooks-learn-alchemy-edc775 (last updated 2026-10-24)