Skip to content

forge-effect

Write a Cloudflare app in Effect. The compiler checks it's wired right, and one command ships it, a step at a time, or undoes it all.

A D1 database, and a Worker that reads it. That’s a whole app:

guestbook.ts
class Book extends D1.Database<Book>()('book', {
migrations: ['CREATE TABLE entries (id INTEGER PRIMARY KEY, name TEXT, message TEXT, at TEXT)'],
}) {}
const fetch = Effect.gen(function* () {
const book = yield* Book; // a typed D1 client
const entries = yield* book.all('SELECT name, message FROM entries ORDER BY id DESC');
return Response.json(entries);
}).pipe(Effect.orDie);
export default Worker.make('guestbook', { fetch }).bind(Book);

Book is two things at once: the client your code uses, and the declaration of the database it needs. Forget the .bind(Book) and nothing deploys half-wired. You’re stopped before you ship:

$ infra check app.ts
error used-undeclared guestbook.ts:14:16
the Worker uses Book, which it doesn't bind
fix: bind it: .bind(…, Book)

Your editor says the same, as you type: it’s a type error.

And when it’s right, one command makes the database, applies its migrations, uploads the Worker and checks it answers:

$ infra ship app.ts
2.01s ✓ created D1 database guestbook-book
2.47s ✓ applied migration 1 to D1 database guestbook-book
3.68s ✓ created Worker guestbook-guestbook, live
8.16s ✓ smoke test passed: 3 × GET /, answered by the new Worker
8.51s ✓ shipped guestbook-guestbook: https://guestbook-guestbook.you.workers.dev

Resources that are clients

KV, D1, R2, queues, Durable Objects, Workflows, AI, Vectorize and more, each a class you yield* for a typed, Effect-returning client.

Wiring the compiler checks

A Worker must bind what its code uses. Calls between Workers are typed at both ends. Mistakes show up in your editor, not in production.

Deploys that undo themselves

infra ship provisions, migrates, rolls out 10% → 50% → 100% behind smoke tests, and puts everything back if any step fails, or you press Ctrl-C.

Tests without mocks

Every resource has an in-memory stand-in, D1 runs on SQLite, and infra dev runs your whole app in workerd, wired as it deploys.