@woltz/rich-domain 1.5.0 → 1.6.0

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.
Files changed (59) hide show
  1. package/README.md +426 -583
  2. package/dist/cjs/base-entity.d.ts +2 -3
  3. package/dist/cjs/base-entity.d.ts.map +1 -1
  4. package/dist/cjs/base-entity.js.map +1 -1
  5. package/dist/cjs/domain-event.d.ts +10 -12
  6. package/dist/cjs/domain-event.d.ts.map +1 -1
  7. package/dist/cjs/domain-event.js +5 -16
  8. package/dist/cjs/domain-event.js.map +1 -1
  9. package/dist/cjs/index.d.ts +1 -2
  10. package/dist/cjs/index.d.ts.map +1 -1
  11. package/dist/cjs/index.js +0 -1
  12. package/dist/cjs/index.js.map +1 -1
  13. package/dist/cjs/types/domain-event.d.ts +7 -13
  14. package/dist/cjs/types/domain-event.d.ts.map +1 -1
  15. package/dist/cjs/types/event-bus.d.ts +16 -0
  16. package/dist/cjs/types/event-bus.d.ts.map +1 -0
  17. package/dist/cjs/types/event-bus.js +3 -0
  18. package/dist/cjs/types/event-bus.js.map +1 -0
  19. package/dist/cjs/types/index.d.ts +1 -0
  20. package/dist/cjs/types/index.d.ts.map +1 -1
  21. package/dist/cjs/types/index.js +1 -0
  22. package/dist/cjs/types/index.js.map +1 -1
  23. package/dist/esm/base-entity.d.ts +2 -3
  24. package/dist/esm/base-entity.d.ts.map +1 -1
  25. package/dist/esm/base-entity.js.map +1 -1
  26. package/dist/esm/domain-event.d.ts +10 -12
  27. package/dist/esm/domain-event.d.ts.map +1 -1
  28. package/dist/esm/domain-event.js +5 -16
  29. package/dist/esm/domain-event.js.map +1 -1
  30. package/dist/esm/index.d.ts +1 -2
  31. package/dist/esm/index.d.ts.map +1 -1
  32. package/dist/esm/index.js +0 -1
  33. package/dist/esm/index.js.map +1 -1
  34. package/dist/esm/types/domain-event.d.ts +7 -13
  35. package/dist/esm/types/domain-event.d.ts.map +1 -1
  36. package/dist/esm/types/event-bus.d.ts +16 -0
  37. package/dist/esm/types/event-bus.d.ts.map +1 -0
  38. package/dist/esm/types/event-bus.js +2 -0
  39. package/dist/esm/types/event-bus.js.map +1 -0
  40. package/dist/esm/types/index.d.ts +1 -0
  41. package/dist/esm/types/index.d.ts.map +1 -1
  42. package/dist/esm/types/index.js +1 -0
  43. package/dist/esm/types/index.js.map +1 -1
  44. package/dist/tsconfig.cjs.tsbuildinfo +1 -1
  45. package/dist/tsconfig.esm.tsbuildinfo +1 -1
  46. package/dist/tsconfig.types.tsbuildinfo +1 -1
  47. package/dist/types/base-entity.d.ts +2 -3
  48. package/dist/types/base-entity.d.ts.map +1 -1
  49. package/dist/types/domain-event.d.ts +10 -12
  50. package/dist/types/domain-event.d.ts.map +1 -1
  51. package/dist/types/index.d.ts +1 -2
  52. package/dist/types/index.d.ts.map +1 -1
  53. package/dist/types/types/domain-event.d.ts +7 -13
  54. package/dist/types/types/domain-event.d.ts.map +1 -1
  55. package/dist/types/types/event-bus.d.ts +16 -0
  56. package/dist/types/types/event-bus.d.ts.map +1 -0
  57. package/dist/types/types/index.d.ts +1 -0
  58. package/dist/types/types/index.d.ts.map +1 -1
  59. package/package.json +1 -1
package/README.md CHANGED
@@ -1,750 +1,593 @@
1
- # Rich Domain
1
+ # @woltz/rich-domain
2
2
 
3
- Uma biblioteca TypeScript para Domain-Driven Design (DDD) com suporte a validação via Standard Schema, rastreamento automático de mudanças, sistema de eventos, e repositories enterprise-ready.
3
+ A TypeScript library for Domain-Driven Design with Standard Schema validation, automatic change tracking, and enterprise-ready repositories.
4
4
 
