Skip to content

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, projections and queries run for real, calls to stubbed external systems wait until you answer them, calls to the others run through their adapters while the story records them, and a clock you control stamps commands and fires scheduled ones. A story fails on any committed event, rejection, external system call or saga defect it doesn’t account for.

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 })),
),
);

Answer an external system call, then move the clock

Story.call(FraudCheck.score, { accountId: account.id, amount: 60_000 }),
Story.succeed(FraudCheck.score, { risk: 0.95 }),
Story.clock("2026-01-02T09:00:00Z"),
Story.advance("1 day"),

Name calls when several are waiting

Story.call("a-1", FraudCheck.score, { accountId: a1.id }),
Story.call("a-2", FraudCheck.score, { accountId: a2.id }),
Story.succeed("a-2", FraudCheck.score, { risk: 0.1 }),
Story.succeed("a-1", FraudCheck.score, { risk: 0.95 }),

Assert a real call to a system that is not stubbed

const story = Story.make(Bank, { layer: BankHandlers, stub: [FraudCheck] });
story(
Story.when(account, new RecordSettlement({ settlementId: "st_1", amount: 500 })),
Story.call(Payments.refund, { settlementId: "st_1", amount: 500 }),
Story.returned(Payments.refund, { refundId: "re_st_1" }),
).pipe(Effect.provide(StripePayments));

Fail a call with the operation’s declared error, or a defect

Story.call(LockVendor.deleteCode, { codeId: "pc_1" }),
Story.fail(LockVendor.deleteCode, new LockVendorRefused({ reason: "gateway-lost" })),
Story.call(LockVendor.deleteCode, { codeId: "pc_2" }),
Story.die(LockVendor.deleteCode, new Error("socket closed")),
Story.provide(CurrentActor, sam),
Story.query(AccountLedger, { account: account.id }, [{ kind: "MoneyDeposited", amount: 100 }]),
Story.provide(CurrentActor, alex),
Story.query(AccountLedger, { account: account.id }),
Story.rejected(new NotOwner()),
Story.saga(ExecuteTransfer, transfer, { _tag: "Running" }),
Story.signal(ExecuteTransfer, transfer, Decision, "approved"),