agentsclimarketplace

Ts ddd domain service

Skill llodev/skills/skills/ts-ddd-domain-service

Create, review, or guide pure Domain Service implementation in a TypeScript + DDD monorepo. Use when the request involves `*.service.ts` files inside `apps/api/src/<bc>/domain/services/`, domain policies, pure calculations/rules across multiple entities/VOs, naming patterns (`*Policy`, `*Calculator`, `*Resolver`, `*Specification`), branching on closed-set enums (status / kind / role) from `@acme/celebrations-contracts`, or tests in `apps/api/test/<bc>/domain/services/`. Scope is the *pure* domain layer; for orchestrators that need a repository or other I/O (e.g. `SlugAllocator`), see the sibling `application/services/` section at the end.From its SKILL.md

Install
npx -y skills add llodev/skills --skill ts-ddd-domain-service

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

2 things 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.
  • runs commandsInstructs the agent to run 2 commands, including `pnpm --filter api typecheck && pnpm --filter api test` and 1 more.

SKILL.md

8.0 KB, ~1.7k tokens by cl100k_base, as published. Nobody here has run it

TypeScript DDD Domain Service

MANDATORY — READ ENTIRE FILE: Before any implementation step, read references/domain-service-pattern.md completely. Do NOT load other DDD skills (entity, use-case, repository) unless explicitly requested.

This skill covers pure domain services that live in apps/api/src/<bc>/domain/services/. They never see a repository, NestJS, Firestore, HTTP, Zod, or any DTO. They take entities/VOs/primitives as arguments and return a Result or a plain domain value.

If the rule needs I/O (load by slug, retry against the database, call an external API), it is an application service, not a domain service — jump straight to the "When you need I/O" section below.


Before You Start

Before writing a single method, answer:

  • Ownership: does the rule use only one entity's own data? → put it as an entity method + cloneWith (see Celebration.publish), not a service.
  • Cross-aggregate?: does it combine 2+ entities/VOs without a clear owner? → Domain Service.
  • I/O needed?: does the rule require a repository, query, Firestore, or external call? → Application Service in application/services/ (DI + @Injectable), not Domain Service.
  • Already exists?: is the rule already captured in a VO (Slug, PaletteKey) or entity method (addSection, publish)? → do not duplicate.
  • Closed-set branch?: if you compare a status/kind/role against a literal string, stop — use the enum from @acme/celebrations-contracts (see "Branching on closed sets" in the reference).

Domain Service vs Use Case vs Application Service

For the Entity Method criteria (single-aggregate invariants — when the rule belongs inside *.entity.ts rather than in any service), see the ts-ddd-entity skill references. This skill owns the boundary between the three service-shaped homes below.

SituationWhere to put itWhy
Rule combines 2+ entities/VOs with no clear owner, no I/ODomain Service (domain/services/)No single entity owns the rule; pure → no DI needed
Rule is pure but only used in one flowInline in Use CaseYAGNI — extract to service only when reused
Rule needs I/O (repository, query, Firestore, HTTP)Use Case or Application ServiceDomain layer must stay pure
Multi-step I/O flow that doesn't fit UseCase<IN, OUT>Application Service (application/services/)SlugAllocator-style: needs DI, retries, repo access
Rule is a boolean policy over a collectionDomain Service (*Policy)Stateless check over multiple objects

