@shirudo/ddd-kit 2.2.0 → 3.0.0-rc.4

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