Skip to content

Domain.Api reference

Source: packages/domain/src/Api.ts

The public surface of a domain for one kind of client: the commands it may send, the views and streams it may read, and the queries it may ask. Nothing in an Api needs a handler. The class is an Effect: yield* MyApi gives a Worker its fetch. Call it with a typed client over Domain.protocolHttp, Domain.protocolWebSocket or Domain.protocolInProcess, or with clientHttp, clientWebSocket or clientInProcess, which provide them.

import * as Domain from "@alchemy.run/domain";
import { Account, DepositMoney, WithdrawMoney } from "./Account.ts";
import { AccountSummary } from "./AccountSummary.ts";
import { Bank } from "./Bank.ts";
// Serving something Bank does not hold is a type error; nothing unlisted is served.
export class CustomerApi extends Domain.Api("CustomerApi", {
model: Bank,
commands: ["DepositMoney", "WithdrawMoney"],
views: ["AccountSummary"],
}) {}
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, BankPolicies } from "./Bank.ts";
import { CustomerApi } from "./CustomerApi.ts";
import { SessionLive } from "./Session.ts";
const BankLive = SessionLive.pipe(
Layer.provideMerge(Bank.layer(BankPolicies)),
Layer.provide(DomainCloudflare.layerDurableObjects()),
);
export default class BankWorker extends Cloudflare.Worker<BankWorker>()(
"BankWorker",
{ main: import.meta.url },
Effect.gen(function* () {
const api = yield* CustomerApi;
return { fetch: api.fetch };
}).pipe(Effect.provide(BankLive)),
) {}

Domain.protocolWebSocket sends commands, get and list over HTTP, and opens a WebSocket for each watch and events, pushed by the object that owns the key. Domain.protocolHttp sends everything over HTTP.

const bank = yield* CustomerApi.client;
const { reply } = yield* bank.DepositMoney(accountId, { amount: 1_000 });
const summary = yield* bank.AccountSummary.get(accountId);
yield* bank.AccountSummary.watch(accountId).pipe(
Stream.runForEach(({ state }) => Effect.log(state?.balance)),
);

clientInProcess serves the Api in the caller’s process, through the middleware of everything it serves, with nothing sent over a network. Code that has already authenticated its caller provides a middleware Layer that trusts it.

const asSystem = Layer.succeed(Session, (effect) =>
Effect.provideService(effect, CurrentActor, system("payments-webhook")),
);
const bank = yield* CustomerApi.clientInProcess.pipe(Effect.provide(asSystem));
yield* bank.DepositMoney(accountId, { amount: 1_000 });

The last argument is its annotations: data for what is built on the model, read back as X.annotations. Add your own keys by augmenting Domain.Annotations.

Its description heads the CLI’s root help and is the OpenAPI document’s info.description.

export class CustomerApi extends Domain.Api(
"CustomerApi",
{ model: Bank, commands: ["DepositMoney"], views: ["AccountSummary"] },
{ description: "The bank, as a customer reaches it." },
) {}