@shirudo/ddd-kit 2.2.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 +204 -55
- package/dist/chunks/deep-equal-except.js +639 -0
- package/dist/chunks/deep-equal-except.js.map +1 -0
- package/dist/chunks/errors.d.ts +785 -0
- package/dist/chunks/errors.js +822 -0
- package/dist/chunks/errors.js.map +1 -0
- package/dist/chunks/ports.js +891 -0
- package/dist/chunks/ports.js.map +1 -0
- package/dist/chunks/snapshot-store.d.ts +2808 -0
- package/dist/chunks/utils.d.ts +110 -0
- package/dist/http.d.ts +64 -51
- package/dist/http.js +54 -20
- package/dist/http.js.map +1 -1
- package/dist/index.d.ts +2341 -2640
- package/dist/index.js +6073 -3915
- package/dist/index.js.map +1 -1
- package/dist/money.d.ts +376 -0
- package/dist/money.js +578 -0
- package/dist/money.js.map +1 -0
- package/dist/presentation.d.ts +86 -37
- package/dist/presentation.js +208 -39
- package/dist/presentation.js.map +1 -1
- package/dist/testing.d.ts +517 -335
- package/dist/testing.js +2396 -1184
- package/dist/testing.js.map +1 -1
- package/dist/utils.d.ts +2 -106
- package/dist/utils.js +2 -530
- package/package.json +35 -18
- package/dist/aggregate-DFi6HlEh.d.ts +0 -771
- package/dist/utils.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,28 +1,41 @@
|
|
|
1
1
|
# @shirudo/ddd-kit
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Tactical Domain-Driven Design building blocks for TypeScript.
|
|
4
4
|
|
|
5
|
-
|
|
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
|
|
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
|

|
|
10
22
|

|
|
11
23
|
|
|
12
|
-
##
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
19
|
-
-
|
|
20
|
-
-
|
|
21
|
-
-
|
|
22
|
-
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
|
35
|
+
|
|
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.
|
|
26
39
|
|
|
27
40
|
## Installation
|
|
28
41
|
|
|
@@ -30,60 +43,193 @@ Composable TypeScript toolkit for tactical Domain-Driven Design. Ships the canon
|
|
|
30
43
|
pnpm add @shirudo/ddd-kit @shirudo/result @shirudo/base-error
|
|
31
44
|
```
|
|
32
45
|
|
|
33
|
-
`@shirudo/result` and `@shirudo/base-error` are peer dependencies
|
|
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";
|
|
34
61
|
|
|
35
|
-
|
|
62
|
+
type OrderId = Id<"OrderId">;
|
|
36
63
|
|
|
37
|
-
|
|
38
|
-
|
|
64
|
+
type OrderState = {
|
|
65
|
+
status: "draft" | "confirmed";
|
|
66
|
+
};
|
|
39
67
|
|
|
40
|
-
type
|
|
68
|
+
type OrderConfirmed = DomainEvent<
|
|
69
|
+
"OrderConfirmed",
|
|
70
|
+
{ orderId: OrderId }
|
|
71
|
+
>;
|
|
41
72
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
+
}
|
|
45
84
|
}
|
|
46
85
|
|
|
47
|
-
|
|
48
|
-
|
|
86
|
+
class Order extends AggregateRoot<OrderState, OrderId, OrderEvent> {
|
|
87
|
+
protected readonly aggregateType = "Order";
|
|
49
88
|
|
|
50
|
-
|
|
89
|
+
private constructor(id: OrderId, state: OrderState) {
|
|
90
|
+
super(id, state);
|
|
91
|
+
}
|
|
51
92
|
|
|
52
|
-
|
|
93
|
+
static draft(id: OrderId): Order {
|
|
94
|
+
return new Order(id, { status: "draft" });
|
|
95
|
+
}
|
|
53
96
|
|
|
54
|
-
|
|
97
|
+
get status(): OrderState["status"] {
|
|
98
|
+
return this.state.status;
|
|
99
|
+
}
|
|
55
100
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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) |
|
|
101
|
+
confirm(): void {
|
|
102
|
+
if (this.state.status === "confirmed") {
|
|
103
|
+
throw new OrderAlreadyConfirmedError(this.id);
|
|
104
|
+
}
|
|
72
105
|
|
|
73
|
-
|
|
106
|
+
this.commit(
|
|
107
|
+
{ status: "confirmed" },
|
|
108
|
+
this.createEvent("OrderConfirmed", { orderId: this.id }),
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
74
112
|
|
|
75
|
-
|
|
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.
|
|
113
|
+
const order = Order.draft("order-1" as OrderId);
|
|
79
114
|
|
|
80
|
-
|
|
115
|
+
order.confirm();
|
|
81
116
|
|
|
82
|
-
|
|
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
|
|
83
218
|
|
|
84
219
|
## Contributing
|
|
85
220
|
|
|
86
|
-
|
|
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`.
|
|
87
233
|
|
|
88
234
|
## License
|
|
89
235
|
|
|
@@ -91,4 +237,7 @@ MIT.
|
|
|
91
237
|
|
|
92
238
|
## Author
|
|
93
239
|
|
|
94
|
-
**Shirudo
|
|
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)
|