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

docs: show where uniqueness-style rules belong - #150

Merged
btravers merged 2 commits into
mainfrom
docs/39-uniqueness-rules
Oct 6, 2026
Merged

btravers merged 2 commits into
mainfrom
docs/39-uniqueness-rules

Conversation

@btravers

@btravers btravers commented Oct 6, 2026

Copy link
Copy Markdown
Contributor

Closes #39

Summary

  • New how-to, Enforce a uniqueness rule (docs/how-to/enforce-uniqueness.md, added to the how-to sidebar). It separates a state invariant (closed-world, in invariants), a command precondition (preflight lookup in the use case, feedback only), and a database constraint (the unique index, the only one that holds under concurrency). It links Ecto's validations/constraints as prior art and Let an invariant carry a domain error code, not just a message #70 for stable error codes.
  • Runnable example in examples/billing-persistence/src/uniqueness.ts: registerOrganization builds an Organization, runs a preflight slugIsTaken, then inserts. An in-memory store that checks and writes in one synchronous step stands in for a DB unique index. The docs say plainly that a real unique index plays that role.
  • The conflict maps to a typed SlugTaken (TaggedError) whether the preflight or the constraint caught it. It is deliberately not an InvalidEntity. Only a UniqueViolation on the named constraint is qualified that way. Every other store rejection, and any failed lookup, stays a Defect.
  • Short "Uniqueness" sections in the example README and in docs/examples/billing-persistence.md.
  • packages/entity is unchanged: no async predicates and no repository in construction, so there is no changeset.

Test plan

  • uniqueness.spec.ts: two concurrent creates are held until both lookups answer. Both see the slug free, exactly one write lands, and the outcome is [201, 409].
  • A sequential duplicate fails the preflight (409).
  • An invariant failure (422) never reaches the store.
  • A store that rejects with a connection error is a Defect (503), not a conflict.
  • Full gate green: pnpm format --check && pnpm lint && pnpm typecheck && pnpm test && pnpm knip && pnpm build.

🤖 Generated with Claude Code

Add a how-to separating a state invariant, a command precondition and a
database constraint, with a runnable check-then-create example in
billing-persistence. Two concurrent creates both pass the preflight lookup;
the store's write-time unique check rejects one, and that violation is
qualified into a typed SlugTaken while an unavailable store stays a defect.

Closes #39

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings October 6, 2026 08:17
@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 21 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: 41586d48-b239-416f-a921-d8cb337f94d2
📥 Commits

Reviewing files that changed from the base of the PR and between 4039194 and 7eacea5.

📒 Files selected for processing (6)
  • docs/.vitepress/config.ts
  • docs/examples/billing-persistence.md
  • docs/how-to/enforce-uniqueness.md
  • examples/billing-persistence/README.md
  • examples/billing-persistence/src/uniqueness.spec.ts
  • examples/billing-persistence/src/uniqueness.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 review overview

🟢 Approval recommended

It adds only low-risk documentation and a well-tested example (core package untouched), with the sole finding being a minor non-compiling doc snippet.

Review effort: Balanced
Findings: 1 Low severity

Open (1)
What changed in this PR

This PR answers issue #39 ("where do uniqueness-style rules belong?") with documentation and a runnable example rather than any library change. It establishes a three-layer separation — a closed-world state invariant (in invariants), an open-world command precondition (preflight lookup, feedback only), and a database constraint (the unique index, the only layer that holds under concurrency) — and shows how to map a known unique-violation back to a typed SlugTaken error while keeping infrastructure failures in the Defect channel. The packages/entity core is deliberately untouched (no async predicates, no repository in construction), so no changeset is included.

Changes:

  • Adds a runnable uniqueness.ts example (registerOrganization + in-memory store standing in for a unique index) with a deterministic concurrency spec asserting [201, 409].
  • Adds a new how-to guide (enforce-uniqueness.md) plus README and example-doc sections, and wires the how-to into the VitePress sidebar.
File Description
examples/​billing-persistence/​src/​uniqueness.ts New example: typed SlugTaken, in-memory store, check-then-create use case routing unexpected rejections to Defect.
examples/​billing-persistence/​src/​uniqueness.spec.ts Spec covering preflight duplicate, concurrent-race enforcement, invariant short-circuit, and store-down Defect.
examples/​billing-persistence/​README.md Adds a "Uniqueness" section summarizing the example and linking the how-to.
docs/​how-to/​enforce-uniqueness.md New how-to guide separating state invariant, precondition, and DB constraint.
docs/​examples/​billing-persistence.md Adds a "Uniqueness" section cross-linking the how-to.
docs/​.vitepress/​config.ts Adds the new how-to to the shared guide sidebar.

The example's unthrown API usage (fromPromise/fromSafePromise/.toAsync/.ensure), the Entity.CreateInput/Entity.InvalidEntity namespace types, all doc cross-links, and the concurrency test's determinism were verified. One minor documentation snippet uses a zod schema value in type position; see the inline comment.


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread docs/how-to/enforce-uniqueness.md Outdated
Comment thread docs/how-to/enforce-uniqueness.md Outdated
The store handed raw promises to the use case, which pulled the
violation back out with `instanceof`. The port now returns
AsyncResult: an adapter triages the driver rejection once, at its own
`fromPromise` boundary, into a `UniqueViolation` TaggedError or a
defect, and the use case maps the violation with `mapErrCases`.

Also type `SlugTaken`'s payload as `Organization["slug"]` in the how-to,
where `Slug` named a value in type position.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@btravers
btravers merged commit 2fe10af into main Oct 6, 2026
14 checks passed
@btravers
btravers deleted the docs/39-uniqueness-rules branch October 6, 2026 08:35
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.

Guidance (or support) for async invariants — uniqueness-style rules

2 participants