A web page that shows you the infrastructure that is currently serving it.
Deploy the sample stack, open the URL, and by default you're looking at a live diagram of
the exact Cloudflare resources rendering that diagram in front of you — the Api Worker
serves this viewer as a static asset, and the viewer's own default graph is that same
Worker's deployment state.
Under the hood, retort reads the deployment state of a Cloudflare stack managed by
Alchemy (Infrastructure-as-Effects), extracts a dependency graph of
the cloud resources, and renders it with Foldkit (an Elm-like
frontend framework). Both happen to be built on Effect, the
TypeScript functional ecosystem — which is also where the name comes from: Alchemy's own
metaphor is a retort, the vessel used in distillation. The graph leans into that metaphor
visually too: nodes render as flasks filled with a gradient liquid, and dependency edges
carry a flowing droplet animation.
Alchemy can persist deployment state as plain JSON files on disk (via
State.localState()) instead of a remote state store. This project reads that local
state, extracts a dependency graph of your cloud resources (which resource binds to
which), and renders it as an interactive diagram in the browser.
The sample stack's Api Worker also serves this project's own built viewer as a static
asset (see alchemy.run.ts) — so once deployed, the graph it shows by default is its own.
- Cloudflare only. Other providers (AWS, etc.) are out of scope.
- This is a throwaway proof of concept, not a maintained package. There is no npm publish plan.
- A Cloudflare account.
- Bun.
bun install
# Build the viewer so the Api Worker has something under ./dist to deploy as
# a static asset (see alchemy.run.ts).
bun run build
# Deploy the sample stack (KV Namespace + D1 Database + 2 Workers) to
# Cloudflare. This writes local state files under .alchemy/state/ (gitignored
# — they contain real account/resource identifiers).
bunx alchemy deploy
# Read .alchemy/state/**/*.json and write src/graph.generated.json.
bun run parse-alchemy
# Start the dev server (hot-reloading; separate from the static build above).
bun run devOpen the dev server URL and you'll see each resource as a labeled box, with edges
showing which resource binds to which (e.g. the Api Worker binding a KV Namespace, a
D1 Database, the Auth Worker via a service binding, and the static assets it serves).
Clicking a node highlights it and its directly connected neighbors, dimming the rest of
the graph.
If you skip the deploy/parse steps, the app falls back to a small built-in dummy graph (same shape as the sample stack) so the viewer still renders something out of the box.
| Command | Description |
|---|---|
bun run dev |
Start the Vite dev server |
bun run build |
Build for production |
bun run preview |
Preview the production build |
bun run parse-alchemy |
Regenerate src/graph.generated.json from .alchemy/state/ |
bun run typecheck |
Type-check with tsc --noEmit |
bun run test |
Run the test suite (vitest) |
bun run lint |
Lint src/ (oxlint) |
bun run format |
Format the repo (prettier) |
Each Alchemy state file (one per resource) has a bindings array listing what that
resource depends on. The Api Worker's state file, for example, lists the KV Namespace,
D1 Database, and Auth Worker it binds to, each tagged with a sid (the target's
logicalId) and a binding type (kv_namespace, d1, service, etc.). Reading
bindings alone is enough to reconstruct almost the full dependency graph — nodes come
from each file's logicalId / resourceType, edges from bindings.
The one exception is the static assets a Worker serves (assets in alchemy.run.ts):
that's a plain prop on the Worker's own state file, not a bindings entry, so
scripts/parse-alchemy.ts special-cases it — a Worker with props.assets set gets a
synthetic Viewer node and an edge pointing at it.
The viewer itself is as much a demo of Foldkit's Effect-based testing/devtools story as it is a demo of the graph it renders:
- Schema-validated at every boundary.
scripts/parse-alchemy.tsdecodes each raw Alchemy state file with an EffectSchema.Structbefore it ever becomes a node/edge — unrelated fields (attr,props, account IDs, ...) are silently dropped rather than leaking intograph.generated.json.src/main.tsdecodes that generated file again at runtime withSchema.decodeUnknownOption, so a malformed or stalegraph.generated.jsonfalls back todummyGraphinstead of crashing the app. updatetested with no DOM at all.src/story.test.tsusesfoldkit/story'sgiven/message/modelDSL to dispatch messages straight at theupdatefunction and assert on the resulting model and emitted commands.viewtested without a browser.src/scene.test.tsusesfoldkit/scene'sclick/selector/textDSL to drive the realview+updatepair underhappy-domand assert on rendered output — e.g. clicking#Bucketand checking the detail panel text.- Time-travel devtools, live.
src/entry.tswires@foldkit/devtools'soverlayinto the running app, giving Elm-style time travel in the browser. Thefoldkit-devtoolsMCP server (.mcp.json) exposes the same runtime to AI agents: list dispatched messages, replay to any keyframe, diff two models, or dispatch synthetic messages, all without adding a singleconsole.log.
- No file-watching / live reload of
.alchemy/state/yet; re-runbun run parse-alchemyafter each deploy.
retort is deliberately narrow: it reads local state and draws the binding
graph. If you want a dashboard, look at these instead:
- alchemy-run/alchemy#1640 —
the official dashboard (draft):
alchemy dashboard,deploy --ui, plan approval and apply progress in the browser, hosted variants for Cloudflare and AWS. - pkishorez/monorepo — alchemy-console — a multi-store console for browsing remote state and safely deleting stages.
Neither draws a dependency graph, and neither is built on Foldkit.
Built and deployed against alchemy 2.0.0-beta.67 and foldkit 0.137.0
(September 2026). Alchemy is still in beta and Foldkit is pre-1.0; both
change quickly and nothing here has been re-tested against newer releases.
If it stops working, pin to those versions.
MIT — see LICENSE.