Core Rules

  • File lives in apps/api/src/<bc>/domain/services/<name>.service.ts — never under application/, infra/, presentation/, or libs/contracts/**.
  • Each leaf folder has an index.ts barrel — re-export every new service from apps/api/src/<bc>/domain/services/index.ts.
  • Pure and deterministic: same input always produces same output, no Date.now()/Math.random() inside the rule (inject if needed).
  • Imports allowed: @acme/shared (Result, Entity, VOs), @acme/<bc>-contracts (enums + interfaces), other entities/VOs in the same BC. Nothing else.
  • No @nestjs/*, @Injectable, constructor injection, firebase-admin, Zod, DTOs, or framework decorators in this file.
  • Receives domain objects (entities, VOs, primitives) — returns Result<T> or a plain domain value.
  • Name by rule intent: *Policy, *Calculator, *Resolver, *Specification.
  • Default to a class with static methods for stateless calculations; instance methods only when the service genuinely holds configuration that varies per call site.
  • Compare closed-set fields against enum members (CelebrationStatusEnum.PUBLISHED), never raw strings. Iterate catalogs via Object.values(XxxEnum).
  • On failure use Result.fail("DOMAIN_ERROR_CODE") (UPPER_SNAKE). Aggregate multi-error validation with Result.combine([...]) or by pushing into an errors[] array — same pattern as Celebration.tryCreate.
  • Bubble up failures from inner VOs/entities via result.withFail instead of re-wrapping.

NEVER

  • NEVER inject a repository, query, or Firestore client into a Domain Service — I/O belongs in a Use Case or Application Service.
  • NEVER import @nestjs/*, firebase-admin, next, react, or any framework here — that contamination is what the layer exists to prevent.
  • NEVER duplicate logic already in an entity method or VO — if Celebration exposes publish(), the service calls it instead of re-implementing the transition.
  • NEVER put orchestration decisions (which repository to call next, which event to emit, retry loops) in a Domain Service — that is application concern.
  • NEVER return null/undefined/throw to signal failure — return Result.fail("CODE") so callers handle errors uniformly.
  • NEVER branch on a raw string literal for status/kind/role/palette — import the enum from @acme/celebrations-contracts (or the relevant BC contracts).
  • NEVER call the function execute here — execute is reserved for UseCase<IN, OUT>. Name the method after the rule (check, calculate, resolve, isSatisfiedBy).
  • NEVER call Date.now(), Math.random(), crypto.randomUUID(), performance.now(), new Date() (without an argument), or any other non-deterministic primitive inside a domain service — pure means deterministic given inputs. Pass a clock/random/id-generator as a parameter (or generate the value in the use case before calling the service). Why: non-determinism makes tests flaky, breaks property-based testing, and hides the real input set the rule depends on — the service silently couples to wall-clock time instead of taking it as data.

When you need I/O — use an Application Service

If your "service" needs a repository, retries against the database, or external calls, it is not a Domain Service. Put it in apps/api/src/<bc>/application/services/<name>.service.ts, mark it @Injectable(), inject ports via DI tokens, and still return Result. Canonical reference in this repo: apps/api/src/celebrations/application/services/slug-allocator.service.ts (allocates a unique Slug by retrying against CelebrationRepository). See the final section of references/domain-service-pattern.md for the contrast.

Verification

After writing or editing a service, run:

pnpm --filter api typecheck && pnpm --filter api test

(or make check-api). Test files live in apps/api/test/<bc>/domain/services/<name>.test.ts.

References

See references/domain-service-pattern.md for: file paths, canonical snippets (*Policy and *Calculator), the closed-set enum rule, test strategy, the implementation checklist, and the application-service contrast.

What ships with it: 10 files

39.9 KB alongside SKILL.md, 3 of them executable

references/

Gives 0 of the 12 instructions most test skills give in ~1.7k tokens

Counted across 1,201 of the 2,096 authors here whose files we hold, read 2026-09-06

  • Write a failing test before writing codein 43 of 1201, across 36 files
  • Run the full test suitein 36 of 1201, across 35 files
  • Test only one variable per experimentin 34 of 1201, across 17 files
  • Read product marketing context before asking questionsin 34 of 1201, across 14 files
  • Mock external dependenciesin 34 of 1201, across 30 files
  • Define primary, secondary, and guardrail metricsin 33 of 1201, across 16 files
  • Pre-determine sample size before startingin 31 of 1201, across 14 files
  • Test behavior rather than implementationin 31 of 1201, across 29 files
  • Formulate a hypothesis before designing a testin 30 of 1201, across 13 files
  • Document every test hypothesis, variant, and resultin 29 of 1201, across 11 files
  • Use descriptive test function namesin 25 of 1201, across 21 files
  • Commit to the methodology without stopping earlyin 24 of 1201, across 8 files

Said here and by no other author read

  • Read the domain service pattern reference before implementation
  • Place files in the domain services directory
  • Re-export new services from the index file
  • Ensure methods are pure and deterministic
  • Use Result objects for all return values
  • Name services by their rule intent

Grouped from the skills themselves: near-identical wordings counted once, and counted by distinct author, so one author publishing three of these counts once. Length counted with cl100k_base; the agent that loads this file may tokenize it differently.

Keep looking

Skills are one crate of 325,949. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.