ts-ddd-repository
Create, review, or guide repository contracts and implementations in a TypeScript + DDD codebase.
Skill by llodev
Install
Works with Claude Code, Claude apps, Codex, Cursor, Gemini CLI, GitHub Copilot, VS Code, OpenCode, Goose, Windsurf. Read the repository before you give any tool access to your data.
npx agentmarket install skill/llodev-ts-ddd-repository
Asks which of your tools to add it to (Claude Code, Codex, Cursor, VS Code, Windsurf, Gemini CLI, Claude Desktop) and shows the change first. See https://agentmart.dev/cli.
Get the weekly diff
The weekly diff: one email a week with new agent tools, notable updates and grade changes, plus security alerts for tools you follow. Double opt-in, one-click unsubscribe.
About
Use cases: documents, backend, testing
Reviews
Write a reviewNo reviews yet. Installed it? Reviews come from verified installers only. How reviews work
Permissions
What this skill can do and what it asks for, from its SKILL.md. Checked Oct 25, 2026.
- Tools
- No tools found
- Secrets
- None asked for
- Install scripts
- None found
- Runs as
- Node.js package
Network hosts it talks to 1
codeload.github.com· found in install command
Files it touches
~/.claude/skills/ts-ddd-repository
What's inside
- Skill name
ts-ddd-repository- Description
- Create, review, or guide repository contracts and implementations in a TypeScript + DDD codebase. Use when the request involves `*.repository.ts` files under `apps/api/src/<bc>/domain/repositories/` or `apps/api/src/<bc>/infra/{firestore,memory}/`, persistence operations (save / findBy* / list*), the `<NAME>_REPOSITORY` DI token, adapting the Firebase Admin SDK to a domain port, `toFirestore`/`fromFirestore` mappers, the side-by-side Firestore + InMemory adapter pair, contract tests under `apps/api/test/<bc>/infra/{firestore,memory}/`, `Result` error handling, or CQRS Repository vs Query separation.
- License
- MIT
- Metadata
- version: 0.1.0
- Instructions
- 104 lines in SKILL.md · view on GitHub
Before installing
- Ships executable scripts
Files (11)
- CHANGELOG.md
- LICENSE
- README.md
- SKILL.md
- README.es-ES.md · docs/i18n/
- README.pt-BR.md · docs/i18n/
- in-memory-product.repository.ts · examples/
- product.repository.ts · examples/
- package.json
- firestore-adapter.md · references/
- repository-pattern.md · references/
SKILL.md (first lines)
# TypeScript DDD Repository
**MANDATORY — READ ENTIRE FILE**: Before any implementation step, read
[`references/repository-pattern.md`](references/repository-pattern.md) completely,
then [`references/firestore-adapter.md`](references/firestore-adapter.md) completely.
The Firestore Admin SDK is the only production persistence stack in this repo
(no Prisma, no MongoDB, no Supabase).
**Do NOT load** other DDD skills (`ts-ddd-use-case`, `ts-ddd-entity`,
`ts-ddd-dto`, `ts-query-cqrs`) unless explicitly requested.
> **Before You Start** — ask: which aggregate owns this write? Persistence boundaries must match aggregate boundaries; if a single `save()` touches data from two aggregates, you've conflated them.
---
## Where the files live
Each bounded context owns a hexagonal slice:
```
apps/api/src/<bc>/
domain/
repositories/
<name>.repository.ts ← port (interface) + DI token symbol
index.ts ← barrel
infra/
firestore/
firestore-<name>.repository.ts ← Firestore adapter (Injectable)
<name>.mapper.ts ← to/from Firestore helpers
index.ts
memory/
in-memory-<name>.repository.ts ← InMemory adapter (no decorators)
index.ts
<bc>.module.ts ← wires { provide: <NAME>_REPOSITORY, useClass: Firestore<Name>Repository }
apps/api/test/<bc>/infra/
firestore/firestore-<name>.repository.test.ts ← fake-DB at the SDK boundary
memory/in-memory-<name>.repository.test.ts ← straight unit test
```
Path aliases used everywhere:
- `@acme/shared` → `Result`, `Entity`, base building blocks.
- `@acme/<bc>-contracts` → wire types, status/kind enums (e.g. `CelebrationStatusEnum`).
- `@<bc>/domain/...`, `@<bc>/infra/...` → cross-layer imports inside the BC.
- `@shared/firebase` → `FirestoreService`.
- Relative paths only within the same folder.
---
## Before You Start
Answer first:
- **Which operations?** Define the smallest port that satisfies the use cases. Prefer intent-named methods (`save`, `findBySlug`, `listByStatus`, `saveSection`, `deleteSection`) over a generic `CrudRepository`.
- **Aggregate boundary?** A single port should own writes to the whole aggregate (e.g. `CelebrationRepository` owns `celebrations` doc + its `sections` subcollection). Do not split aggregates across multiple ports.
- **Repository or Query?** Loading an entity to enforce invariants → Repository. Returning a read DTO for the API/front → Query (separate interface, separate skill: `ts-query-cqrs`).
- **DI token name?** Add an exported `Symbol("<NAME>_REPOSITORY")` next to the interface — Nest injects by token, not class.
- **InMemory adapter?** Always ship one. Use-case tests substitute it via the same DI token; no need for `jest.fn()` stubs.
---
## Repository vs Query (CQRS)
| Need | Use | Returns |
| ------------------------------------------------ | ---------------------------- | --------------------------------- |
| Load entity to preserve invariants before update | `Repository.findBy*` | Domain entity (or `null`) |
| Existence check before a write | `Repository.findBy*` | Entity or `null` or `Result.fail` |
| Read projection for an API/front response | `Query` (separate interface) | Read DTO |
| Custom domain-oriented lookup (`findBySlug`) | `Repository` method | Domain entity |
| Paginated list for the UI | `Query` | `PaginatedResultDTO<XxxListItem>` |
**Rule**: if the caller needs the entity to run domain logic → Repository. If the caller only needs data to display → Query. Both can share an adapter class but the **TypeScript interfaces must be separate**.
---
## Core Rules
- The port lives in `<bc>/domain/repositories/` — no `firebase-admin`, no `@nestjs/*`, no Zod, no DTOs.
- Export a DI token symbol next to the port: `export const CELEBRATION_REPOSITORY = Symbol("CELEBRATION_REPOSITORY");`
- Every method returns `Promise<Result<T>>`. Adapters never throw in the normal flow — wrap I/O in `try/catch` and return `Result.fail("SHORT_SCREAMING_SNAKE_CODE")`.
- Lookup methods that legitimately mean "absent" return `Result<T | null>` (Firestore + InMemory both used `findBySlug(...): Promise<Result<Celebration | null>>`). Mutation methods that require an existing aggregate return `Result.fail("<AGG>_NOT_FOUND")`.
- Mapping lives in dedicated `<name>.mapper.ts` files (`toFirestore` / `fromFirestore` returning `Result<Entity>`) — never inline transformation inside an operation method.
- `save` accepts a fully constructed entity (already validated by `Entity.tryCreate` / `cloneWith` in the use case). The adapter does **not** patch partial fields.
- Aggregate writes that touch a subcollection (e.g. wholesale-replace sections) document their atomicity guarantees as a code comment — if the write is non-transactional, say so.
## NEVER
- **NEVER** import `firebase-admin`, `@nestjs/*`, Zod, or BC contracts that carry HTTP/UI shapes into a port file.
- **NEVER** add a read-projection / DTO-returning method to a Repository — that belongs in a `Query` interface (see `ts-query-cqrs`).
- **NEVER** inline mapping in `findBy*` / `save` — extract `toFirestore` / `fromFirestore`.
- **NEVER** accept partial fields in `save()`. The use case does `cloneWith` first; the adapter receives a complete, validated aggregate.
- **NEVER** wire an adapter directly via `useClass` _without_ the token symbol — Nest cannot inject an interface, only a token.
- **NEVER** hit real Firestore from a unit test. Pass a fake at the SDK boundary (see firestore-adapter reference).
- **NEVER** share entity references across repository round-trips. Always `toSnapshot()` on save and `Celebration.tryCreate(structuredClone(snap))` on read — otherwise two callers can mutate the same in-memory aggregate and corrupt invariants between use cases.
- **NEVER** call `snap.data()` without first checking `snap.exists`. It returns `undefined` and the next field access crashes downstream — guard with `if (!snap.exists) return Result.ok<T | null>(null);` first.
- **NEVER** let `firebase-admin/firestore.Timestamp` leak into the domain entity. Convert at the boundary (`Timestamp.fromDate()` on write, `.toDate()` on read) inside the mapper. The domain holds plain `Date`, never `Timestamp` — otherwise the domain layer transitively depends on `firebase-admin`.
## References
- [`references/repository-pattern.md`](references/repository-pattern.md) — port shape, DI token, InMemory adapter, dual-adapter test strategy, enums-in-fixtures rule, checklist.
- [`references/firestore-adapter.md`](references/firestore-adapter.md) — Firebase Admin SDK basics (`collection().doc().get()`, subcollections, `Timestamp` conversions, fake-DB test harness, mapper helpers, aggregate-write caveats).From llodev/skills, MIT.
More from llodev/skills
- 0.563score
ts-ddd-dto
llodev
Create, review, or guide Data Transfer Objects in a TypeScript + DDD monorepo.
SkillBFreshness: B★ 2Claude Code +9 - 0.563score
ts-ddd-domain-service
llodev
Create, review, or guide pure Domain Service implementation in a TypeScript + DDD monorepo.
SkillBFreshness: B★ 2Claude Code +9 - 0.563score
ts-ddd-use-case
llodev
Create, review, or guide use case (application service) implementation in a TypeScript + DDD codebase.
SkillBFreshness: B★ 2Claude Code +9 - 0.563score
ts-ddd-controller
llodev
Create, review, or guide HTTP controllers in a TypeScript + DDD api.
SkillBFreshness: B★ 2Claude Code +9 - 0.563score
ts-ddd-entity
llodev
Create, review, or guide Domain Entity implementation in a TypeScript + DDD codebase.
SkillBFreshness: B★ 2Claude Code +9 - 0.563score
ts-ddd-value-object
llodev
Create, review, or guide Value Object implementation in a TypeScript + DDD codebase.
SkillBFreshness: B★ 2Claude Code +9 - 0.563score
ts-query-cqrs
llodev
Create, review, or guide read-side queries in a TypeScript + DDD CQRS codebase.
SkillBFreshness: B★ 2Claude Code +9 - 0.558score
django-schema-design
llodev
Design robust Django database schemas with model best practices, migration patterns, and primary key strategy (incremental, UUID, UUIDv7).
SkillBFreshness: B★ 2Claude Code +9 - 0.558score
pm-tasks-asana
llodev
Asana adapter for the @llodev/pm-tasks-* family.
SkillBFreshness: B★ 2Claude Code +9 - 0.558score
pm-tasks-core
llodev
Core extraction + vocabulary for the @llodev/pm-tasks-* family.
SkillBFreshness: B★ 2Claude Code +9 - 0.558score
pm-tasks-trello
llodev
Trello adapter for the @llodev/pm-tasks-* family.
SkillBFreshness: B★ 2Claude Code +9 - 0.554score
pm-tasks-linear
llodev
Linear adapter for the @llodev/pm-tasks-* family.
SkillBFreshness: B★ 2Claude Code +9
Signals
- Stars
- 2
- Forks
- 0
- Open issues
- 0
- Last commit (this folder)
- Jul 17, 2026 (3 months ago)
- Last release
- Aug 21, 2026 (2 months ago)
From GitHub (fetched Oct 24, 2026).
Details
- Version
- 0.1.0
- License
- MIT
- Repository
- github.com/llodev/skills
- Website
- www.npmjs.com/org/llodev
- First seen
- Oct 24, 2026
- Last checked
- Oct 24, 2026
- Source
- GitHubgithub-graphql
More from llodev
- 0.563score
ts-ddd-value-object
llodev
Create, review, or guide Value Object implementation in a TypeScript + DDD codebase.
SkillBFreshness: B★ 2Claude Code +9 - 0.563score
ts-ddd-controller
llodev
Create, review, or guide HTTP controllers in a TypeScript + DDD api.
SkillBFreshness: B★ 2Claude Code +9 - 0.563score
ts-ddd-domain-service
llodev
Create, review, or guide pure Domain Service implementation in a TypeScript + DDD monorepo.
SkillBFreshness: B★ 2Claude Code +9 - 0.563score
ts-ddd-dto
llodev
Create, review, or guide Data Transfer Objects in a TypeScript + DDD monorepo.
SkillBFreshness: B★ 2Claude Code +9 - 0.563score
ts-ddd-entity
llodev
Create, review, or guide Domain Entity implementation in a TypeScript + DDD codebase.
SkillBFreshness: B★ 2Claude Code +9 - 0.563score
ts-ddd-use-case
llodev
Create, review, or guide use case (application service) implementation in a TypeScript + DDD codebase.
SkillBFreshness: B★ 2Claude Code +9
Cite this page
ts-ddd-repository skill. Agent Mart. https://agentmart.dev/skills/llodev-ts-ddd-repository (last updated 2026-10-24)