Library / SDK
alchemy-run/alchemy-async avatar
alchemy-run/alchemy-async

alchemy-async makes infrastructure out of memoised async functions and deletes at finalize

Infrastructure as TypeScript

2,209 stars138 forksTypeScriptApache-2.0

At a glance

What is it?
An embeddable infrastructure-as-code library written entirely in one language, where a resource is a function and the reconciliation happens when you call finalize. The interesting corners are the AI-first strategy that explains its example count, and a clean-build script that deletes your local environment file before fetching it again.
Who is it for?
alchemy-async is worth a look if you write TypeScript for the application and do not want a second language, a second toolchain and a second state file to provision infrastructure next to it. Three things to know before you commit.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 66 days ago.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 5, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A resource is a memoised function, and finalize is the only thing that deletes

The whole abstraction is one sentence: resources are simple memoised async functions. That is why the library needs no provider plugin architecture and no YAML, and why it can run anywhere JavaScript runs, including the browser, serverless functions and durable workflows.

The sample shows the lifecycle in nine lines. You initialise an application with a name for its default state, create a resource by awaiting a call, pass bindings between resources by referencing earlier ones, and wrap the two secrets in a call that marks them as secret rather than plain values.

Then the last call, and the comment above it says what it does: finalising the application triggers deletion of orphaned resources.

That is the whole reconciliation model, and it has a consequence worth stating plainly. Nothing is deleted when you remove a line from your file. Deletion happens at the point you finalise, which means a process that throws, or exits, or is killed before that call leaves resources behind in your cloud account. For a library whose selling point is that it is a JavaScript function you can embed in an application, that ordering is a deliberate choice with a real operational edge.

State is stored in files in your project, which the feature list says you can inspect, modify and check into version control.

The last line of the quick start sample is a stray backtick

The sample is reproduced here exactly as it appears in the document, and the last line inside the block is a single backtick on a line of its own:

ts
import alchemy from "alchemy";

// initialize the app (with default state $USER)
const app = await alchemy("cloudflare-worker");

// create a Cloudflare Worker
export const worker = await Worker("worker", {
  name: "my-worker",
  entrypoint: "./src/index.ts",
  bindings: {
    COUNTER: counter,
    STORAGE: storage,
    AUTH_STORE: authStore,
    GITHUB_CLIENT_ID: alchemy.secret(process.env.GITHUB_CLIENT_ID),
    GITHUB_CLIENT_SECRET: alchemy.secret(process.env.GITHUB_CLIENT_SECRET),
  },
});

