Domain.Query reference
Source:
packages/domain/src/Query.ts
A question a client asks across keys, answered from a store you own: the
table, index or warehouse a Projection writes. Where a view is one
key’s read model, a query is a message, like a command: it declares its
input, its result, the middleware that guards it, and the rejections it
may return. Its Layer is one handler: it checks who may ask, then reads
the store, scoped to what the caller may see. List it in the model’s
queries, and in an Api to give clients a typed method.
Query: Declaring a query
Section titled “Query: Declaring a query”import * as Domain from "@alchemy.run/domain";import * as Schema from "effect/Schema";import { AccountId } from "./Account.ts";import { Ledger } from "./Ledger.ts";import { NotOwner } from "./NotOwner.ts";import { Session } from "./Session.ts";
export const LedgerEntry = Schema.Struct({ id: Schema.String, kind: Schema.String, amount: Schema.Int });
export class AccountLedger extends Domain.Query("AccountLedger", { input: { account: AccountId }, success: LedgerEntry, paged: true, waitsFor: [Ledger], middleware: [Session], rejects: [NotOwner],}) {}Query: Implementing it
Section titled “Query: Implementing it”The construction Effect runs once and resolves the store and any model
member the handler reads. The handler gets the input and, for a paged
query, the page asked for. It refuses a caller by failing with one of the
query’s rejections, and returns the entries and the cursor of the next
page (null on the last, never a short page).
export const AccountLedgerLive = AccountLedger.toLayer( Effect.gen(function* () { const store = yield* LedgerStore; const summaries = yield* AccountSummary; return Effect.fn(function* ({ account }, page) { const actor = yield* CurrentActor; const summary = yield* summaries.get(Account.ref(account)).pipe(Effect.orDie); const owner = Option.getOrNull(summary)?.customerId; if (owner && owner !== actor.customerId) return yield* new NotOwner(); return yield* store.forAccount(account, page); }); }),);Query: Asking it
Section titled “Query: Asking it”Most calls are plain reads and get what the store holds now.
A page at a time
const page = yield* bank.AccountLedger({ account: accountId }, { take: 20 });if (page.cursor) yield* bank.AccountLedger({ account: accountId }, { after: page.cursor });Right after its own command, a client passes the command’s receipt as
atLeast. The query finds the keys to wait on in that command’s events,
waits until every read in waitsFor has handled them, then runs the handler.
The caller passes receipts, never projections or keys: one, or one per
command when it wrote to several aggregate instances.
Reading your own write
const receipt = yield* bank.DepositMoney(accountId, { amount: 100 });const page = yield* bank.AccountLedger({ account: accountId }, { atLeast: receipt });Query: Annotating queries
Section titled “Query: Annotating queries”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.
An Api’s MCP tools, CLI help and OpenAPI document show its description.
export class AccountLedger extends Domain.Query( "AccountLedger", { input: { account: AccountId }, success: LedgerRow, paged: true }, { description: "One account's ledger, a page at a time." },) {}