Skip to content

Part 5: Deploy to Cloudflare

Put the bank on Cloudflare. Every account, customer, view and policy becomes a Durable Object, and a Worker serves the Api. You need a configured Cloudflare profile.

Terminal window
pnpm add https://pkg.alchemy.run/@alchemy.run/cloudflare/pr:2930:51c662a
src/BankWorker.ts
import * as Cloudflare from "@alchemy.run/cloudflare";
import * as DomainCloudflare from "@alchemy.run/cloudflare/Domain";
import * as Effect from "effect/Effect";
import * as Layer from "effect/Layer";
import { Bank, BankHandlers } from "./Bank.model.ts";
import { BankApi } from "./BankApi.api.ts";
import { SessionLive } from "./Session.middleware.ts";
const BankLive = SessionLive.pipe(
Layer.provideMerge(Bank.layer(BankHandlers)),
Layer.provide(DomainCloudflare.layerDurableObjects()),
);
export default class BankWorker extends Cloudflare.Worker<BankWorker>()(
"BankWorker",
{ main: import.meta.url },
Effect.gen(function* () {
const api = yield* BankApi;
return { fetch: api.fetch };
}).pipe(Effect.provide(BankLive)),
) {}

DomainCloudflare.layerDurableObjects() hosts the model: every aggregate, view and policy becomes a Durable Object class on this Worker, with no bindings to write. SessionLive is the middleware the Api’s entries declare. yield* BankApi serves the Api, and its fetch becomes the Worker’s.

alchemy.run.ts
import * as Alchemy from "alchemy";
import * as Cloudflare from "@alchemy.run/cloudflare";
import * as Effect from "effect/Effect";
import BankWorker from "./src/BankWorker.ts";
export default Alchemy.Stack(
"Bank",
{ providers: Cloudflare.providers(), state: Cloudflare.state() },
Effect.gen(function* () {
const bank = yield* BankWorker;
return { url: bank.url.as<string>() };
}),
);

The stack deploys the Worker and returns its URL.

Terminal window
pnpm alchemy dev

alchemy dev runs the Worker and its Durable Objects on your machine and prints the URL, such as http://localhost:1337.

scripts/try.ts
import * as Domain from "@alchemy.run/domain";
import * as Effect from "effect/Effect";
import * as FetchHttpClient from "effect/http/FetchHttpClient";
import * as Layer from "effect/Layer";
import * as Stream from "effect/Stream";
import { AccountId } from "../src/Account.id.ts";
import { BankApi } from "../src/BankApi.api.ts";
import { CustomerId } from "../src/Customer.id.ts";
import { sessionClient } from "../src/Session.middleware.ts";
const [url, customerId = "c-1"] = process.argv.slice(2);
const accountId = AccountId.make(`${customerId}-main`);
const program = Effect.gen(function* () {
const bank = yield* BankApi.client;
yield* bank.RegisterCustomer(CustomerId.make(customerId), { name: customerId }).pipe(
Effect.catchTag("AlreadyRegistered", () => Effect.void),
);
const summaries = bank.AccountSummary.watch(accountId).pipe(
Stream.map(({ state }) => state),
Stream.filter((summary) => summary !== null && summary.owner !== null),
);
// The policy opens the account shortly after registration: wait for it.
yield* Stream.runHead(summaries);
// Print the balance now and after every change, in the background.
yield* summaries.pipe(
Stream.runForEach((summary) => Effect.log(`balance: ${summary?.balance}`)),
Effect.forkChild,
);
yield* Effect.sleep("1 second");
yield* bank.DepositMoney(accountId, { amount: 100 });
yield* Effect.sleep("1 second");
yield* bank.WithdrawMoney(accountId, { amount: 30 });
yield* Effect.sleep("2 seconds");
});
const Connection = Layer.mergeAll(Domain.protocolHttp(url!), sessionClient(customerId)).pipe(
Layer.provide(FetchHttpClient.layer),
);
Effect.runPromise(program.pipe(Effect.scoped, Effect.provide(Connection)));

The script registers a customer and waits for the policy to open their account, which happens shortly after registration. Then it watches the balance in the background while it deposits and withdraws.

Terminal window
bun scripts/try.ts http://localhost:1337
[12:00:01.402] INFO (#9): balance: 0
[12:00:02.613] INFO (#9): balance: 100
[12:00:03.804] INFO (#9): balance: 70

The watch prints the balance each time it changes. Over HTTP it checks for changes a few times a second, so the script pauses between commands to show each one; Part 6 switches to WebSockets, where the bank pushes every change.

Terminal window
pnpm alchemy deploy

The deploy prints the Worker’s url. Run the same script against it.

Terminal window
bun scripts/try.ts https://bank-bankworker-….workers.dev

The bank now runs on Cloudflare, with one Durable Object per account.