Skip to content
This repository was archived by the owner on Oct 7, 2026. It is now read-only.

docs: model invoice commands that refuse transitions and return events - #155

Merged
btravers merged 1 commit into
mainfrom
docs/136-40-commands-and-events
Oct 6, 2026
Merged

btravers merged 1 commit into
mainfrom
docs/136-40-commands-and-events

Conversation

@btravers

@btravers btravers commented Oct 6, 2026

Copy link
Copy Markdown
Contributor

Closes #136
Closes #40

Summary

Docs and example only. packages/entity is untouched: no new Entity.* member, and update() keeps its return type. No changeset, because the example packages are private.

Example (examples/billing-domain)

  • The invoice is now two variants of BillingDocumentBase. DraftInvoice has editable lines and no number. Invoice has a number, lines marked immutable, and a status of ISSUED, PAID, VOID or UNCOLLECTIBLE. BillingDocument dispatches over draft, invoice and credit note.
  • Three commands are ordinary methods on the variant they apply to:
    • addLine returns Result<DraftInvoice, CurrencyMismatch | InvalidEntity> and announces nothing.
    • issue({ number, at }) returns Result<{ entity: Invoice; events: [InvoiceIssued] }, InvalidEntity>. The number and the instant are external preconditions the caller passes in.
    • void() returns Result<{ entity; events: [InvoiceVoided] }, InvoiceNotVoidable | InvalidEntity> and refuses PAID → VOID, though both states are valid.
  • events.ts holds the internal domain events and a separate, strict, versioned integration contract (InvoiceIssuedV1) that is mapped by hand and leaves out the lines.

Docs

  • New how-to, Write commands and events: commands, typed refusals, variant transitions, external facts, events beside the entity, silent make/update, the outbox transaction seam, integration contracts, and the module boundary.
  • New explanation, Invariants and transitions: state invariants vs transition rules vs external preconditions, an honest account of what update() permits, why lifecycles become variants, and why events come from commands rather than diffs.
  • Updated the billing-domain example page, the examples index, the example README, and a cross-link from Number without gaps. Both new pages are in the sidebar.

Test plan

  • pnpm format --check && pnpm lint && pnpm typecheck && pnpm test && pnpm knip && pnpm build passes. typecheck includes the two-compiler declaration emit (7.0.2 and 5.9.3) and the emitted-.d.ts check.
  • New specs in examples/billing-domain/src/index.spec.ts cover:
    • a forbidden transition between two valid states (paid → void) refused with InvoiceNotVoidable, with the source unchanged
    • the update() bypass of that same transition succeeding
    • success returning a new entity plus exactly one typed event
    • a refused command producing no outcome
    • draft → issued as a change of variant
    • immutable lines on the issued variant
    • commands existing only on their own variant (@ts-expect-error)
    • rehydration and recalculation emitting nothing
    • the integration contract staying narrower than the domain event
  • vitepress build with --force: no dead links, and the cross-page anchor resolves.

Not covered here: a runnable outbox transaction. That belongs to the persistence work in #37, and the how-to only shows the seam.

🤖 Generated with Claude Code

Copilot AI balanced review requested due to automatic review settings October 6, 2026 08:25
@coderabbitai

coderabbitai Bot commented Oct 6, 2026 •

Copy link
Copy Markdown

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 17 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

⚙️ Run configuration
  • Configuration used: Repository: btravstack/entity/.coderabbit.yaml
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 044ddcb0-13ef-4004-89c7-dedf63b8dc9c
📥 Commits

Reviewing files that changed from the base of the PR and between 2271fd2 and 50fabe8.

📒 Files selected for processing (11)
  • docs/.vitepress/config.ts
  • docs/examples/billing-domain.md
  • docs/examples/index.md
  • docs/explanation/invariants-and-transitions.md
  • docs/how-to/number-without-gaps.md
  • docs/how-to/write-commands.md
  • examples/billing-domain/README.md
  • examples/billing-domain/src/events.ts
  • examples/billing-domain/src/index.spec.ts
  • examples/billing-domain/src/index.ts
  • examples/billing-domain/src/vocabulary.ts
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Split the example's invoice into DraftInvoice and Invoice variants of the
billing root, moved by named commands (addLine, issue, void) that return
typed TaggedError refusals and, where something is announced, explicit
domain events beside the new entity. update() is unchanged; the spec pins
the bypass it leaves open.

Add a how-to (Write commands and events) and an explanation (Invariants and
transitions) separating state invariants, transition rules and external
preconditions, and covering the outbox seam and integration contracts.

Closes #136
Closes #40

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@btravers
btravers force-pushed the docs/136-40-commands-and-events branch from ac9dc82 to 50fabe8 Compare October 6, 2026 08:37
@btravers
btravers merged commit e0ce701 into main Oct 6, 2026
14 checks passed
@btravers
btravers deleted the docs/136-40-commands-and-events branch October 6, 2026 08:42
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Document and demonstrate business transitions separately from state invariants Domain events: let update/create surface what changed

2 participants