Domain.Story reference
Source:
packages/domain/src/Story.ts
Given / when / then specifications of a domain model, run against the real model in memory: policies run for real, external system calls wait until you resolve them, and a clock you control stamps commands and fires scheduled ones. A story fails on any rejection, external system call or saga defect it doesn’t account for.
Story: Writing a story
Section titled “Story: Writing a story”import { it } from "@effect/vitest";import { Story } from "@alchemy.run/domain/Story";import { Account, InsufficientFunds, MoneyDeposited, MoneyWithdrawn, WithdrawMoney } from "../src/Account.ts";import { Bank, BankPolicies } from "../src/Bank.ts";
const story = Story.make(Bank, { layer: BankPolicies });const account = Account.ref("a-1");
it.effect("withdraws within the balance, and refuses beyond it", () => story( Story.given(account, new MoneyDeposited({ amount: 100, balanceAfter: 100 })), Story.when(account, new WithdrawMoney({ amount: 30 })), Story.then(account, new MoneyWithdrawn({ amount: 30, balanceAfter: 70 })), Story.replied({ balance: 70 }), Story.when(account, new WithdrawMoney({ amount: 100 })), Story.rejected(new InsufficientFunds({ balance: 70, requested: 100 })), ),);Story: External systems and the clock
Section titled “Story: External systems and the clock”Answer an external system call, then move the clock
Story.expectCall(FraudCheck.score, { accountId: account.id, amount: 60_000 }),Story.resolve(FraudCheck.score, { risk: 0.95 }),Story.clock("2026-01-02T09:00:00Z"),Name calls when several are waiting
Story.expectCall("a-1", FraudCheck.score, { accountId: a1.id }),Story.expectCall("a-2", FraudCheck.score, { accountId: a2.id }),Story.resolve("a-2", FraudCheck.score, { risk: 0.1 }),Story.resolve("a-1", FraudCheck.score, { risk: 0.95 }),Story: Sagas
Section titled “Story: Sagas”Story.saga(ExecuteTransfer, transfer, { _tag: "Running" }),Story.signal(ExecuteTransfer, transfer, Decision, "approved"),