5
- ## Características
5
+ [![npm version](https://img.shields.io/npm/v/@woltz/rich-domain.svg)](https://www.npmjs.com/package/@woltz/rich-domain)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
7
 
7
- - 🏗️ **Entities & Aggregates** - Classes base com identidade e ciclo de vida
8
- - 💎 **Value Objects** - Objetos imutáveis comparados por valor
9
- - ✅ **Standard Schema Validation** - Integração com Zod, ArkType, Valibot e outras libs
10
- - 📜 **Change Tracking** - Histórico automático de todas as mudanças
11
- - 🔔 **Subscriptions** - Sistema de eventos para observar mudanças
12
- - 🎯 **Hooks** - Interceptação de criação e atualização de entidades
13
- - 🆔 **Smart IDs** - Identificadores que sabem se a entidade é nova ou existente
14
- - 🔍 **Criteria Pattern** - Query builder type-safe com filtros, ordenação e paginação
15
- - 📦 **Repository Pattern** - Abstrações para persistência com suporte a Prisma, TypeORM, etc.
16
- - 🔄 **Unit of Work** - Gerenciamento de transações cross-repository
17
- - 📊 **Paginated Results** - Resultados paginados com serialização profunda
8
+ ## Features
18
9
 
19
- ## Instalação
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
20
21
 
21
22
  ```bash
22
- npm install rich-domain
23
+ npm install @woltz/rich-domain
24
+
25
+ # With your preferred validation library
26
+ npm install zod # or valibot, arktype
23
27
  ```
24
28
 
25
29
  ## Quick Start
26
30
 
27
- ### 1. Definindo um Aggregate com Validação
31
+ ### 1. Define Your Domain Entities
28
32
 
29
33
  ```typescript
34
+ import { Aggregate, Entity, Id } from "@woltz/rich-domain";
30
35
  import { z } from "zod";
31
- import {
32
- Id,
33
- Aggregate,
34
- EntityValidation,
35
- EntityHooks,
36
- BaseProps,
37
- throwValidationError,
38
- } from "rich-domain";
39
-
40
- // Define as propriedades
41
- interface UserProps extends BaseProps {
42
- name: string;
43
- email: string;
44
- age: number;
45
- status: "active" | "inactive";
36
+
37
+ // Value Object
38
+ class Email extends ValueObject<{ value: string }> {
39
+ protected static validation = {
40
+ schema: z.object({
41
+ value: z.string().email("Invalid email format"),
42
+ }),
43
+ };
44
+
45
+ get value() {
46
+ return this.props.value;
47
+ }
46
48
  }
47
49
 
48
- // Define o schema de validação (Zod, ArkType, Valibot, etc.)
49
- const userSchema = z.object({
50
- id: z.custom<Id>((val) => val instanceof Id),
51
- name: z.string().min(2, "Nome deve ter pelo menos 2 caracteres"),
52
- email: z.string().email("Email inválido"),
53
- age: z.number().min(0).max(150),
54
- status: z.enum(["active", "inactive"]),
50
+ // Entity (child of Aggregate)
51
+ const PostSchema = z.object({
52
+ id: z.custom<Id>(),
53
+ title: z.string().min(3),
54
+ content: z.string(),
55
+ published: z.boolean(),
56
+ createdAt: z.date(),
55
57
  });
56
58
 
57
- // Cria o Aggregate
58
- class User extends Aggregate<UserProps> {
59
- // Configuração de validação
60
- protected static validation: EntityValidation<UserProps> = {
61
- schema: userSchema,
62
- config: {
63
- onCreate: true,
64
- onUpdate: true,
65
- throwOnError: true,
66
- },
67
- };
59
+ class Post extends Entity<z.infer<typeof PostSchema>> {
60
+ protected static validation = { schema: PostSchema };
68
61
 
69
- // Hooks de ciclo de vida
70
- protected static hooks: EntityHooks<UserProps, User> = {
71
- onCreate: (entity) => {
72
- console.log(`Usuário criado: ${entity.name}`);
73
- },
62
+ publish(): void {
63
+ this.props.published = true;
64
+ }
74
65
 
75
- onBeforeUpdate: (entity, snapshot) => {
76
- // Bloquear mudança de email
77
- if (snapshot.email !== entity.email) {
78
- return false;
79
- }
80
- return true;
81
- },
66
+ get title() {
67
+ return this.props.title;
68
+ }
69
+ }
82
70
 
83
- rules: (entity) => {
84
- if (entity.name.toLowerCase() === "admin") {
85
- throwValidationError("name", 'Nome não pode ser "admin"');
86
- }
87
- },
88
- };
71
+ // Aggregate Root
72
+ const UserSchema = z.object({
73
+ id: z.custom<Id>(),
74
+ email: z.custom<Email>(),
75
+ name: z.string(),
76
+ posts: z.array(z.instanceof(Post)),
77
+ createdAt: z.date(),
78
+ updatedAt: z.date(),
79
+ });
89
80
 
90
- get name() {
91
- return this.props.name;
92
- }
93
- set name(value: string) {
94
- this.props.name = value;
81
+ class User extends Aggregate<z.infer<typeof UserSchema>> {
82
+ protected static validation = { schema: UserSchema };
83
+
84
+ addPost(title: string, content: string): void {
85
+ const post = new Post({
86
+ title,
87
+ content,
88
+ published: false,
89
+ createdAt: new Date(),
90
+ });
91
+ this.props.posts.push(post);
95
92
  }
96
93
 
97
94
  get email() {
98
- return this.props.email;
99
- }
100
- get age() {
101
- return this.props.age;
102
- }
103
- get status() {
104
- return this.props.status;
95
+ return this.props.email.value;
105
96
  }
106
97
 
107
- activate() {
108
- this.props.status = "active";
109
- }
110
- deactivate() {
111
- this.props.status = "inactive";
98
+ get posts() {
99
+ return this.props.posts;
112
100
  }
113
101
  }
114
102
  ```
115
103
 
116
- ### 2. Repository Pattern
104
+ ### 2. Automatic Change Tracking
117
105
 
118
- #### In-Memory (Para Testes)
106
+ Changes are tracked automatically - no manual tracking needed:
119
107
 
120
108
  ```typescript
121
- import { InMemoryRepository, Criteria } from "rich-domain";
122
-
123
- const userRepo = new InMemoryRepository<User>();
124
-
125
- // Salvar
109
+ // Load existing user
126
110
  const user = new User({
127
- name: "João Silva",
128
- email: "joao@example.com",
129
- age: 30,
130
- status: "active",
111
+ id: Id.from("user-123"),
112
+ email: new Email({ value: "john@example.com" }),
113
+ name: "John Doe",
114
+ posts: [
115
+ new Post({
116
+ id: Id.from("post-1"),
117
+ title: "First Post",
118
+ content: "Content here",
119
+ published: false,
120
+ createdAt: new Date(),
121
+ }),
122
+ ],
123
+ createdAt: new Date(),
124
+ updatedAt: new Date(),
131
125
  });
132
- await userRepo.save(user);
133
-
134
- // Buscar por ID
135
- const found = await userRepo.findById(user.id);
136
-
137
- // Buscar com Criteria (type-safe!)
138
- const result = await userRepo.find(
139
- Criteria.create<User>()
140
- .whereEquals("status", "active")
141
- .where("age", "greaterThan", 18)
142
- .orderByDesc("age")
143
- .paginate(1, 10)
144
- );
145
-
146
- console.log(result.data); // Array de User
147
- console.log(result.meta); // { page, limit, total, totalPages, hasNext, hasPrevious }
148
-
149
- // Serializar para API
150
- const json = result.toJSON(); // Deep serialization de todos os agregados
151
- res.json(json);
126
+
127
+ // Make changes
128
+ user.addPost("Second Post", "More content"); // Create
129
+ user.posts[0].publish(); // Update
130
+ user.posts.splice(0, 1); // Delete
131
+
132
+ // Get all changes automatically organized
133
+ const changes = user.getChanges();
134
+
135
+ console.log(changes.hasCreates()); // true
136
+ console.log(changes.hasUpdates()); // true
137
+ console.log(changes.hasDeletes()); // true
138
+
139
+ // Changes are organized by depth for proper FK handling
140
+ const batch = changes.toBatchOperations();
141
+ // {
142
+ // deletes: [{ entity: "Post", depth: 1, ids: ["post-1"] }],
143
+ // creates: [{ entity: "Post", depth: 1, items: [...] }],
144
+ // updates: [{ entity: "Post", depth: 1, items: [...] }]
145
+ // }
152
146
  ```
153
147
 
154
- #### Production (Prisma, TypeORM, etc)
148
+ ### 3. Type-Safe Queries with Criteria
155
149
 
156
- ```typescript
157
- import { BaseRepository, BaseMapper } from "rich-domain";
158
-
159
- // Mapper: Domain ↔ Persistence
160
- class UserMapper extends BaseMapper<User, PrismaUser> {
161
- toDomain(persistence: PrismaUser): User {
162
- return new User({
163
- id: Id.from(persistence.id),
164
- name: persistence.name,
165
- email: persistence.email,
166
- age: persistence.age,
167
- status: persistence.status as "active" | "inactive",
168
- });
169
- }
150
+ Build complex queries with full type safety:
170
151
 
171
- toPersistence(domain: User): PrismaUser {
172
- return {
173
- id: domain.id.value,
174
- name: domain.name,
175
- email: domain.email,
176
- age: domain.age,
177
- status: domain.status,
178
- };
179
- }
180
- }
152
+ ```typescript
153
+ import { Criteria } from "@woltz/rich-domain";
181
154
 
182
- // Repository
183
- class UserRepository extends BaseRepository<User, PrismaUser> {
184
- constructor(private prisma: PrismaClient) {
185
- super(new UserMapper());
186
- }
155
+ // Simple query
156
+ const activePosts = Criteria.create<Post>()
157
+ .where("published", "equals", true)
158
+ .orderBy("createdAt", "desc")
159
+ .limit(10);
187
160
 
188
- protected async insertOne(data: PrismaUser) {
189
- return this.prisma.user.create({ data });
190
- }
161
+ // Complex query with multiple filters
162
+ const criteria = Criteria.create<User>()
163
+ .where("name", "contains", "John")
164
+ .where("email", "startsWith", "john")
165
+ .where("createdAt", "greaterThan", new Date("2024-01-01"))
166
+ .orderBy("name", "asc")
167
+ .paginate(1, 20);
191
168
 
192
- protected async updateOne(id: string, data: PrismaUser) {
193
- return this.prisma.user.update({ where: { id }, data });
194
- }
169
+ // Use with repository
170
+ const result = await userRepository.find(criteria);
171
+ // result: PaginatedResult<User>
195
172
 
196
- protected async deleteOne(id: string) {
197
- await this.prisma.user.delete({ where: { id } });
198
- }
173
+ console.log(result.data); // User[]
174
+ console.log(result.meta); // { page: 1, pageSize: 20, total: 100, totalPages: 5 }
175
+ ```
199
176
 
200
- protected async findOneById(id: string) {
201
- const persistence = await this.prisma.user.findUnique({ where: { id } });
202
- return persistence ? this.mapper.toDomain(persistence) : null;
203
- }
177
+ ### 4. Repository Pattern
204
178
 
205
- protected async findMany() {
206
- const persistence = await this.prisma.user.findMany();
207
- return this.mapper.toDomainList!(persistence);
208
- }
179
+ Abstract your persistence layer:
209
180
 
210
- protected async applyCriteria(criteria: Criteria<User>) {
211
- const where = this.buildWhereClause(criteria);
212
- const orderBy = this.buildOrderBy(criteria);
213
- const pagination = criteria.getPagination();
181
+ ```typescript
182
+ import { IRepository, Criteria } from "@woltz/rich-domain";
214
183
 
215
- const [data, total] = await Promise.all([
216
- this.prisma.user.findMany({
217
- where,
218
- orderBy,
219
- skip: pagination.offset,
220
- take: pagination.limit,
221
- }),
222
- this.prisma.user.count({ where }),
223
- ]);
224
-
225
- return [data, total];
226
- }
184
+ interface IUserRepository extends IRepository<User> {
185
+ findByEmail(email: string): Promise<User | null>;
186
+ findActiveUsers(): Promise<User[]>;
187
+ }
227
188
 
228
- protected async countByCriteria(criteria?: Criteria<User>) {
229
- const where = criteria ? this.buildWhereClause(criteria) : {};
230
- return this.prisma.user.count({ where });
189
+ class UserRepository implements IUserRepository {
190
+ async save(user: User): Promise<void> {
191
+ // Your persistence logic
192
+ const changes = user.getChanges();
193
+
194
+ // Handle deletes (deepest first)
195
+ for (const deletion of changes.toBatchOperations().deletes) {
196
+ // Delete by entity and IDs
197
+ }
198
+
199
+ // Handle creates (root first)
200
+ for (const creation of changes.toBatchOperations().creates) {
201
+ // Create new entities
202
+ }
203
+
204
+ // Handle updates
205
+ for (const update of changes.toBatchOperations().updates) {
206
+ // Update only changed fields
207
+ }
231
208
  }
232
209
 
233
- protected async existsById(id: string) {
234
- const count = await this.prisma.user.count({ where: { id } });
235
- return count > 0;
210
+ async findById(id: string): Promise<User | null> {
211
+ // Your query logic
236
212
  }
237
213
 
238
- // Helpers para converter Criteria Prisma
239
- private buildWhereClause(criteria: Criteria<User>) {
240
- // Ver src/repository/examples/prisma-repository.example.ts
214
+ async find(criteria: Criteria<User>): Promise<PaginatedResult<User>> {
215
+ // Transform criteria to your ORM query
216
+ const filters = criteria.getFilters();
217
+ const ordering = criteria.getOrdering();
218
+ const pagination = criteria.getPagination();
219
+
220
+ // Execute query and return paginated result
241
221
  }
242
222
 
243
- private buildOrderBy(criteria: Criteria<User>) {
244
- // Ver src/repository/examples/prisma-repository.example.ts
223
+ async findByEmail(email: string): Promise<User | null> {
224
+ const criteria = Criteria.create<User>()
225
+ .where("email", "equals", email);
226
+
227
+ const result = await this.find(criteria);
228
+ return result.data[0] ?? null;
245
229
  }
246
230
  }
247
-
248
- // Uso
249
- const prisma = new PrismaClient();
250
- const userRepo = new UserRepository(prisma);
251
-
252
- const result = await userRepo.find(
253
- Criteria.create<User>()
254
- .whereEquals("status", "active")
255
- .orderByDesc("createdAt")
256
- .paginate(1, 10)
257
- );
258
231
  ```
259
232
 
260
- ### 3. Criteria Pattern (Type-Safe Queries)
261
-
262
- ```typescript
263
- import { Criteria } from "rich-domain";
233
+ ## Advanced Features
264
234
 
265
- // Criar criteria
266
- const criteria = Criteria.create<User>()
267
- // Filtros
268
- .whereEquals("status", "active")
269
- .where("age", "greaterThan", 18)
270
- .where("age", "lessThan", 65)
271
- .whereContains("name", "silva")
272
- .whereIn("status", ["active", "pending"])
273
- .whereBetween("age", 18, 65)
274
- .whereNull("deletedAt")
275
- .whereNotNull("email")
276
-
277
- // Ordenação
278
- .orderBy("name", "asc")
279
- .orderByDesc("createdAt")
280
-
281
- // Paginação
282
- .paginate(1, 10)
283
- .limit(20);
284
-
285
- // Busca em múltiplos campos
286
- criteria.search(["name", "email"], "joão");
287
-
288
- // Serialização
289
- const json = criteria.toJSON();
290
-
291
- // Clone
292
- const cloned = criteria.clone();
293
-
294
- // From query params (para APIs)
295
- const criteriaFromUrl = Criteria.fromQueryParams<User>({
296
- "status:equals": "active",
297
- "age:greaterThan": "18",
298
- orderBy: "name:asc,createdAt:desc",
299
- page: "1",
300
- limit: "10",
301
- search: "joão",
302
- searchFields: "name,email",
303
- });
304
- ```
235
+ ### Lifecycle Hooks
305
236
 
306
- ### 4. Unit of Work (Transações)
237
+ Add validation and side effects at key points:
307
238
 
308
239
  ```typescript
309
- import { UnitOfWork } from "rich-domain";
310
-
311
- // Executar múltiplas operações em transação
312
- await uow.transaction(async (ctx) => {
313
- const userRepo = uow.getRepository(UserRepository);
314
- const orderRepo = uow.getRepository(OrderRepository);
315
-
316
- await userRepo.save(user);
317
- await orderRepo.save(order);
318
-
319
- // Auto-commit on success
320
- // Auto-rollback on error
321
- });
322
-
323
- // Controle manual
324
- const ctx = await uow.begin();
325
- try {
326
- await userRepo.save(user);
327
- await orderRepo.save(order);
328
- await ctx.commit();
329
- } catch (error) {
330
- await ctx.rollback();
331
- throw error;
240
+ class Product extends Aggregate<ProductProps> {
241
+ protected static validation = {
242
+ schema: ProductSchema,
243
+ hooks: {
244
+ onCreate: (props) => {
245
+ console.log(`Product created: ${props.name}`);
246
+ },
247
+ onBeforeUpdate: (current, updates) => {
248
+ // Prevent price changes on inactive products
249
+ if (current.status === "inactive" && "price" in updates) {
250
+ delete updates.price;
251
+ }
252
+ },
253
+ },
254
+ rules: (props) => {
255
+ if (props.price > 1000 && props.stock === 0) {
256
+ throw new ValidationError(
257
+ "Product",
258
+ "stock",
259
+ "Premium products must have stock available"
260
+ );
261
+ }
262
+ },
263
+ };
332
264
  }
333
265
  ```
334
266
 
335
- ### 5. Paginated Results com Deep Serialization
267
+ ### Domain Events
268
+
269
+ Communicate across aggregate boundaries:
336
270
 
337
271
  ```typescript
338
- // Com Entities/Aggregates
339
- const users = await userRepo.find(criteria);
340
- // users: PaginatedResult<User>
272
+ import { DomainEvent } from "@woltz/rich-domain";
273
+
274
+ class OrderConfirmedEvent extends DomainEvent {
275
+ constructor(
276
+ aggregateId: Id,
277
+ public readonly customerId: string,
278
+ public readonly total: number
279
+ ) {
280
+ super(aggregateId);
281
+ }
341
282
 
342
- // Serialização profunda (Ids, nested entities, value objects)
343
- const json = users.toJSON();
344
- // {
345
- // data: [
346
- // { id: "123", name: "João", ... }, // IDs serializados para string
347
- // { id: "456", name: "Maria", ... }
348
- // ],
349
- // meta: {
350
- // page: 1,
351
- // limit: 10,
352
- // total: 100,
353
- // totalPages: 10,
354
- // hasNext: true,
355
- // hasPrevious: false
356
- // }
357
- // }
283
+ protected getPayload() {
284
+ return { customerId: this.customerId, total: this.total };
285
+ }
286
+ }
287
+
288
+ class Order extends Aggregate<OrderProps> {
289
+ confirm(): void {
290
+ if (this.props.items.length === 0) {
291
+ throw new DomainError("Cannot confirm empty order");
292
+ }
293
+
294
+ this.props.status = "confirmed";
295
+
296
+ // Emit event
297
+ this.addDomainEvent(
298
+ new OrderConfirmedEvent(this.id, this.customerId, this.total)
299
+ );
300
+ }
301
+ }
358
302
 
359
- // Utilitários
360
- users.isEmpty; // boolean
361
- users.hasMore; // boolean
362
- users.map((user) => user.name); // Transforma cada item
303
+ // After saving
304
+ await orderRepository.save(order);
305
+ await order.dispatchAll(eventBus);
306
+ order.clearEvents();
363
307
  ```
364
308
 
365
- ## Value Objects
309
+ ### Value Objects
310
+
311
+ Immutable objects compared by value:
366
312
 
367
313
  ```typescript
368
- import { ValueObject } from "rich-domain";
314
+ import { ValueObject } from "@woltz/rich-domain";
369
315
 
370
- interface AddressProps {
371
- street: string;
372
- city: string;
373
- zipCode: string;
374
- }
316
+ class Money extends ValueObject<{ amount: number; currency: string }> {
317
+ protected static validation = {
318
+ schema: z.object({
319
+ amount: z.number().positive(),
320
+ currency: z.enum(["USD", "EUR", "BRL"]),
321
+ }),
322
+ };
375
323
 
376
- class Address extends ValueObject<AddressProps> {
377
- get street() {
378
- return this.props.street;
324
+ add(other: Money): Money {
325
+ if (this.props.currency !== other.props.currency) {
326
+ throw new DomainError("Cannot add different currencies");
327
+ }
328
+
329
+ return this.clone({
330
+ amount: this.props.amount + other.props.amount
331
+ });
379
332
  }
380
- get city() {
381
- return this.props.city;
333
+
334
+ get amount() {
335
+ return this.props.amount;
382
336
  }
383
337
 
384
- changeCity(newCity: string): Address {
385
- return this.clone({ city: newCity });
338
+ get currency() {
339
+ return this.props.currency;
386
340
  }
387
341
  }
388
342
 
389
- const addr1 = new Address({
390
- street: "Av. Paulista",
391
- city: "São Paulo",
392
- zipCode: "01310-100",
393
- });
343
+ const price1 = new Money({ amount: 100, currency: "USD" });
344
+ const price2 = new Money({ amount: 50, currency: "USD" });
345
+ const total = price1.add(price2);
394
346
 
395
- const addr2 = new Address({
396
- street: "Av. Paulista",
397
- city: "São Paulo",
398
- zipCode: "01310-100",
399
- });
400
-
401
- addr1.equals(addr2); // true (comparação por valor)
402
-
403
- const addr3 = addr1.changeCity("Rio de Janeiro");
404
- addr1.city; // São Paulo (imutável)
405
- addr3.city; // Rio de Janeiro
347
+ console.log(total.amount); // 150
406
348
  ```
407
349
 
408
- ## Sistema de IDs
409
-
410
- ```typescript
411
- import { Id } from "rich-domain";
412
-
413
- // Nova entidade - gera UUID
414
- const newId = new Id();
415
- console.log(newuser.isNew()); // true
350
+ ## Integration with ORMs
416
351
 
417
- // Entidade existente
418
- const existingId = new Id("user-123");
419
- console.log(existinguser.isNew()); // false
352
+ Rich Domain provides official adapters for popular ORMs:
420
353
 
421
- // Comparação
422
- newId.equals(existingId); // false
423
- existingId.equals("user-123"); // true
354
+ ### Prisma
424
355
 
425
- // Static methods
426
- const id1 = Id.create(); // Novo
427
- const id2 = Id.from("abc-123"); // Existente
356
+ ```bash
357
+ npm install @woltz/rich-domain-prisma
428
358
  ```
429
359
 
430
- ## Change Tracking & Subscriptions
431
-
432
360
  ```typescript
433
- const user = new User({
434
- name: "João",
435
- email: "joao@example.com",
436
- });
437
-
438
- // Subscribe
439
- user.subscribe({
440
- name: {
441
- onChange: ({ previous, current }) => {
442
- console.log(`Nome: ${previous} → ${current}`);
443
- },
444
- },
445
- });
361
+ import { PrismaRepository, PrismaToPersistence } from "@woltz/rich-domain-prisma";
362
+
363
+ class UserToPersistence extends PrismaToPersistence<User> {
364
+ protected readonly registry = schemaRegistry;
365
+
366
+ protected async onCreate(user: User): Promise<void> {
367
+ await this.context.user.create({
368
+ data: {
369
+ id: user.id.value,
370
+ email: user.email,
371
+ name: user.name,
372
+ },
373
+ });
374
+ }
446
375
 
447
- user.name = "Maria"; // Trigger onChange
376
+ protected async onUpdate(user: User, changes: AggregateChanges): Promise<void> {
377
+ // Automatic batch operations handling
378
+ }
379
+ }
380
+ ```
448
381
 
449
- // History
450
- const history = user.getHistory();
451
- // [{ path: 'name', previousValue: 'João', currentValue: 'Maria', timestamp: ... }]
382
+ ### TypeORM
452
383
 
453
- user.clearHistory();
384
+ ```bash
385
+ npm install @woltz/rich-domain-typeorm
454
386
  ```
455
387
 
456
- ## Domain Events
457
-
458
388
  ```typescript
459
- import { DomainEvent, DomainEventBus } from "rich-domain";
389
+ import { TypeORMRepository } from "@woltz/rich-domain-typeorm";
460
390
 
461
- // Definir evento
462
- class UserCreatedEvent extends DomainEvent {
463
- constructor(public readonly userId: Id, public readonly userName: string) {
464
- super("UserCreated", userId);
465
- }
391
+ class UserRepository extends TypeORMRepository<User, UserEntity> {
392
+ // Automatic change tracking and batch operations
466
393
  }
394
+ ```
467
395
 
468
- // Criar aggregate
469
- class User extends Aggregate<UserProps> {
470
- static create(props: Omit<UserProps, "id">) {
471
- const user = new User({ ...props, id: new Id() });
396
+ ## CLI Tool
472
397
 
473
- // Adicionar evento
474
- user.addDomainEvent(new UserCreatedEvent(user.id, user.name));
398
+ Bootstrap projects and generate domain code:
475
399
 
476
- return user;
477
- }
478
- }
400
+ ```bash
401
+ npm install -g @woltz/rich-domain-cli
479
402
 
480
- // Handler
481
- class SendWelcomeEmailHandler {
482
- async handle(event: UserCreatedEvent) {
483
- await sendEmail(event.userName);
484
- }
485
- }
403
+ # Initialize new project
404
+ rich-domain init my-app --template fullstack
486
405
 
487
- // Registrar handler
488
- const bus = DomainEventBus.getInstance();
489
- bus.subscribe(UserCreatedEvent, new SendWelcomeEmailHandler());
406
+ # Generate domain from Prisma schema
407
+ rich-domain generate --schema prisma/schema.prisma
490
408
 
491
- // Publicar eventos
492
- const user = User.create({ name: "João", email: "joao@example.com" });
493
- await user.dispatchAll(bus);
409
+ # Add entity manually
410
+ rich-domain add User name:string email:string --with-repo
494
411
  ```
495
412
 
496
- ## Validation
413
+ ## API Reference
497
414
 
498
- ### Com Throw
415
+ ### Core Classes
499
416
 
500
- ```typescript
501
- const user = new User({
502
- name: "J", // Muito curto
503
- email: "invalid",
504
- });
505
- // throws ValidationError
506
- ```
417
+ #### `Id`
507
418
 
508
- ### Sem Throw
419
+ Unique identifier for entities:
509
420
 
510
421
  ```typescript
511
- class UserSafe extends Aggregate<UserProps> {
512
- protected static validation = {
513
- schema: userSchema,
514
- config: { throwOnError: false },
515
- };
516
- }
517
-
518
- const user = new UserSafe({
519
- name: "J",
520
- email: "invalid",
521
- });
422
+ const id = Id.create(); // Generate new UUID
423
+ const existingId = Id.from("uuid-string"); // From existing value
522
424
 
523
- if (user.hasValidationErrors) {
524
- console.log(user.validationErrors!.getMessages());
525
- // ['Nome deve ter pelo menos 2 caracteres', 'Email inválido']
526
- }
425
+ console.log(id.value); // string
426
+ console.log(id.isNew); // boolean
427
+ console.log(id.equals(otherId)); // boolean
527
428
  ```
528
429
 
529
- ## Compatibilidade Standard Schema
430
+ #### `Entity<T>`
530
431
 
531
- ### Zod
432
+ Base class for entities:
532
433
 
533
434
  ```typescript
534
- import { z } from "zod";
535
- const schema = z.object({ ... });
435
+ abstract class Entity<T extends { id: Id }> {
436
+ get id(): Id;
437
+ get isNew(): boolean;
438
+ equals(other: Entity<T>): boolean;
439
+ toJSON(): object;
440
+ }
536
441
  ```
537
442
 
538
- ### Valibot
539
-
540
- ```typescript
541
- import * as v from "valibot";
542
- const schema = v.object({ ... });
543
- ```
443
+ #### `Aggregate<T>`
544
444
 
545
- ### ArkType
445
+ Root entity with change tracking:
546
446
 
547
447
  ```typescript
548
- import { type } from "arktype";
549
- const schema = type({ ... });
448
+ abstract class Aggregate<T extends { id: Id }> extends Entity<T> {
449
+ getChanges(): AggregateChanges;
450
+
451
+ // Domain Events
452
+ protected addDomainEvent(event: IDomainEvent): void;
453
+ getUncommittedEvents(): IDomainEvent[];
454
+ clearEvents(): void;
455
+ dispatchAll(bus: DomainEventBus): Promise<void>;
456
+ }
550
457
  ```
551
458
 
552
- ## Estrutura de Arquivos
459
+ #### `ValueObject<T>`
553
460
 
554
- Para exemplos completos, veja:
555
-
556
- - `src/repository/examples/prisma-repository.example.ts` - Implementação Prisma completa
557
- - `src/repository/examples/README.md` - Documentação detalhada
558
- - `tests/` - Testes completos de todos os recursos
559
-
560
- ## API Reference
561
-
562
- ### Repository
461
+ Immutable object compared by value:
563
462
 
564
463
  ```typescript
565
- interface IRepository<TDomain> {
566
- findById(id: Id): Promise<TDomain | null>;
567
- find(criteria: Criteria<TDomain>): Promise<PaginatedResult<TDomain>>;
568
- findAll(criteria?: Criteria<TDomain>): Promise<TDomain[]>;
569
- findOne(criteria: Criteria<TDomain>): Promise<TDomain | null>;
570
- save(aggregate: TDomain): Promise<void>;
571
- saveMany(aggregates: TDomain[]): Promise<void>;
572
- delete(aggregate: TDomain): Promise<void>;
573
- deleteById(id: Id): Promise<void>;
574
- exists(id: Id): Promise<boolean>;
575
- count(criteria?: Criteria<TDomain>): Promise<number>;
464
+ abstract class ValueObject<T> {
465
+ protected readonly props: T;
466
+
467
+ equals(other: ValueObject<T>): boolean;
468
+ toJSON(): T;
469
+ protected clone(updates: Partial<T>): this;
576
470
  }
577
471
  ```
578
472
 
579
- ### Criteria
473
+ ### Criteria API
580
474
 
581
475
  ```typescript
582
476
  class Criteria<T> {
583
477
  static create<T>(): Criteria<T>;
584
-
478
+
585
479
  // Filters
586
- where(field, operator, value?): this;
587
- whereEquals(field, value): this;
588
- whereContains(field, value): this;
589
- whereIn(field, values): this;
590
- whereBetween(field, min, max): this;
591
- whereNull(field): this;
592
- whereNotNull(field): this;
593
-
480
+ where<K extends FieldPath<T>>(
481
+ field: K,
482
+ operator: FilterOperator,
483
+ value: any
484
+ ): this;
485
+
594
486
  // Ordering
595
- orderBy(field, direction?): this;
596
- orderByAsc(field): this;
597
- orderByDesc(field): this;
598
-
487
+ orderBy<K extends FieldPath<T>>(
488
+ field: K,
489
+ direction: "asc" | "desc"
490
+ ): this;
491
+
599
492
  // Pagination
600
- paginate(page, limit): this;
601
- limit(limit): this;
602
-
493
+ limit(limit: number): this;
494
+ offset(offset: number): this;
495
+ paginate(page: number, pageSize: number): this;
496
+
603
497
  // Search
604
- search(fields, value): this;
605
-
606
- // Utils
607
- clone(): Criteria<T>;
608
- toJSON(): object;
609
-
610
- static fromObject<T>(obj): Criteria<T>;
611
- static fromQueryParams<T>(query): Criteria<T>;
498
+ search(term: string): this;
499
+
500
+ // Getters
501
+ getFilters(): Filter<T>[];
502
+ getOrdering(): Order<T> | null;
503
+ getPagination(): Pagination | null;
504
+ getSearch(): string | null;
612
505
  }
613
506
  ```
614
507
 
615
- ### PaginatedResult
508
+ ### Exception Handling
616
509
 
617
- ```typescript
618
- class PaginatedResult<T> {
619
- readonly data: T[];
620
- readonly meta: PaginationMeta;
621
-
622
- static create<T>(data, pagination, total): PaginatedResult<T>;
623
- static createMeta(pagination, total): PaginationMeta;
624
- static fromArray<T>(items, criteria): PaginatedResult<T>;
625
-
626
- toJSON(): PaginatedJsonResult<T>; // Deep serialization
627
- map<U>(fn): PaginatedResult<U>;
628
-
629
- get isEmpty(): boolean;
630
- get hasMore(): boolean;
631
- }
632
- ```
633
-
634
- ### Id
510
+ Rich Domain provides comprehensive exception types:
635
511
 
636
512
  ```typescript
637
- class Id {
638
- constructor(value?: string);
639
-
640
- get value(): string;
641
- get isNew(): boolean;
513
+ import {
514
+ ValidationError,
515
+ DomainError,
516
+ EntityNotFoundError,
517
+ DuplicateEntityError,
518
+ ConcurrencyError,
519
+ RepositoryError
520
+ } from "@woltz/rich-domain";
642
521
 
643
- toString(): string;
644
- toJSON(): string;
645
- equals(other: Id | string): boolean;
646
-
647
- static create(): Id;
648
- static from(value: string): Id;
522
+ try {
523
+ const user = new User({ /* invalid props */ });
524
+ } catch (error) {
525
+ if (error instanceof ValidationError) {
526
+ console.log(error.entity); // "User"
527
+ console.log(error.field); // "email"
528
+ console.log(error.message); // "Invalid email format"
529
+ }
649
530
  }
650
531
  ```
651
532
 
652
- ### BaseEntity
533
+ ## Package Format
653
534
 
654
- ```typescript
655
- abstract class BaseEntity<T extends BaseProps> {
656
- get id(): Id;
657
- get isNew(): boolean;
658
- get hasValidationErrors(): boolean;
659
- get validationErrors(): ValidationError | undefined;
535
+ This library is published as a **dual package** supporting both CommonJS and ES Modules:
660
536
 
661
- subscribe(config: SubscriptionConfig<T>): void;
662
- getHistory(): HistoryEntry[];
663
- clearHistory(): void;
664
- toJson(): DeepJsonResult<T>;
537
+ ```javascript
538
+ // CommonJS
539
+ const { Id, Entity, Aggregate } = require('@woltz/rich-domain');
665
540
 
666
- // Domain Events
667
- protected addDomainEvent(event: IDomainEvent): void;
668
- getUncommittedEvents(): IDomainEvent[];
669
- clearEvents(): void;
670
- async dispatchAll(bus: DomainEventBus): Promise<void>;
671
- }
541
+ // ES Modules
542
+ import { Id, Entity, Aggregate } from '@woltz/rich-domain';
672
543
  ```
673
544
 
674
- ### ValueObject
545
+ Benefits:
546
+ - ✅ Universal compatibility (Node.js, Vite, Webpack, etc.)
547
+ - ✅ Tree-shaking support for modern bundlers
548
+ - ✅ Full TypeScript support with type definitions
549
+ - ✅ Zero configuration - automatically uses the correct format
675
550
 
676
- ```typescript
677
- abstract class ValueObject<T> {
678
- protected readonly props: T;
551
+ ## Documentation
679
552
 
680
- equals(other: ValueObject<T>): boolean;
681
- toJson(): T;
682
- protected clone(updates: Partial<T>): this;
683
- }
684
- ```
685
-
686
- ## Testing
553
+ - 📚 [Full Documentation](https://woltz.mintlify.app)
554
+ - 🚀 [Quick Start Guide](https://woltz.mintlify.app/quickstart)
555
+ - 📖 [Core Concepts](https://woltz.mintlify.app/core/entities-and-aggregates)
556
+ - 🔌 [Integrations](https://woltz.mintlify.app/integrations/prisma)
557
+ - ⚛️ [React Components](https://woltz.mintlify.app/integrations/react)
687
558
 
688
- ```typescript
689
- import { InMemoryRepository } from "rich-domain";
559
+ ## Examples
690
560
 
691
- describe("UserService", () => {
692
- const userRepo = new InMemoryRepository<User>();
693
- const service = new UserService(userRepo);
561
+ Check out the [examples directory](./examples) for complete implementations:
694
562
 
695
- beforeEach(() => userRepo.clear());
696
-
697
- it("should create user", async () => {
698
- const user = await service.createUser({
699
- name: "João",
700
- email: "joao@example.com",
701
- });
702
-
703
- expect(user.user.isNew()).toBe(false);
704
- expect(await userRepo.exists(user.id)).toBe(true);
705
- });
706
- });
707
- ```
563
+ - Basic CRUD operations
564
+ - Complex aggregate relationships
565
+ - Custom validation rules
566
+ - Domain events
567
+ - Repository implementations for different ORMs
708
568
 
709
- ## Roadmap
569
+ ## Ecosystem
710
570
 
711
- - [ ] TypeORM repository example
712
- - [ ] MongoDB repository example
713
- - [ ] Drizzle ORM repository example
714
- - [ ] GraphQL integration utilities
715
- - [ ] Advanced caching strategies
571
+ | Package | Description | Version |
572
+ |---------|-------------|---------|
573
+ | [@woltz/rich-domain](https://www.npmjs.com/package/@woltz/rich-domain) | Core library | [![npm](https://img.shields.io/npm/v/@woltz/rich-domain.svg)](https://www.npmjs.com/package/@woltz/rich-domain) |
574
+ | [@woltz/rich-domain-prisma](https://www.npmjs.com/package/@woltz/rich-domain-prisma) | Prisma adapter | [![npm](https://img.shields.io/npm/v/@woltz/rich-domain-prisma.svg)](https://www.npmjs.com/package/@woltz/rich-domain-prisma) |
575
+ | [@woltz/rich-domain-typeorm](https://www.npmjs.com/package/@woltz/rich-domain-typeorm) | TypeORM adapter | [![npm](https://img.shields.io/npm/v/@woltz/rich-domain-typeorm.svg)](https://www.npmjs.com/package/@woltz/rich-domain-typeorm) |
576
+ | [@woltz/rich-domain-criteria-zod](https://www.npmjs.com/package/@woltz/rich-domain-criteria-zod) | Zod criteria builder | [![npm](https://img.shields.io/npm/v/@woltz/rich-domain-criteria-zod.svg)](https://www.npmjs.com/package/@woltz/rich-domain-criteria-zod) |
577
+ | [@woltz/rich-domain-cli](https://www.npmjs.com/package/@woltz/rich-domain-cli) | CLI tool | [![npm](https://img.shields.io/npm/v/@woltz/rich-domain-cli.svg)](https://www.npmjs.com/package/@woltz/rich-domain-cli) |
716
578
 
717
- ## Contribuindo
579
+ ## Contributing
718
580
 
719
- Contribuições são bem-vindas! Veja [CONTRIBUTING.md](CONTRIBUTING.md) para detalhes.
581
+ Contributions are welcome! Please read our [Contributing Guide](./CONTRIBUTING.md) for details.
720
582
 
721
- ## Licença
583
+ ## License
722
584
 
723
- MIT
585
+ MIT © [Tarcisio Andrade](https://github.com/tarcisioandrade)
724
586
 
725
587
  ## Links
726
588
 
727
- - [Documentação Completa](https://github.com/yourusername/rich-domain)
728
- - [Exemplos](./src/repository/examples)
729
- - [Issues](https://github.com/yourusername/rich-domain/issues)
730
-
731
- ## Package Formats
732
-
733
- This library is published as a **dual package** supporting both CommonJS and ES Modules:
734
-
735
- ### CommonJS (Node.js)
736
- ```javascript
737
- const { Id, Entity, Aggregate } = require('@woltz/rich-domain');
738
- ```
739
-
740
- ### ES Modules (Modern bundlers & Node.js with ESM)
741
- ```javascript
742
- import { Id, Entity, Aggregate } from '@woltz/rich-domain';
743
- ```
744
-
745
- ### Benefits
746
- - ✅ **Universal compatibility**: Works in any Node.js environment
747
- - ✅ **Tree-shaking**: Modern bundlers (Vite, Rollup, Webpack 5+) can eliminate unused code
748
- - ✅ **TypeScript support**: Full type definitions included
749
- - ✅ **Zero configuration**: Automatically uses the correct format for your environment
750
-
589
+ - [Documentation](https://woltz.mintlify.app)
590
+ - [GitHub Repository](https://github.com/tarcisioandrade/rich-domain)
591
+ - [npm Package](https://www.npmjs.com/package/@woltz/rich-domain)
592
+ - [Issues](https://github.com/tarcisioandrade/rich-domain/issues)
593
+ - [Changelog](./CHANGELOG.md)