@shirudo/ddd-kit 2.0.0 → 3.0.0-rc.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,26 +1,41 @@
1
1
  # @shirudo/ddd-kit
2
2
 
3
- Composable TypeScript toolkit for tactical Domain-Driven Design. Ships the canonical building blocks (Value Objects, Entities, Aggregate Roots, Domain Events, Repositories, and CQRS handlers) without a framework or runtime lock-in. ESM-only; runs on Node 18+, Cloudflare Workers, Vercel Edge, Deno, and Bun.
3
+ Tactical Domain-Driven Design building blocks for TypeScript.
4
4
 
5
- > **Stable: 1.0**
5
+ `@shirudo/ddd-kit` supplies the main parts for a domain model. These parts
6
+ include value objects, entities, aggregates, domain events, and repositories.
7
+ The package also supplies application handlers, outbox ports, projections, and
8
+ adapter contract tests.
9
+
10
+ It is not an application framework. You keep your HTTP layer, database, queue,
11
+ ORM, and runtime choices. The kit gives your domain model a strong center and
12
+ clear boundaries around persistence and side effects.
13
+
14
+ > **Release candidate: 3.0** (`3.0.0-rc`, npm dist-tag `next`); latest stable
15
+ > release is 2.2.
6
16
  >
7
- > The public API is stable and follows [Semantic Versioning](https://semver.org/). Breaking changes bump the major and ship with a migration path in the [CHANGELOG](https://github.com/shi-rudo/ddd-kit-ts/blob/main/CHANGELOG.md).
17
+ > The public API follows [Semantic Versioning](https://semver.org/). Breaking
18
+ > changes bump the major version and are documented with migration notes in the
19
+ > [CHANGELOG](./CHANGELOG.md).
8
20
 
9
21
  ![npm version](https://img.shields.io/npm/v/@shirudo/ddd-kit)
10
22
  ![license](https://img.shields.io/npm/l/@shirudo/ddd-kit)
11
23
 
12
- ## Features
24
+ ## When This Helps
25
+
26
+ Use this kit when your TypeScript code has domain rules that deserve more than
27
+ DTOs and service functions:
28
+
29
+ - an order can only be confirmed once
30
+ - a booking must stay inside an allowed date range
31
+ - money must never lose precision at a JSON boundary
32
+ - optimistic concurrency conflicts must be handled deliberately
33
+ - domain events must be persisted and dispatched reliably
34
+ - repository adapters must prove they enforce the same contract
13
35
 
14
- - **Value Objects:** deep-frozen, by-attribute equality (`vo`, `ValueObject`, `voEquals`).
15
- - **Entities:** identity + lifecycle, with collection helpers branded by `Id<Tag>`.
16
- - **Aggregate Roots:** state-stored (`AggregateRoot`) and event-sourced (`EventSourcedAggregate`), with optimistic-concurrency versioning.
17
- - **Domain Events:** typed, deeply frozen, carry metadata for traceability and schema evolution.
18
- - **Repositories:** technology-agnostic persistence ports with an Identity-Map contract and OCC.
19
- - **CQRS:** zero-config in-memory `CommandBus` / `QueryBus`, plus `CommandHandler` / `QueryHandler` types for external brokers.
20
- - **Unit of Work:** opt-in `UnitOfWork` facade with tx-bound repositories, repository-side enrollment, a per-operation Identity Map, and aggregate-level dirty tracking (`changedKeys` / `hasChanges`) for partial writes. Honestly speaking: a transaction coordinator with registration and Identity Map; writes stay explicit by design (no auto-flush).
21
- - **Outbox:** `withCommit` harvests pending events inside the transaction, stamps them with the aggregate's commit version, and publishes them atomically.
22
- - **Repository contract tests:** `@shirudo/ddd-kit/testing` ships the suite every adapter must pass: OCC is a testable contract, not a documented pattern.
23
- - **Result-first boundary:** a typed error hierarchy on [`@shirudo/base-error`](https://www.npmjs.com/package/@shirudo/base-error) and `Result` from [`@shirudo/result`](https://www.npmjs.com/package/@shirudo/result); `voValidated` collects field violations and renders RFC 9457 via the opt-in `@shirudo/ddd-kit/http` entry.
36
+ The library is intentionally boring at the edges. It does not ship an ORM, a
37
+ message broker, decorators, a dependency-injection container, or a web
38
+ framework. Those choices belong to the application.
24
39
 
25
40
  ## Installation
26
41
 
@@ -28,59 +43,193 @@ Composable TypeScript toolkit for tactical Domain-Driven Design. Ships the canon
28
43
  pnpm add @shirudo/ddd-kit @shirudo/result @shirudo/base-error
29
44
  ```
30
45
 
31
- `@shirudo/result` and `@shirudo/base-error` are peer dependencies; install them once in the consuming app.
46
+ `@shirudo/result` and `@shirudo/base-error` are peer dependencies. Install them
47
+ once in the consuming app.
48
+
49
+ The package is ESM-only, requires TypeScript 5.9+, and supports Node 22+,
50
+ Cloudflare Workers, Vercel Edge, Deno, and Bun.
51
+
52
+ ## A Small Aggregate
53
+
54
+ ```ts
55
+ import {
56
+ AggregateRoot,
57
+ DomainError,
58
+ type DomainEvent,
59
+ type Id,
60
+ } from "@shirudo/ddd-kit";
32
61
 
33
- ## Quick start
62
+ type OrderId = Id<"OrderId">;
34
63
 
35
- ```typescript
36
- import { vo, type VO } from "@shirudo/ddd-kit";
64
+ type OrderState = {
65
+ status: "draft" | "confirmed";
66
+ };
37
67
 
38
- type EmailAddress = VO<{ value: string }>;
68
+ type OrderConfirmed = DomainEvent<
69
+ "OrderConfirmed",
70
+ { orderId: OrderId }
71
+ >;
39
72
 
40
- function createEmail(value: string): EmailAddress {
41
- if (!value.includes("@")) throw new Error("Invalid email address");
42
- return vo({ value }); // deeply frozen, immutable
73
+ type OrderEvent = OrderConfirmed;
74
+
75
+ class OrderAlreadyConfirmedError extends DomainError<
76
+ "ORDER_ALREADY_CONFIRMED"
77
+ > {
78
+ constructor(orderId: OrderId) {
79
+ super({
80
+ code: "ORDER_ALREADY_CONFIRMED",
81
+ message: `Order ${orderId} is already confirmed.`,
82
+ });
83
+ }
43
84
  }
44
85
 
45
- const email = createEmail("user@example.com");
46
- ```
86
+ class Order extends AggregateRoot<OrderState, OrderId, OrderEvent> {
87
+ protected readonly aggregateType = "Order";
47
88
 
48
- For a complete walkthrough (a minimal `Order` aggregate with typed events, `commit()`, and the App-Service boundary), see [Getting Started](https://github.com/shi-rudo/ddd-kit-ts/blob/main/docs/guide/getting-started.md).
89
+ private constructor(id: OrderId, state: OrderState) {
90
+ super(id, state);
91
+ }
49
92
 
50
- ## Core concepts
93
+ static draft(id: OrderId): Order {
94
+ return new Order(id, { status: "draft" });
95
+ }
51
96
 
52
- Each building block has a dedicated guide. Start with [Design Decisions](https://github.com/shi-rudo/ddd-kit-ts/blob/main/docs/guide/design-decisions.md) for the non-obvious calls (Result at the App boundary, no Specification pattern, the TransactionScope/Unit-of-Work layering, class-based aggregates).
97
+ get status(): OrderState["status"] {
98
+ return this.state.status;
99
+ }
53
100
 
54
- | Concept | Guide |
55
- |---|---|
56
- | Value Objects (`vo`, `ValueObject`, `voWithValidation`, `voValidated`) | [Value Objects](https://github.com/shi-rudo/ddd-kit-ts/blob/main/docs/guide/value-objects.md) |
57
- | Entities and identity | [Entities](https://github.com/shi-rudo/ddd-kit-ts/blob/main/docs/guide/entities.md) |
58
- | Aggregate Roots, factories, reconstitution | [Aggregate Roots](https://github.com/shi-rudo/ddd-kit-ts/blob/main/docs/guide/aggregates.md) |
59
- | Event sourcing (`apply`, replay, snapshots) | [Event Sourcing](https://github.com/shi-rudo/ddd-kit-ts/blob/main/docs/guide/event-sourcing.md) |
60
- | Domain Events (`createDomainEvent`, metadata) | [Domain Events](https://github.com/shi-rudo/ddd-kit-ts/blob/main/docs/guide/domain-events.md) |
61
- | Errors: throw vs Result, `ValidationError`, RFC 9457 | [Result vs Throw](https://github.com/shi-rudo/ddd-kit-ts/blob/main/docs/guide/result-vs-throw.md) |
62
- | Commands, queries, buses | [CQRS & Buses](https://github.com/shi-rudo/ddd-kit-ts/blob/main/docs/guide/cqrs-and-buses.md) |
63
- | Repositories, Identity Map, OCC | [Repository](https://github.com/shi-rudo/ddd-kit-ts/blob/main/docs/guide/repository.md) |
64
- | Unit of Work, enrollment, contract test suite | [Unit of Work](https://github.com/shi-rudo/ddd-kit-ts/blob/main/docs/guide/unit-of-work.md) |
65
- | Outbox, `withCommit`, transactions | [Outbox & Transactions](https://github.com/shi-rudo/ddd-kit-ts/blob/main/docs/guide/outbox.md) |
66
- | Read-side projections | [Projections](https://github.com/shi-rudo/ddd-kit-ts/blob/main/docs/guide/projections.md) |
67
- | Concurrency & operation-scoped aggregates | [Concurrency](https://github.com/shi-rudo/ddd-kit-ts/blob/main/docs/guide/concurrency.md) |
68
- | Edge runtimes (Workers, Deno, Bun) | [Edge Runtimes](https://github.com/shi-rudo/ddd-kit-ts/blob/main/docs/guide/edge-runtimes.md) |
101
+ confirm(): void {
102
+ if (this.state.status === "confirmed") {
103
+ throw new OrderAlreadyConfirmedError(this.id);
104
+ }
69
105
 
70
- ## Documentation
106
+ this.commit(
107
+ { status: "confirmed" },
108
+ this.createEvent("OrderConfirmed", { orderId: this.id }),
109
+ );
110
+ }
111
+ }
71
112
 
72
- - **[LLM.md](https://github.com/shi-rudo/ddd-kit-ts/blob/main/LLM.md):** hand-curated, high-signal guide for LLM coding tools and a fast human skim of the whole surface.
73
- - **[Common Mistakes](https://github.com/shi-rudo/ddd-kit-ts/blob/main/docs/guide/common-mistakes.md):** the footgun catalogue; read it before writing consumer code.
74
- - **API reference:** full type definitions ship with the package (`node_modules/@shirudo/ddd-kit/dist/index.d.ts`); the `@shirudo/ddd-kit/http` subpath exports the RFC 9457 presenter.
75
- - **[CHANGELOG](https://github.com/shi-rudo/ddd-kit-ts/blob/main/CHANGELOG.md):** release history with a migration path for every breaking change.
113
+ const order = Order.draft("order-1" as OrderId);
76
114
 
77
- ## TypeScript support
115
+ order.confirm();
78
116
 
79
- Requires TypeScript 5.9+. The kit leans on branded, conditional, and mapped types for a type-safe DDD experience; all APIs are fully typed.
117
+ order.status; // "confirmed"
118
+ order.version; // 1
119
+ order.pendingEvents[0]?.type; // "OrderConfirmed"
120
+ ```
121
+
122
+ That example is deliberately small, but it shows the core shape:
123
+
124
+ - The aggregate owns the rule.
125
+ - The domain throws an error when an invariant is broken.
126
+ - `commit(...)` changes the state and records the event together.
127
+ - `createEvent(...)` captures the immutable domain decision and aggregate source.
128
+ - The application shell adds event identity, recording time, and trace metadata.
129
+ - Persistence stays outside the aggregate.
130
+
131
+ In production, a repository and `withCommit` or `UnitOfWork` persist the state,
132
+ write the events to an outbox inside the same transaction, and mark the
133
+ aggregate as persisted after the transaction commits.
134
+
135
+ ## What You Get
136
+
137
+ **Domain modeling**
138
+
139
+ - value objects via `vo()` and `ValueObject<T>`
140
+ - exact Money helpers in `@shirudo/ddd-kit/money`
141
+ - child entities with branded identity
142
+ - state-stored and event-sourced aggregate roots
143
+ - domain events with metadata, schema version, and commit stamps
144
+ - a domain state machine for named lifecycle states
145
+
146
+ **Application boundaries**
147
+
148
+ - `CommandHandler` and `QueryHandler` types
149
+ - in-process `CommandBus` and `QueryBus` for modular apps, tests, and edge
150
+ runtimes
151
+ - a clear error split: domain code throws, command/query boundaries return
152
+ `Result`
153
+ - `voValidated` for collecting field-level validation issues
154
+ - optional HTTP/RFC 9457 presentation helpers
155
+
156
+ **Persistence and delivery**
157
+
158
+ - repository interfaces for id-based and filtered access
159
+ - a per-operation Identity Map contract
160
+ - optimistic concurrency errors and duplicate-insert errors
161
+ - `withCommit` for transaction, outbox, event harvest, and post-commit cleanup
162
+ - `UnitOfWork` for repository registration and enrollment
163
+ - outbox dispatcher, projection, event-store, and snapshot ports
164
+ - contract tests for repository and outbox adapters
165
+
166
+ ## What It Does Not Do
167
+
168
+ The kit does not decide your architecture for you. It gives you hard boundaries
169
+ where the domain model needs them and stays out of the rest.
170
+
171
+ - No ORM adapter is bundled.
172
+ - No queue or broker is required.
173
+ - No global application container is introduced.
174
+ - No query DSL or expression trees: `Specification` evaluates in memory and is translated explicitly by adapters, never reverse-engineered into SQL.
175
+ - No money rounding, allocation, or FX policy is hidden in the library.
176
+ - No cross-process command bus is pretended to be in-process code.
177
+
178
+ Those are application decisions. The guides show the recommended seams.
179
+
180
+ ## Guide Map
181
+
182
+ Start with [Getting Started](./docs/guide/getting-started.md) if you want the
183
+ short walkthrough. Read [Design Decisions](./docs/guide/design-decisions.md) if
184
+ you want to understand why the kit is shaped this way. Keep
185
+ [Common Mistakes](./docs/guide/common-mistakes.md) nearby when writing your
186
+ first adapter or aggregate.
187
+
188
+ | Topic | Guide |
189
+ | --- | --- |
190
+ | Value objects and validation helpers | [Value Objects](./docs/guide/value-objects.md) |
191
+ | Exact money values | [Money](./docs/guide/money.md) |
192
+ | Child entities and identity | [Entities](./docs/guide/entities.md) |
193
+ | State-stored aggregates | [Aggregate Roots](./docs/guide/aggregates.md) |
194
+ | Event-sourced aggregates and snapshots | [Event Sourcing](./docs/guide/event-sourcing.md) |
195
+ | Domain event shape and factories | [Domain Events](./docs/guide/domain-events.md) |
196
+ | Named lifecycle states | [Domain State Machine](./docs/guide/domain-state-machine.md) |
197
+ | Throwing in the domain, returning `Result` at the boundary | [Result vs Throw](./docs/guide/result-vs-throw.md) |
198
+ | Commands, queries, and in-process buses | [CQRS & Buses](./docs/guide/cqrs-and-buses.md) |
199
+ | Repository contracts and Identity Map | [Repository](./docs/guide/repository.md) |
200
+ | Transaction-scoped repositories | [Unit of Work](./docs/guide/unit-of-work.md) |
201
+ | Duplicate-safe commands and inbox handling | [Command Idempotency](./docs/guide/idempotency.md) |
202
+ | Reliable event harvest and delivery | [Outbox & Transactions](./docs/guide/outbox.md) |
203
+ | Read models and projectors | [Projections](./docs/guide/projections.md) |
204
+ | Event schema changes | [Event Upcasting](./docs/guide/event-upcasting.md) |
205
+ | Optimistic concurrency | [Concurrency](./docs/guide/concurrency.md) |
206
+ | Workers, Deno, Bun, and other edge runtimes | [Edge Runtimes](./docs/guide/edge-runtimes.md) |
207
+
208
+ The generated API reference lives in [docs/api](./docs/api/).
209
+
210
+ ## Examples
211
+
212
+ - [examples/order](./examples/order): a minimal state-stored aggregate
213
+ - [examples/order-with-entity-items](./examples/order-with-entity-items): an
214
+ aggregate with child entities
215
+ - [examples/rugby](./examples/rugby): an event-sourced aggregate
216
+ - [examples/saga](./examples/saga): state-stored and event-sourced process
217
+ manager / saga variants
80
218
 
81
219
  ## Contributing
82
220
 
83
- Contributions are welcome. For bugs and feature requests, use the [issue tracker](https://github.com/shi-rudo/ddd-kit-ts/issues); open a pull request against `main`.
221
+ `pnpm typecheck` runs the native TypeScript 7 compiler. The `typescript`
222
+ development dependency intentionally aliases the official TypeScript 6
223
+ compatibility package because TypeDoc and Vite+ Pack's declaration bundler still
224
+ consume the compiler API, which TypeScript 7.0 does not expose. Keep the two
225
+ packages side by side until those API-based tools support TypeScript 7.
226
+
227
+ Tests and package builds run through Vite+ (`pnpm test`, `pnpm build`). Biome
228
+ remains the repository's lint and format policy.
229
+
230
+ Bug reports, questions, and pull requests are welcome on
231
+ [GitHub](https://github.com/shi-rudo/ddd-kit-ts). Please open pull requests
232
+ against `main`.
84
233
 
85
234
  ## License
86
235
 
@@ -88,4 +237,7 @@ MIT.
88
237
 
89
238
  ## Author
90
239
 
91
- **Shirudo:** [@shi-rudo](https://github.com/shi-rudo) · [npm](https://www.npmjs.com/package/@shirudo/ddd-kit) · [repo](https://github.com/shi-rudo/ddd-kit-ts)
240
+ **Shirudo**:
241
+ [@shi-rudo](https://github.com/shi-rudo) |
242
+ [npm](https://www.npmjs.com/package/@shirudo/ddd-kit) |
243
+ [repository](https://github.com/shi-rudo/ddd-kit-ts)