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

feat!: require an identity field on every aggregate - #167

Merged
btravers merged 1 commit into
mainfrom
feat/aggregate-requires-identity
Oct 6, 2026
Merged

btravers merged 1 commit into
mainfrom
feat/aggregate-requires-identity

Conversation

@btravers

@btravers btravers commented Oct 6, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Entity.aggregate now requires at least one field flagged identity: true. A field map without one is a compile error (an inline __anAggregateRootNeedsAnIdentityField rejection on the field map, so TypeDoc gets no new internal name), and a declaration that gets past the types throws while it runs. That follows the precedent of a root's redeclared field.
  • sameIdentityAs is therefore always available on an aggregate.
  • Entity is unchanged: identity stays optional, because it also models aggregate parts and value-like records that have none.
  • Every aggregate in the repo already flags its id, so no example changes.
  • Breaking for 0.8.0 (where Entity.aggregate shipped without the rule): a minor changeset marked Breaking, with the migration line.

Test plan

  • aggregate.test-d.ts: an aggregate without an identity field doesn't compile
  • aggregate.spec.ts: forced past the types, the declaration throws, naming the aggregate
  • Full gate: format, lint, typecheck (incl. both emit passes), test, knip, build; TypeDoc warnings back to 1
  • CI green

🤖 Generated with Claude Code

An aggregate root is what other aggregates reference and what a
repository loads, so `Entity.aggregate` now rejects a field map with no
`identity` field: a compile error (an inline rejection literal) plus a
declaration-time defect, the redeclaration precedent. `sameIdentityAs`
is therefore always available on an aggregate. `Entity` keeps identity
optional, since it also models aggregate parts and value-like records.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Copilot AI balanced review requested due to automatic review settings October 6, 2026 12:52
@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 36 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: 073c8616-da86-4227-9aad-6aa4e5ef4e5f
📥 Commits

Reviewing files that changed from the base of the PR and between 79162db and 2370c4d.

📒 Files selected for processing (8)
  • .changeset/aggregate-requires-identity.md
  • CLAUDE.md
  • docs/how-to/model-an-event-driven-aggregate.md
  • docs/reference/declaration.md
  • docs/typedoc.json
  • packages/entity/src/aggregate.spec.ts
  • packages/entity/src/aggregate.test-d.ts
  • packages/entity/src/aggregate.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

The change is small, self-contained, consistently follows existing type-rejection and runtime-defect precedents, and is covered by both a type-level and a runtime test with no issues found.

Review effort: Balanced
Findings: None

What changed in this PR

This PR makes identity mandatory for aggregates, closing a gap where Entity.aggregate (shipped in 0.8.0) allowed an aggregate root with no identity field even though a root is precisely what other aggregates reference and what a repository loads by. The rule is enforced at two levels: a compile-time rejection (an inline __anAggregateRootNeedsAnIdentityField literal intersected onto the field-map argument, so TypeDoc gets no new internal name) and a runtime throw for callers who bypass the types. Plain Entity is intentionally left unchanged, since it also models aggregate parts and value-like records that have no identity.

Changes:

  • Added a type-level identity requirement ([IdentityKeys<S>] extends [never]) plus a runtime guard that throws, naming the tag, when no field is flagged identity: true.
  • Added a negative type-test (@ts-expect-error) and a runtime-throw spec test.
  • Updated docs (declaration reference, how-to, CLAUDE.md, typedoc exclusions) and a Breaking changeset.
File Description
packages/​entity/​src/​aggregate.ts Adds the type-level rejection on the field-map argument and a runtime throw mirroring entity.ts identity detection and the base.ts defect precedent.
packages/​entity/​src/​aggregate.test-d.ts Adds a @ts-expect-error case proving an aggregate without an identity field does not compile.
packages/​entity/​src/​aggregate.spec.ts Adds a runtime test asserting the declaration throws, naming the aggregate.
docs/​typedoc.json Adds IdentityKeys to intentionallyNotExported to keep TypeDoc warnings at 1.
docs/​reference/​declaration.md Documents the mandatory identity field for aggregates.
docs/​how-to/​model-an-event-driven-aggregate.md Notes the identity requirement in the how-to guide.
CLAUDE.md Records the new aggregate identity rule in the architecture notes.
.changeset/​aggregate-requires-identity.md Minor Breaking changeset with the migration line.

The type-level check matches the existing sameIdentityAs typing (types.ts:318,652), the runtime guard is identical to the identity detection in entity.ts:129, and the throw follows the base.ts redeclaration precedent (tag-prefixed message with oxlint-disable unthrown/no-throw). The existing Subscription example already flags identity: true, so no example changes are needed, and the minor bump is correct for a breaking change on a 0.x package.


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

@btravers
btravers merged commit 12e5485 into main Oct 6, 2026
15 checks passed
@btravers
btravers deleted the feat/aggregate-requires-identity branch October 6, 2026 13:16
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.

2 participants