@woltz/rich-domain 1.8.3 → 1.8.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 +659 -652
- package/dist/cjs/core/base-entity.d.ts.map +1 -1
- package/dist/cjs/core/base-entity.js.map +1 -1
- package/dist/cjs/core/change-tracker.d.ts.map +1 -1
- package/dist/cjs/core/change-tracker.js +6 -2
- package/dist/cjs/core/change-tracker.js.map +1 -1
- package/dist/cjs/core/value-object.d.ts.map +1 -1
- package/dist/cjs/core/value-object.js.map +1 -1
- package/dist/cjs/repository/mapper.d.ts +2 -1
- package/dist/cjs/repository/mapper.d.ts.map +1 -1
- package/dist/cjs/repository/mapper.js +3 -0
- package/dist/cjs/repository/mapper.js.map +1 -1
- package/dist/cjs/types/change-tracker.d.ts.map +1 -1
- package/dist/cjs/types/criteria.d.ts.map +1 -1
- package/dist/cjs/types/utils.d.ts.map +1 -1
- package/dist/cjs/validation-error.d.ts.map +1 -1
- package/dist/cjs/validation-error.js +24 -19
- package/dist/cjs/validation-error.js.map +1 -1
- package/dist/esm/core/base-entity.d.ts.map +1 -1
- package/dist/esm/core/base-entity.js.map +1 -1
- package/dist/esm/core/change-tracker.d.ts.map +1 -1
- package/dist/esm/core/change-tracker.js +6 -2
- package/dist/esm/core/change-tracker.js.map +1 -1
- package/dist/esm/core/value-object.d.ts.map +1 -1
- package/dist/esm/core/value-object.js.map +1 -1
- package/dist/esm/repository/mapper.d.ts +2 -1
- package/dist/esm/repository/mapper.d.ts.map +1 -1
- package/dist/esm/repository/mapper.js +3 -0
- package/dist/esm/repository/mapper.js.map +1 -1
- package/dist/esm/types/change-tracker.d.ts.map +1 -1
- package/dist/esm/types/criteria.d.ts.map +1 -1
- package/dist/esm/types/utils.d.ts.map +1 -1
- package/dist/esm/validation-error.d.ts.map +1 -1
- package/dist/esm/validation-error.js +24 -19
- package/dist/esm/validation-error.js.map +1 -1
- package/dist/tsconfig.cjs.tsbuildinfo +1 -1
- package/dist/tsconfig.esm.tsbuildinfo +1 -1
- package/dist/tsconfig.types.tsbuildinfo +1 -1
- package/dist/types/core/base-entity.d.ts.map +1 -1
- package/dist/types/core/change-tracker.d.ts.map +1 -1
- package/dist/types/core/value-object.d.ts.map +1 -1
- package/dist/types/repository/mapper.d.ts +2 -1
- package/dist/types/repository/mapper.d.ts.map +1 -1
- package/dist/types/types/change-tracker.d.ts.map +1 -1
- package/dist/types/types/criteria.d.ts.map +1 -1
- package/dist/types/types/utils.d.ts.map +1 -1
- package/dist/types/validation-error.d.ts.map +1 -1
- package/package.json +70 -70
package/README.md
CHANGED
|
@@ -1,652 +1,659 @@
|
|
|
1
|
-
# @woltz/rich-domain
|
|
2
|
-
|
|
3
|
-
A TypeScript library for Domain-Driven Design with Standard Schema validation, automatic change tracking, and enterprise-ready repositories.
|
|
4
|
-
|
|
5
|
-
[](https://www.npmjs.com/package/@woltz/rich-domain)
|
|
6
|
-
[](https://opensource.org/licenses/MIT)
|
|
7
|
-
|
|
8
|
-
## Features
|
|
9
|
-
|
|
10
|
-
- 🎯 **Type-Safe DDD Building Blocks** - Entities, Aggregates, Value Objects with full TypeScript support
|
|
11
|
-
- ✅ **Validation Agnostic** - Works with Zod, Valibot, ArkType, or any Standard Schema compatible library
|
|
12
|
-
- 🔄 **Automatic Change Tracking** - Track changes across nested entities and collections without boilerplate
|
|
13
|
-
- 🗄️ **ORM Independent** - Use with Prisma, TypeORM, Drizzle, or any persistence layer
|
|
14
|
-
- 🔍 **Rich Query API** - Type-safe Criteria pattern with fluent API for complex queries
|
|
15
|
-
- 📦 **Repository Pattern** - Abstract your persistence layer with built-in pagination and filtering
|
|
16
|
-
- 🎭 **Domain Events** - Built-in event system for cross-aggregate communication
|
|
17
|
-
- 🔐 **Lifecycle Hooks** - onCreate, onBeforeUpdate, and business rule validation
|
|
18
|
-
- 🪝 **React Integration** - Ready-to-use hooks and components via [@woltz/react-rich-domain](https://www.npmjs.com/package/@woltz/react-rich-domain)
|
|
19
|
-
|
|
20
|
-
## Installation
|
|
21
|
-
|
|
22
|
-
```bash
|
|
23
|
-
npm install @woltz/rich-domain
|
|
24
|
-
|
|
25
|
-
# With your preferred validation library
|
|
26
|
-
npm install zod # or valibot, arktype
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
## Quick Start
|
|
30
|
-
|
|
31
|
-
### 1. Define Your Domain Entities
|
|
32
|
-
|
|
33
|
-
```typescript
|
|
34
|
-
import { Aggregate, Entity, Id } from "@woltz/rich-domain";
|
|
35
|
-
import { z } from "zod";
|
|
36
|
-
|
|
37
|
-
// Value Object
|
|
38
|
-
class Email extends ValueObject<string> {
|
|
39
|
-
protected static validation = {
|
|
40
|
-
schema: z.string().email("Invalid email format"),
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
getDomain(): string {
|
|
44
|
-
return this.value.split("@")[1];
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// Entity (child of Aggregate)
|
|
49
|
-
const PostSchema = z.object({
|
|
50
|
-
id: z.custom<Id>(),
|
|
51
|
-
title: z.string().min(3),
|
|
52
|
-
content: z.string(),
|
|
53
|
-
published: z.boolean(),
|
|
54
|
-
createdAt: z.date(),
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
class Post extends Entity<z.infer<typeof PostSchema>> {
|
|
58
|
-
protected static validation = { schema: PostSchema };
|
|
59
|
-
|
|
60
|
-
publish(): void {
|
|
61
|
-
this.props.published = true;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
get title() {
|
|
65
|
-
return this.props.title;
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// Aggregate Root
|
|
70
|
-
const UserSchema = z.object({
|
|
71
|
-
id: z.custom<Id>(),
|
|
72
|
-
email: z.custom<Email>(),
|
|
73
|
-
name: z.string(),
|
|
74
|
-
posts: z.array(z.instanceof(Post)),
|
|
75
|
-
createdAt: z.date(),
|
|
76
|
-
updatedAt: z.date(),
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
class User extends Aggregate<z.infer<typeof UserSchema>> {
|
|
80
|
-
protected static validation = { schema: UserSchema };
|
|
81
|
-
|
|
82
|
-
addPost(title: string, content: string): void {
|
|
83
|
-
const post = new Post({
|
|
84
|
-
title,
|
|
85
|
-
content,
|
|
86
|
-
published: false,
|
|
87
|
-
createdAt: new Date(),
|
|
88
|
-
});
|
|
89
|
-
this.props.posts.push(post);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
get email() {
|
|
93
|
-
return this.props.email.value;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
get posts() {
|
|
97
|
-
return this.props.posts;
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
```
|
|
101
|
-
|
|
102
|
-
### 2. Automatic Change Tracking
|
|
103
|
-
|
|
104
|
-
Changes are tracked automatically - no manual tracking needed:
|
|
105
|
-
|
|
106
|
-
```typescript
|
|
107
|
-
// Load existing user
|
|
108
|
-
const user = new User({
|
|
109
|
-
id: Id.from("user-123"),
|
|
110
|
-
email: new Email("john@example.com"),
|
|
111
|
-
name: "John Doe",
|
|
112
|
-
posts: [
|
|
113
|
-
new Post({
|
|
114
|
-
id: Id.from("post-1"),
|
|
115
|
-
title: "First Post",
|
|
116
|
-
content: "Content here",
|
|
117
|
-
published: false,
|
|
118
|
-
createdAt: new Date(),
|
|
119
|
-
}),
|
|
120
|
-
],
|
|
121
|
-
createdAt: new Date(),
|
|
122
|
-
updatedAt: new Date(),
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
// Make changes
|
|
126
|
-
user.addPost("Second Post", "More content"); // Create
|
|
127
|
-
user.posts[0].publish(); // Update
|
|
128
|
-
user.posts.splice(0, 1); // Delete
|
|
129
|
-
|
|
130
|
-
// Get all changes automatically organized
|
|
131
|
-
const changes = user.getChanges();
|
|
132
|
-
|
|
133
|
-
console.log(changes.hasCreates()); // true
|
|
134
|
-
console.log(changes.hasUpdates()); // true
|
|
135
|
-
console.log(changes.hasDeletes()); // true
|
|
136
|
-
|
|
137
|
-
// Changes are organized by depth for proper FK handling
|
|
138
|
-
const batch = changes.toBatchOperations();
|
|
139
|
-
// {
|
|
140
|
-
// deletes: [{ entity: "Post", depth: 1, ids: ["post-1"] }],
|
|
141
|
-
// creates: [{ entity: "Post", depth: 1, items: [...] }],
|
|
142
|
-
// updates: [{ entity: "Post", depth: 1, items: [...] }]
|
|
143
|
-
// }
|
|
144
|
-
```
|
|
145
|
-
|
|
146
|
-
### 3. Type-Safe Queries with Criteria
|
|
147
|
-
|
|
148
|
-
Build complex queries with full type safety:
|
|
149
|
-
|
|
150
|
-
```typescript
|
|
151
|
-
import { Criteria } from "@woltz/rich-domain";
|
|
152
|
-
|
|
153
|
-
// Simple query
|
|
154
|
-
const activePosts = Criteria.create<Post>()
|
|
155
|
-
.where("published", "equals", true)
|
|
156
|
-
.orderBy("createdAt", "desc")
|
|
157
|
-
.limit(10);
|
|
158
|
-
|
|
159
|
-
// Complex query with multiple filters
|
|
160
|
-
const criteria = Criteria.create<User>()
|
|
161
|
-
.where("name", "contains", "John")
|
|
162
|
-
.where("email", "startsWith", "john")
|
|
163
|
-
.where("createdAt", "greaterThan", new Date("2024-01-01"))
|
|
164
|
-
.orderBy("name", "asc")
|
|
165
|
-
.paginate(1, 20);
|
|
166
|
-
|
|
167
|
-
// Use with repository
|
|
168
|
-
const result = await userRepository.find(criteria);
|
|
169
|
-
// result: PaginatedResult<User>
|
|
170
|
-
|
|
171
|
-
console.log(result.data); // User[]
|
|
172
|
-
console.log(result.meta); // { page: 1, pageSize: 20, total: 100, totalPages: 5 }
|
|
173
|
-
```
|
|
174
|
-
|
|
175
|
-
### 4. Repository Pattern
|
|
176
|
-
|
|
177
|
-
Abstract your persistence layer:
|
|
178
|
-
|
|
179
|
-
```typescript
|
|
180
|
-
import { IRepository, Criteria } from "@woltz/rich-domain";
|
|
181
|
-
|
|
182
|
-
interface IUserRepository extends IRepository<User> {
|
|
183
|
-
findByEmail(email: string): Promise<User | null>;
|
|
184
|
-
findActiveUsers(): Promise<User[]>;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
class UserRepository implements IUserRepository {
|
|
188
|
-
async save(user: User): Promise<void> {
|
|
189
|
-
// Your persistence logic
|
|
190
|
-
const changes = user.getChanges();
|
|
191
|
-
|
|
192
|
-
// Handle deletes (deepest first)
|
|
193
|
-
for (const deletion of changes.toBatchOperations().deletes) {
|
|
194
|
-
// Delete by entity and IDs
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
// Handle creates (root first)
|
|
198
|
-
for (const creation of changes.toBatchOperations().creates) {
|
|
199
|
-
// Create new entities
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
// Handle updates
|
|
203
|
-
for (const update of changes.toBatchOperations().updates) {
|
|
204
|
-
// Update only changed fields
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
async findById(id: string): Promise<User | null> {
|
|
209
|
-
// Your query logic
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
async find(criteria: Criteria<User>): Promise<PaginatedResult<User>> {
|
|
213
|
-
// Transform criteria to your ORM query
|
|
214
|
-
const filters = criteria.getFilters();
|
|
215
|
-
const ordering = criteria.getOrdering();
|
|
216
|
-
const pagination = criteria.getPagination();
|
|
217
|
-
|
|
218
|
-
// Execute query and return paginated result
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
async findByEmail(email: string): Promise<User | null> {
|
|
222
|
-
const criteria = Criteria.create<User>()
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
public readonly
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
await
|
|
361
|
-
order.
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
currency,
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
const
|
|
402
|
-
const
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
console.log(
|
|
406
|
-
console.log(
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
```
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
```
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
):
|
|
544
|
-
|
|
545
|
-
//
|
|
546
|
-
|
|
547
|
-
field: K,
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
}
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
```
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
```
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
-
|
|
613
|
-
-
|
|
614
|
-
-
|
|
615
|
-
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
-
|
|
623
|
-
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
1
|
+
# @woltz/rich-domain
|
|
2
|
+
|
|
3
|
+
A TypeScript library for Domain-Driven Design with Standard Schema validation, automatic change tracking, and enterprise-ready repositories.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@woltz/rich-domain)
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
7
|
+
|
|
8
|
+
## Features
|
|
9
|
+
|
|
10
|
+
- 🎯 **Type-Safe DDD Building Blocks** - Entities, Aggregates, Value Objects with full TypeScript support
|
|
11
|
+
- ✅ **Validation Agnostic** - Works with Zod, Valibot, ArkType, or any Standard Schema compatible library
|
|
12
|
+
- 🔄 **Automatic Change Tracking** - Track changes across nested entities and collections without boilerplate
|
|
13
|
+
- 🗄️ **ORM Independent** - Use with Prisma, TypeORM, Drizzle, or any persistence layer
|
|
14
|
+
- 🔍 **Rich Query API** - Type-safe Criteria pattern with fluent API for complex queries
|
|
15
|
+
- 📦 **Repository Pattern** - Abstract your persistence layer with built-in pagination and filtering
|
|
16
|
+
- 🎭 **Domain Events** - Built-in event system for cross-aggregate communication
|
|
17
|
+
- 🔐 **Lifecycle Hooks** - onCreate, onBeforeUpdate, and business rule validation
|
|
18
|
+
- 🪝 **React Integration** - Ready-to-use hooks and components via [@woltz/react-rich-domain](https://www.npmjs.com/package/@woltz/react-rich-domain)
|
|
19
|
+
|
|
20
|
+
## Installation
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npm install @woltz/rich-domain
|
|
24
|
+
|
|
25
|
+
# With your preferred validation library
|
|
26
|
+
npm install zod # or valibot, arktype
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Quick Start
|
|
30
|
+
|
|
31
|
+
### 1. Define Your Domain Entities
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
import { Aggregate, Entity, Id } from "@woltz/rich-domain";
|
|
35
|
+
import { z } from "zod";
|
|
36
|
+
|
|
37
|
+
// Value Object
|
|
38
|
+
class Email extends ValueObject<string> {
|
|
39
|
+
protected static validation = {
|
|
40
|
+
schema: z.string().email("Invalid email format"),
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
getDomain(): string {
|
|
44
|
+
return this.value.split("@")[1];
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Entity (child of Aggregate)
|
|
49
|
+
const PostSchema = z.object({
|
|
50
|
+
id: z.custom<Id>(),
|
|
51
|
+
title: z.string().min(3),
|
|
52
|
+
content: z.string(),
|
|
53
|
+
published: z.boolean(),
|
|
54
|
+
createdAt: z.date(),
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
class Post extends Entity<z.infer<typeof PostSchema>> {
|
|
58
|
+
protected static validation = { schema: PostSchema };
|
|
59
|
+
|
|
60
|
+
publish(): void {
|
|
61
|
+
this.props.published = true;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
get title() {
|
|
65
|
+
return this.props.title;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Aggregate Root
|
|
70
|
+
const UserSchema = z.object({
|
|
71
|
+
id: z.custom<Id>(),
|
|
72
|
+
email: z.custom<Email>(),
|
|
73
|
+
name: z.string(),
|
|
74
|
+
posts: z.array(z.instanceof(Post)),
|
|
75
|
+
createdAt: z.date(),
|
|
76
|
+
updatedAt: z.date(),
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
class User extends Aggregate<z.infer<typeof UserSchema>> {
|
|
80
|
+
protected static validation = { schema: UserSchema };
|
|
81
|
+
|
|
82
|
+
addPost(title: string, content: string): void {
|
|
83
|
+
const post = new Post({
|
|
84
|
+
title,
|
|
85
|
+
content,
|
|
86
|
+
published: false,
|
|
87
|
+
createdAt: new Date(),
|
|
88
|
+
});
|
|
89
|
+
this.props.posts.push(post);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
get email() {
|
|
93
|
+
return this.props.email.value;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
get posts() {
|
|
97
|
+
return this.props.posts;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### 2. Automatic Change Tracking
|
|
103
|
+
|
|
104
|
+
Changes are tracked automatically - no manual tracking needed:
|
|
105
|
+
|
|
106
|
+
```typescript
|
|
107
|
+
// Load existing user
|
|
108
|
+
const user = new User({
|
|
109
|
+
id: Id.from("user-123"),
|
|
110
|
+
email: new Email("john@example.com"),
|
|
111
|
+
name: "John Doe",
|
|
112
|
+
posts: [
|
|
113
|
+
new Post({
|
|
114
|
+
id: Id.from("post-1"),
|
|
115
|
+
title: "First Post",
|
|
116
|
+
content: "Content here",
|
|
117
|
+
published: false,
|
|
118
|
+
createdAt: new Date(),
|
|
119
|
+
}),
|
|
120
|
+
],
|
|
121
|
+
createdAt: new Date(),
|
|
122
|
+
updatedAt: new Date(),
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
// Make changes
|
|
126
|
+
user.addPost("Second Post", "More content"); // Create
|
|
127
|
+
user.posts[0].publish(); // Update
|
|
128
|
+
user.posts.splice(0, 1); // Delete
|
|
129
|
+
|
|
130
|
+
// Get all changes automatically organized
|
|
131
|
+
const changes = user.getChanges();
|
|
132
|
+
|
|
133
|
+
console.log(changes.hasCreates()); // true
|
|
134
|
+
console.log(changes.hasUpdates()); // true
|
|
135
|
+
console.log(changes.hasDeletes()); // true
|
|
136
|
+
|
|
137
|
+
// Changes are organized by depth for proper FK handling
|
|
138
|
+
const batch = changes.toBatchOperations();
|
|
139
|
+
// {
|
|
140
|
+
// deletes: [{ entity: "Post", depth: 1, ids: ["post-1"] }],
|
|
141
|
+
// creates: [{ entity: "Post", depth: 1, items: [...] }],
|
|
142
|
+
// updates: [{ entity: "Post", depth: 1, items: [...] }]
|
|
143
|
+
// }
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### 3. Type-Safe Queries with Criteria
|
|
147
|
+
|
|
148
|
+
Build complex queries with full type safety:
|
|
149
|
+
|
|
150
|
+
```typescript
|
|
151
|
+
import { Criteria } from "@woltz/rich-domain";
|
|
152
|
+
|
|
153
|
+
// Simple query
|
|
154
|
+
const activePosts = Criteria.create<Post>()
|
|
155
|
+
.where("published", "equals", true)
|
|
156
|
+
.orderBy("createdAt", "desc")
|
|
157
|
+
.limit(10);
|
|
158
|
+
|
|
159
|
+
// Complex query with multiple filters
|
|
160
|
+
const criteria = Criteria.create<User>()
|
|
161
|
+
.where("name", "contains", "John")
|
|
162
|
+
.where("email", "startsWith", "john")
|
|
163
|
+
.where("createdAt", "greaterThan", new Date("2024-01-01"))
|
|
164
|
+
.orderBy("name", "asc")
|
|
165
|
+
.paginate(1, 20);
|
|
166
|
+
|
|
167
|
+
// Use with repository
|
|
168
|
+
const result = await userRepository.find(criteria);
|
|
169
|
+
// result: PaginatedResult<User>
|
|
170
|
+
|
|
171
|
+
console.log(result.data); // User[]
|
|
172
|
+
console.log(result.meta); // { page: 1, pageSize: 20, total: 100, totalPages: 5 }
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### 4. Repository Pattern
|
|
176
|
+
|
|
177
|
+
Abstract your persistence layer:
|
|
178
|
+
|
|
179
|
+
```typescript
|
|
180
|
+
import { IRepository, Criteria } from "@woltz/rich-domain";
|
|
181
|
+
|
|
182
|
+
interface IUserRepository extends IRepository<User> {
|
|
183
|
+
findByEmail(email: string): Promise<User | null>;
|
|
184
|
+
findActiveUsers(): Promise<User[]>;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
class UserRepository implements IUserRepository {
|
|
188
|
+
async save(user: User): Promise<void> {
|
|
189
|
+
// Your persistence logic
|
|
190
|
+
const changes = user.getChanges();
|
|
191
|
+
|
|
192
|
+
// Handle deletes (deepest first)
|
|
193
|
+
for (const deletion of changes.toBatchOperations().deletes) {
|
|
194
|
+
// Delete by entity and IDs
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Handle creates (root first)
|
|
198
|
+
for (const creation of changes.toBatchOperations().creates) {
|
|
199
|
+
// Create new entities
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Handle updates
|
|
203
|
+
for (const update of changes.toBatchOperations().updates) {
|
|
204
|
+
// Update only changed fields
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
async findById(id: string): Promise<User | null> {
|
|
209
|
+
// Your query logic
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
async find(criteria: Criteria<User>): Promise<PaginatedResult<User>> {
|
|
213
|
+
// Transform criteria to your ORM query
|
|
214
|
+
const filters = criteria.getFilters();
|
|
215
|
+
const ordering = criteria.getOrdering();
|
|
216
|
+
const pagination = criteria.getPagination();
|
|
217
|
+
|
|
218
|
+
// Execute query and return paginated result
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
async findByEmail(email: string): Promise<User | null> {
|
|
222
|
+
const criteria = Criteria.create<User>().where("email", "equals", email);
|
|
223
|
+
|
|
224
|
+
const result = await this.find(criteria);
|
|
225
|
+
return result.data[0] ?? null;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
## Advanced Features
|
|
231
|
+
|
|
232
|
+
### Lifecycle Hooks
|
|
233
|
+
|
|
234
|
+
Add validation and side effects at key points:
|
|
235
|
+
|
|
236
|
+
```typescript
|
|
237
|
+
class Product extends Aggregate<ProductProps> {
|
|
238
|
+
protected static validation = {
|
|
239
|
+
schema: ProductSchema,
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
protected static hooks = {
|
|
243
|
+
onBeforeCreate: (props) => {
|
|
244
|
+
// Set default values before validation
|
|
245
|
+
if (!props.createdAt) {
|
|
246
|
+
props.createdAt = new Date();
|
|
247
|
+
}
|
|
248
|
+
},
|
|
249
|
+
|
|
250
|
+
onCreate: (entity) => {
|
|
251
|
+
console.log(`Product created: ${entity.name}`);
|
|
252
|
+
},
|
|
253
|
+
|
|
254
|
+
onBeforeUpdate: (entity, snapshot) => {
|
|
255
|
+
// Prevent price changes on inactive products
|
|
256
|
+
if (snapshot.status === "inactive" && entity.price !== snapshot.price) {
|
|
257
|
+
return false; // Reject the change
|
|
258
|
+
}
|
|
259
|
+
return true;
|
|
260
|
+
},
|
|
261
|
+
|
|
262
|
+
rules: (entity) => {
|
|
263
|
+
if (entity.price > 1000 && entity.stock === 0) {
|
|
264
|
+
throw new ValidationError([
|
|
265
|
+
{
|
|
266
|
+
path: ["stock"],
|
|
267
|
+
message: "Premium products must have stock available",
|
|
268
|
+
},
|
|
269
|
+
]);
|
|
270
|
+
}
|
|
271
|
+
},
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
### Optional Input Properties
|
|
277
|
+
|
|
278
|
+
Make properties optional at construction but required in the entity:
|
|
279
|
+
|
|
280
|
+
```typescript
|
|
281
|
+
const userSchema = z.object({
|
|
282
|
+
id: z.custom<Id>(),
|
|
283
|
+
email: z.string().email(),
|
|
284
|
+
password: z.string().min(8), // Required in entity
|
|
285
|
+
createdAt: z.date(), // Required in entity
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
type UserProps = z.infer<typeof userSchema>;
|
|
289
|
+
|
|
290
|
+
// Second generic makes 'password' and 'createdAt' optional at input
|
|
291
|
+
class User extends Aggregate<UserProps, "password" | "createdAt"> {
|
|
292
|
+
protected static validation = { schema: userSchema };
|
|
293
|
+
|
|
294
|
+
protected static hooks = {
|
|
295
|
+
onBeforeCreate: (props) => {
|
|
296
|
+
// Generate values before validation
|
|
297
|
+
if (!props.password) {
|
|
298
|
+
props.password = generateEncryptedPassword();
|
|
299
|
+
}
|
|
300
|
+
if (!props.createdAt) {
|
|
301
|
+
props.createdAt = new Date();
|
|
302
|
+
}
|
|
303
|
+
},
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
get email() {
|
|
307
|
+
return this.props.email;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// ✅ Works without password and createdAt
|
|
312
|
+
const user = new User({
|
|
313
|
+
email: "user@example.com",
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
// ✅ Also works with explicit values
|
|
317
|
+
const customUser = new User({
|
|
318
|
+
email: "user@example.com",
|
|
319
|
+
password: "custom-pass-12345678",
|
|
320
|
+
});
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
### Domain Events
|
|
324
|
+
|
|
325
|
+
Communicate across aggregate boundaries:
|
|
326
|
+
|
|
327
|
+
```typescript
|
|
328
|
+
import { DomainEvent } from "@woltz/rich-domain";
|
|
329
|
+
|
|
330
|
+
class OrderConfirmedEvent extends DomainEvent {
|
|
331
|
+
constructor(
|
|
332
|
+
aggregateId: Id,
|
|
333
|
+
public readonly customerId: string,
|
|
334
|
+
public readonly total: number
|
|
335
|
+
) {
|
|
336
|
+
super(aggregateId);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
protected getPayload() {
|
|
340
|
+
return { customerId: this.customerId, total: this.total };
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
class Order extends Aggregate<OrderProps> {
|
|
345
|
+
confirm(): void {
|
|
346
|
+
if (this.props.items.length === 0) {
|
|
347
|
+
throw new DomainError("Cannot confirm empty order");
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
this.props.status = "confirmed";
|
|
351
|
+
|
|
352
|
+
// Emit event
|
|
353
|
+
this.addDomainEvent(
|
|
354
|
+
new OrderConfirmedEvent(this.id, this.customerId, this.total)
|
|
355
|
+
);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// After saving
|
|
360
|
+
await orderRepository.save(order);
|
|
361
|
+
await order.dispatchAll(eventBus);
|
|
362
|
+
order.clearEvents();
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
### Value Objects
|
|
366
|
+
|
|
367
|
+
Immutable wrappers for primitive values with domain behavior:
|
|
368
|
+
|
|
369
|
+
```typescript
|
|
370
|
+
import { ValueObject, VOHooks, throwValidationError } from "@woltz/rich-domain";
|
|
371
|
+
|
|
372
|
+
class Price extends ValueObject<number> {
|
|
373
|
+
protected static validation = {
|
|
374
|
+
schema: z.number().positive("Price must be positive"),
|
|
375
|
+
};
|
|
376
|
+
|
|
377
|
+
protected static hooks: VOHooks<number, Price> = {
|
|
378
|
+
rules: (price) => {
|
|
379
|
+
if (price.value > 1000000) {
|
|
380
|
+
throwValidationError("value", "Price cannot exceed 1,000,000");
|
|
381
|
+
}
|
|
382
|
+
},
|
|
383
|
+
};
|
|
384
|
+
|
|
385
|
+
addTax(taxRate: number): Price {
|
|
386
|
+
return this.clone(this.value * (1 + taxRate));
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
discount(percentage: number): Price {
|
|
390
|
+
return this.clone(this.value * (1 - percentage / 100));
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
format(currency: string = "USD"): string {
|
|
394
|
+
return new Intl.NumberFormat("en-US", {
|
|
395
|
+
style: "currency",
|
|
396
|
+
currency,
|
|
397
|
+
}).format(this.value);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
const price = new Price(99.99);
|
|
402
|
+
const withTax = price.addTax(0.08);
|
|
403
|
+
const discounted = price.discount(10);
|
|
404
|
+
|
|
405
|
+
console.log(price.format()); // "$99.99"
|
|
406
|
+
console.log(withTax.format()); // "$107.99"
|
|
407
|
+
console.log(discounted.format()); // "$89.99"
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
## Integration with ORMs
|
|
411
|
+
|
|
412
|
+
Rich Domain provides official adapters for popular ORMs:
|
|
413
|
+
|
|
414
|
+
### Prisma
|
|
415
|
+
|
|
416
|
+
```bash
|
|
417
|
+
npm install @woltz/rich-domain-prisma
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
```typescript
|
|
421
|
+
import {
|
|
422
|
+
PrismaRepository,
|
|
423
|
+
PrismaToPersistence,
|
|
424
|
+
} from "@woltz/rich-domain-prisma";
|
|
425
|
+
|
|
426
|
+
class UserToPersistence extends PrismaToPersistence<User> {
|
|
427
|
+
protected readonly registry = schemaRegistry;
|
|
428
|
+
|
|
429
|
+
protected async onCreate(user: User): Promise<void> {
|
|
430
|
+
await this.context.user.create({
|
|
431
|
+
data: {
|
|
432
|
+
id: user.id.value,
|
|
433
|
+
email: user.email,
|
|
434
|
+
name: user.name,
|
|
435
|
+
},
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
protected async onUpdate(
|
|
440
|
+
user: User,
|
|
441
|
+
changes: AggregateChanges
|
|
442
|
+
): Promise<void> {
|
|
443
|
+
// Automatic batch operations handling
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
### TypeORM
|
|
449
|
+
|
|
450
|
+
```bash
|
|
451
|
+
npm install @woltz/rich-domain-typeorm
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
```typescript
|
|
455
|
+
import { TypeORMRepository } from "@woltz/rich-domain-typeorm";
|
|
456
|
+
|
|
457
|
+
class UserRepository extends TypeORMRepository<User, UserEntity> {
|
|
458
|
+
// Automatic change tracking and batch operations
|
|
459
|
+
}
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
## CLI Tool
|
|
463
|
+
|
|
464
|
+
Bootstrap projects and generate domain code:
|
|
465
|
+
|
|
466
|
+
```bash
|
|
467
|
+
npm install -g @woltz/rich-domain-cli
|
|
468
|
+
|
|
469
|
+
# Initialize new project
|
|
470
|
+
rich-domain init my-app --template fullstack
|
|
471
|
+
|
|
472
|
+
# Generate domain from Prisma schema
|
|
473
|
+
rich-domain generate --schema prisma/schema.prisma
|
|
474
|
+
|
|
475
|
+
# Add entity manually
|
|
476
|
+
rich-domain add User name:string email:string --with-repo
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
## API Reference
|
|
480
|
+
|
|
481
|
+
### Core Classes
|
|
482
|
+
|
|
483
|
+
#### `Id`
|
|
484
|
+
|
|
485
|
+
Unique identifier for entities:
|
|
486
|
+
|
|
487
|
+
```typescript
|
|
488
|
+
const id = Id.create(); // Generate new UUID
|
|
489
|
+
const existingId = Id.from("uuid-string"); // From existing value
|
|
490
|
+
|
|
491
|
+
console.log(id.value); // string
|
|
492
|
+
console.log(id.isNew); // boolean
|
|
493
|
+
console.log(id.equals(otherId)); // boolean
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
#### `Entity<T>`
|
|
497
|
+
|
|
498
|
+
Base class for entities:
|
|
499
|
+
|
|
500
|
+
```typescript
|
|
501
|
+
abstract class Entity<T extends { id: Id }> {
|
|
502
|
+
get id(): Id;
|
|
503
|
+
get isNew(): boolean;
|
|
504
|
+
equals(other: Entity<T>): boolean;
|
|
505
|
+
toJSON(): object;
|
|
506
|
+
}
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
#### `Aggregate<T>`
|
|
510
|
+
|
|
511
|
+
Root entity with change tracking:
|
|
512
|
+
|
|
513
|
+
```typescript
|
|
514
|
+
abstract class Aggregate<T extends { id: Id }> extends Entity<T> {
|
|
515
|
+
getChanges(): AggregateChanges;
|
|
516
|
+
|
|
517
|
+
// Domain Events
|
|
518
|
+
protected addDomainEvent(event: IDomainEvent): void;
|
|
519
|
+
getUncommittedEvents(): IDomainEvent[];
|
|
520
|
+
clearEvents(): void;
|
|
521
|
+
dispatchAll(bus: DomainEventBus): Promise<void>;
|
|
522
|
+
}
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
#### `ValueObject<T>`
|
|
526
|
+
|
|
527
|
+
Immutable object compared by value:
|
|
528
|
+
|
|
529
|
+
```typescript
|
|
530
|
+
abstract class ValueObject<T> {
|
|
531
|
+
protected readonly props: T;
|
|
532
|
+
|
|
533
|
+
equals(other: ValueObject<T>): boolean;
|
|
534
|
+
toJSON(): T;
|
|
535
|
+
protected clone(updates: Partial<T>): this;
|
|
536
|
+
}
|
|
537
|
+
```
|
|
538
|
+
|
|
539
|
+
### Criteria API
|
|
540
|
+
|
|
541
|
+
```typescript
|
|
542
|
+
class Criteria<T> {
|
|
543
|
+
static create<T>(): Criteria<T>;
|
|
544
|
+
|
|
545
|
+
// Filters
|
|
546
|
+
where<K extends FieldPath<T>>(
|
|
547
|
+
field: K,
|
|
548
|
+
operator: FilterOperator,
|
|
549
|
+
value: any
|
|
550
|
+
): this;
|
|
551
|
+
|
|
552
|
+
// Ordering
|
|
553
|
+
orderBy<K extends FieldPath<T>>(field: K, direction: "asc" | "desc"): this;
|
|
554
|
+
|
|
555
|
+
// Pagination
|
|
556
|
+
limit(limit: number): this;
|
|
557
|
+
offset(offset: number): this;
|
|
558
|
+
paginate(page: number, pageSize: number): this;
|
|
559
|
+
|
|
560
|
+
// Search
|
|
561
|
+
search(term: string): this;
|
|
562
|
+
|
|
563
|
+
// Getters
|
|
564
|
+
getFilters(): Filter<T>[];
|
|
565
|
+
getOrdering(): Order<T> | null;
|
|
566
|
+
getPagination(): Pagination | null;
|
|
567
|
+
getSearch(): string | null;
|
|
568
|
+
}
|
|
569
|
+
```
|
|
570
|
+
|
|
571
|
+
### Exception Handling
|
|
572
|
+
|
|
573
|
+
Rich Domain provides comprehensive exception types:
|
|
574
|
+
|
|
575
|
+
```typescript
|
|
576
|
+
import {
|
|
577
|
+
ValidationError,
|
|
578
|
+
DomainError,
|
|
579
|
+
EntityNotFoundError,
|
|
580
|
+
DuplicateEntityError,
|
|
581
|
+
ConcurrencyError,
|
|
582
|
+
RepositoryError,
|
|
583
|
+
} from "@woltz/rich-domain";
|
|
584
|
+
|
|
585
|
+
try {
|
|
586
|
+
const user = new User({
|
|
587
|
+
/* invalid props */
|
|
588
|
+
});
|
|
589
|
+
} catch (error) {
|
|
590
|
+
if (error instanceof ValidationError) {
|
|
591
|
+
console.log(error.entity); // "User"
|
|
592
|
+
console.log(error.field); // "email"
|
|
593
|
+
console.log(error.message); // "Invalid email format"
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
```
|
|
597
|
+
|
|
598
|
+
## Package Format
|
|
599
|
+
|
|
600
|
+
This library is published as a **dual package** supporting both CommonJS and ES Modules:
|
|
601
|
+
|
|
602
|
+
```javascript
|
|
603
|
+
// CommonJS
|
|
604
|
+
const { Id, Entity, Aggregate } = require("@woltz/rich-domain");
|
|
605
|
+
|
|
606
|
+
// ES Modules
|
|
607
|
+
import { Id, Entity, Aggregate } from "@woltz/rich-domain";
|
|
608
|
+
```
|
|
609
|
+
|
|
610
|
+
Benefits:
|
|
611
|
+
|
|
612
|
+
- ✅ Universal compatibility (Node.js, Vite, Webpack, etc.)
|
|
613
|
+
- ✅ Tree-shaking support for modern bundlers
|
|
614
|
+
- ✅ Full TypeScript support with type definitions
|
|
615
|
+
- ✅ Zero configuration - automatically uses the correct format
|
|
616
|
+
|
|
617
|
+
## Documentation
|
|
618
|
+
|
|
619
|
+
- 📚 [Full Documentation](https://woltz.mintlify.app)
|
|
620
|
+
- 🚀 [Quick Start Guide](https://woltz.mintlify.app/quickstart)
|
|
621
|
+
- 📖 [Core Concepts](https://woltz.mintlify.app/core/entities-and-aggregates)
|
|
622
|
+
- 🔌 [Integrations](https://woltz.mintlify.app/integrations/prisma)
|
|
623
|
+
- ⚛️ [React Components](https://woltz.mintlify.app/integrations/react)
|
|
624
|
+
|
|
625
|
+
## Examples
|
|
626
|
+
|
|
627
|
+
Check out the [examples directory](./examples) for complete implementations:
|
|
628
|
+
|
|
629
|
+
- Basic CRUD operations
|
|
630
|
+
- Complex aggregate relationships
|
|
631
|
+
- Custom validation rules
|
|
632
|
+
- Domain events
|
|
633
|
+
- Repository implementations for different ORMs
|
|
634
|
+
|
|
635
|
+
## Ecosystem
|
|
636
|
+
|
|
637
|
+
| Package | Description | Version |
|
|
638
|
+
| ------------------------------------------------------------------------------------------------ | -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
|
|
639
|
+
| [@woltz/rich-domain](https://www.npmjs.com/package/@woltz/rich-domain) | Core library | [](https://www.npmjs.com/package/@woltz/rich-domain) |
|
|
640
|
+
| [@woltz/rich-domain-prisma](https://www.npmjs.com/package/@woltz/rich-domain-prisma) | Prisma adapter | [](https://www.npmjs.com/package/@woltz/rich-domain-prisma) |
|
|
641
|
+
| [@woltz/rich-domain-typeorm](https://www.npmjs.com/package/@woltz/rich-domain-typeorm) | TypeORM adapter | [](https://www.npmjs.com/package/@woltz/rich-domain-typeorm) |
|
|
642
|
+
| [@woltz/rich-domain-criteria-zod](https://www.npmjs.com/package/@woltz/rich-domain-criteria-zod) | Zod criteria builder | [](https://www.npmjs.com/package/@woltz/rich-domain-criteria-zod) |
|
|
643
|
+
| [@woltz/rich-domain-cli](https://www.npmjs.com/package/@woltz/rich-domain-cli) | CLI tool | [](https://www.npmjs.com/package/@woltz/rich-domain-cli) |
|
|
644
|
+
|
|
645
|
+
## Contributing
|
|
646
|
+
|
|
647
|
+
Contributions are welcome! Please read our [Contributing Guide](./CONTRIBUTING.md) for details.
|
|
648
|
+
|
|
649
|
+
## License
|
|
650
|
+
|
|
651
|
+
MIT © [Tarcisio Andrade](https://github.com/tarcisioandrade)
|
|
652
|
+
|
|
653
|
+
## Links
|
|
654
|
+
|
|
655
|
+
- [Documentation](https://woltz.mintlify.app)
|
|
656
|
+
- [GitHub Repository](https://github.com/tarcisioandrade/rich-domain)
|
|
657
|
+
- [npm Package](https://www.npmjs.com/package/@woltz/rich-domain)
|
|
658
|
+
- [Issues](https://github.com/tarcisioandrade/rich-domain/issues)
|
|
659
|
+
- [Changelog](./CHANGELOG.md)
|