Ts ddd entity
Collection of Agent Skills for Claude Code, Cursor, Codex, Windsurf, and any agent that speaks the open Skills spec. Ships via npm + plugin + skills add.
npx -y skills add llodev/skills --skill ts-ddd-entityAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 2 stars2 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Create, review, or guide Domain Entity implementation in a TypeScript + DDD codebase. Use when: the request touches `*.entity.ts` files under `apps/api/src/<bc>/domain/entities/` or their tests under `apps/api/test/<bc>/domain/entities/`; modeling business rules with `Entity` from `@acme/shared` (dual API `create`/`tryCreate`, `Result` / `Result.combine` validation); validating FK refs with `Id.required` or own id with `Id.tryCreate`; enforcing enum-backed closed-set fields (status / kind / layout / provider / palette) from `@acme/<bc>-contracts` instead of string literals; validating nested entities or arrays element-by-element; adding state transitions via `cloneWith` (leaf entity) or named domain methods like `publish`, `addSection`, `deactivate` that mutate `_field` + `touch()` (aggregate root); exposing typed getters or `toSnapshot()` for persistence boundaries.
SKILL.md
6.2 KB, as published. Nobody here has run it
TypeScript DDD Entity
MANDATORY — READ ENTIRE FILE: Before any implementation step, read
references/entity-pattern.md completely.
Do NOT load other DDD skills (use-case, repository, dto) unless explicitly requested.
Before You Start
Before writing a single line, answer:
- Bounded context: which BC owns this entity? File lands at
apps/api/src/<bc>/domain/entities/<name>.entity.tsand is re-exported by that folder'sindex.tsbarrel. - Identity: is
idoptional on create (useId.tryCreate/ let theEntitybase auto-generate viaId.create(props.id!)) or a required foreign relation (validate withId.required)? - Closed-set fields: every status / kind / layout / provider / palette / discriminator must come from a string-backed TS enum in
libs/contracts/<bc>/src/interfaces/orlibs/shared— never inline string literals oras consttuples. - Invariants: which VO (
Slug,PaletteKey,ImageRefValue, …) validates each field? Are there arrays of IDs or nested entities (e.g.Section[]insideCelebration)? - State transitions: does behavior require a domain method? Use
cloneWithfor immutable swap-and-revalidate, or mutate_field+this.touch()for entities that own a mutable collection (seeCelebration). - Constructor visibility:
privatefor leaf entities;protectedonly when subclasses need access.
Core Rules
- Extend
Entity<Type, Props>from@acme/shared; keep constructorprivateorprotected. - Import enums and tagged unions from
@acme/<bc>-contracts(e.g.@acme/celebrations-contracts). Never redefine wire types in the entity file. - Expose dual API:
tryCreate(props): Result<T>(returns Result, canonical) andcreate(props): T(delegates totryCreate+throwIfFailed). - Validate every field via VOs / type guards + collect errors (either
Result.combine([...])or a manualerrors: string[]accumulator — both patterns exist in this codebase; useResult.combinewhen all checks returnResult<T>). - Always store normalized values. Spread
vo.instance.valuefor scalars; for sibling entities, prefer building fromSection.tryCreate(sp)and keeping the array asSection[]in a private field while keepingSectionProps[]inpropsfor serialization. - Getters expose domain values;
this.propsis never accessed from outside the entity class. cloneWith(overrides)deep-merges and re-runstryCreateautomatically — never calltryCreateby hand from a domain method whencloneWithsuffices.
Enum Rule (HARD)
Every closed set is a string-backed TS enum in the contracts package. Pattern:
export enum CelebrationStatusEnum {
DRAFT = "draft",
PUBLISHED = "published",
}
export const CELEBRATION_STATUSES = Object.values(CelebrationStatusEnum);
export type CelebrationStatus = (typeof CelebrationStatusEnum)[keyof typeof CelebrationStatusEnum];
export function isCelebrationStatus(v: unknown): v is CelebrationStatus {
return typeof v === "string" && CELEBRATION_STATUSES.includes(v as CelebrationStatus);
}
Inside the entity:
- Validate with the type guard (
isCelebrationStatus(props.status)→ push"INVALID_CELEBRATION_STATUS"on failure). - Store the value typed as the union (
CelebrationStatus), emit it from a getter. - Compare with the enum member:
this._status === CelebrationStatusEnum.PUBLISHED. Never=== "published". - Singleton / limit catalogs reference enum members:
const SINGLETON_KINDS = [SectionKindEnum.HERO, SectionKindEnum.GALLERY, SectionKindEnum.SIGNATURE] as const.
Base Class Behaviour You Must Know
Entity's protected constructor calls Id.create(props.id!, { attribute: "id" }) and stores the normalized id, plus initializes createdAt/updatedAt/deletedAt if absent. Therefore:
- Do not set
createdAt,updatedAt,deletedAtinsidetryCreate. Usethis.props.updatedAt = new Date()via a privatetouch()for mutations. cloneWithusesstructuredCloneonpropsbefore deep-merging, so nested objects are safe from caller mutation.
NEVER
- NEVER use a raw string literal where an enum member exists (
"published"→CelebrationStatusEnum.PUBLISHED). - NEVER store raw VO input in
props— always normalize viavo.instance.value. - NEVER add a public setter — mutate state through a named domain method.
- NEVER skip validating array elements — loop and either push into a manual
errors[]orResult.combineper-element results. - NEVER put a domain invariant in the use case if it must hold for the entity from any caller.
- NEVER expose
this.propsto callers outside the entity — use typed getters or a deliberatetoSnapshot()method. - NEVER import from
@ddd/shared(legacy alias). The shared lib is@acme/shared.
References
See references/entity-pattern.md for: real paths, canonical tryCreate snippet, enum-driven validation, array / nested-entity pattern, cloneWith vs mutable-collection mutation, test layout under apps/api/test/, and the pitfalls table.
See also examples/product.entity.ts and examples/product.entity.test.ts for a self-contained reference entity with an enum-driven status field.