// finalize the alchemy app (triggering deletion of orphaned resources)
await app.finalize();
`

Inside a fenced block a lone backtick has no meaning, so this renders as one stray character on the final line of the example rather than as a structural problem. It reads like a keystroke that survived a paste.

Worth mentioning because of where it sits. The sample is the first thing in the document after the two-sentence description, and it is the only code in the entire readme. Everything else is a feature list and a list of links.

The feature list itself is eight positions rather than prose, and it does not mention finalize, or the ordering requirement, or what happens to orphaned resources. The only statement anywhere that deletion is an explicit final step is the comment inside this block.

One language, no second toolchain, and the comparison is named outright

The positioning is stated as a contrast with three named incumbents: unlike similar tools in the infrastructure-as-code space, this one is implemented entirely in ESM-native TypeScript rather than in a separate language, a templating configuration, or a cloud-specific declaration format.

The feature list then gives eight positions, and several of them are about what the project refuses to do. No second language, toolchain, process or service to carry around. No strong opinions about how you structure your codebase or where you store state. No service, because the state files are local.

Two more are worth pulling out. Extensibility is a function: you implement your own resource with one function, which is the same property that makes the abstraction learnable and that means an unsupported provider is a day of work rather than a wait. And the ESM-native point comes with a stated runtime preference for a specific JavaScript runtime, which tells you the library is written against a modern engine rather than the oldest one you might have to support.

The result is a library whose selling point is the absence of things. That is a coherent position for a team that already lives in one language, and a poor fit for anyone whose organisation standardises on the incumbent tools for reasons of audit rather than taste.

The AI-first bullet is the strategy behind twenty-six example directories

One of the eight features is a position on how the project should grow. It says it actively encourages you to use language models to create, copy, fork or modify resources to fit your needs, and gives the reason: no more waiting around for a provider to be implemented, just do it yourself in a few minutes.

That is a growth strategy, and the example directory is its output. There are twenty-six example directories in the tree, covering plain workers, containers, durable objects with a websocket, email, key-value storage, server-side rendering with several frameworks, a Vite site with a container, a Vite site without, a Vite proxy service, a loader pattern, a simple worker, an Astro site, a Bun single-page app, a Next.js app, a Nuxt pipeline, a Redwood app with a database, a SvelteKit app, a TanStack Start app, a Prisma-backed worker, a LiveStore project, an AI search worker, a Docker example and a PlanetScale project with an ORM.

The readme lists nine of them. Seventeen are not mentioned there, including most of the framework and database integrations.

That is curation rather than an error, but it does mean the readme's example list understates the surface by two thirds, and it means the strategy in that bullet is visible mostly as unlisted directories rather than as documentation.

The repository root is a private monorepo whose own version is zero

The root package file is named for the monorepo rather than the library, is marked private, and its version is zero.

So the repository you clone is not the package you import. The readme's sample imports from a package name that is a workspace member, alongside a templates directory, the examples themselves and a stacks directory. The published version is elsewhere; the tags carry it, and the newest is a sub-one release from August.

The workspace also uses a dependency catalogue rather than repeating versions. Seven packages are pinned once, in a single block, and every workspace member resolves them from there: the container integration, the worker type definitions, a Vite plugin, a local worker emulator, Vite itself, TypeScript and the deployment tool. That is the right pattern for a monorepo of this size and it is the reason a single version bump does not touch forty manifests.

The tree adds three files whose names are agent instructions rather than configuration, along with a rules file for one editor and a directory for another. For a library whose stated ambition is to be extended by generated code, having generated-code instructions checked in is coherent.

The clean build removes ignored files, including your environment file

There is a script called clean in the script list, and a separate one called build:clean, and the second one is worth reading before you ever type it.

It runs a git clean with the flags that force removal of untracked files, including ignored ones. It then reinstalls, builds, reinstalls again, downloads the environment file, and builds the example monorepo.

The environment file it downloads is written by another script, which pipes the output of a hosted secrets service into a dot-env file in the project root.

So the sequence is: delete every untracked and ignored file in your working tree, then fetch your secrets from a third-party service and write them to disk. If you keep anything local in an ignored file and you are not the holder of the credentials for that secrets project, the clean build destroys it and cannot get it back.

This is normal for a monorepo whose test fixtures need real credentials. It is also the kind of script that is safe on the machine of whoever wrote it and hostile on a fresh clone, and it is one line of a script list with no warning attached.

The project deploys itself, and a pure TypeScript tree contains a submodule

Four scripts in the list run this library on its own infrastructure. One deploys the repository itself by running a file in the stacks directory, one deploys the documentation website, one destroys that website, and one deploys a telemetry stack. All four are executed with the same JavaScript runtime rather than with a deployment CLI.

That is a reasonable dogfooding choice and it also means the repository's own website is a resource managed by the resource framework, so a bug in the library can take the documentation offline.

Two more details. One script generates the project's AWS control layer from a script under a scripts directory, so part of the provider surface is code-generated rather than hand-written, which is the same answer to the same problem the AI-first feature gives.

And the tree contains a submodule configuration file. There is nothing in the readme about it, and for a repository whose stated identity is being pure TypeScript with no second language and no extra tooling, a git submodule is the one dependency in the list that a reader cannot account for from the documentation.

The last release and the last commit carry the same timestamp to the second, which means the newest tag is the tip of the default branch rather than a point behind it.

Editorial conclusion

alchemy-async is worth a look if you write TypeScript for the application and do not want a second language, a second toolchain and a second state file to provision infrastructure next to it. Three things to know before you commit. Deletion is not automatic when you drop a resource; it happens when you call finalize, so an application that exits before that point leaves orphans behind. The clean build script removes ignored files including your local environment file and then re-downloads it from a hosted secrets service, so read it before you run it. And state lives in files in your project that you are told you can inspect, edit and commit, which is a feature and also a reason to think about what ends up in version control.

Frequently asked questions

What is the alchemy-async library?

An embeddable, TypeScript-native infrastructure-as-code library. Resources are simple memoised async functions that create, update and delete cloud resources, and they can run in any JavaScript runtime including the browser, serverless functions and durable workflows. State is stored in files inside your own project.

How does alchemy-async delete resources?

By an explicit finalisation call. The sample's last line awaits the application's finalize method, and the comment above it says this triggers deletion of orphaned resources. Nothing is deleted when a resource is removed from your file; deletion happens when you finalise.

How does alchemy-async compare with Pulumi, Terraform or CloudFormation?

It is implemented entirely in ESM-native TypeScript, with no second language, toolchain, process or service. It also takes no position on how you structure your codebase or where state lives, and it runs no service of its own, with state files kept in your project.

How do you add support for a cloud provider that is not built in?

By writing your own resource as a function. The project states this as a feature, encouraging you to use language models to create, copy, fork or modify resources rather than waiting for a provider to be implemented officially.

How is alchemy-async built and tested?

The root is a private workspace monorepo at version zero with a shared dependency catalogue. Tests run under a runner configured at the root, with a fast variant that excludes the create and smoke test files and an opt-in variant that runs everything. Checks are a project build followed by a formatting check.

Is there anything dangerous about the alchemy-async build scripts?

The clean build script runs a git clean that removes untracked and ignored files, then reinstalls, builds and downloads the environment file from a hosted secrets service into the project root. Local content in ignored files is deleted by that step.

Official sources

  1. alchemy-run/alchemy-async on GitHub
  2. License: Apache-2.0
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/alchemy-run-alchemy-async.svg)](https://hysenlabs.com/projects/alchemy-run-alchemy-async)