@llodev/ts-ddd-repository
v0.1.0
Published
Repository ports + adapters — Firestore/InMemory pair, toFirestore/fromFirestore, DI token, contract tests.
Maintainers
Readme
@llodev/ts-ddd-repository
Repository ports + adapters — Firestore/InMemory pair,
toFirestore/fromFirestore, DI token, and contract tests.
Part of the @llodev/ts-ddd family.
What you get:
- A Repository vs Query decision table — load-to-mutate goes through a Repository returning a domain entity; read-for-display goes through a separate Query interface returning a DTO. Never mixed on the same interface.
- The side-by-side adapter pattern — a Firestore adapter and an InMemory adapter behind the same port + DI token symbol, so use-case tests substitute the in-memory one without
jest.fn()stubs. - Mapping discipline —
toFirestore/fromFirestorelive in dedicated mapper files, never inlined in an operation method;firebase-admin/firestore.Timestampnever leaks past the mapper into the domain (Dateonly). - Aggregate-boundary rules — a single port owns writes to the whole aggregate;
save()accepts a fully constructed, already-validated entity and never patches partial fields. - A
NEVERlist covering real Firestore footguns: readingsnap.data()without checkingsnap.exists, sharing entity references across round-trips instead oftoSnapshot()/tryCreate(structuredClone(...)), and wiring an adapter viauseClasswithout the token symbol.
Install
# npm (with skillpm or Claude Code marketplace)
npm i @llodev/ts-ddd-repository
# Vercel CLI
npx skills add llodev/skills/skills/ts-ddd-repositoryNo MCP, no config, no init — it's a pure knowledge skill. Once installed it activates on prompts like the ones below.
Use
| Prompt example | What the agent does |
| --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ |
| "add a ProductRepository port with findBySlug and save" | Emits the port interface, DI token symbol, and the InMemory + Firestore adapter pair |
| "why does this list method leak Firestore Timestamp?" | Points to the mapper boundary rule — convert Timestamp ↔ Date inside toFirestore/fromFirestore |
| "should this be a Repository or a Query?" | Applies the Repository vs Query (CQRS) decision table |
| "review this adapter for aggregate-boundary violations" | Checks that save() touches only one aggregate and receives a fully validated entity |
Contents
| File | Content |
| ------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------- |
| SKILL.md | Trigger conditions, file layout, Repository vs Query table, core rules, and NEVER list. |
| references/repository-pattern.md | Port shape, DI token, InMemory adapter, dual-adapter test strategy, enums-in-fixtures rule, checklist. |
| references/firestore-adapter.md | Firebase Admin SDK basics, subcollections, Timestamp conversions, fake-DB test harness, mapper helpers, aggregate-write caveats. |
| examples/product.repository.ts | A repository port interface with intent-named methods and a DI token symbol. |
| examples/in-memory-product.repository.ts | The InMemory adapter counterpart, used directly in use-case tests via the same DI token. |
License
MIT — see LICENSE.
