@woltz/rich-domain 1.4.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.
- package/README.md +426 -583
- package/dist/cjs/base-entity.d.ts +2 -3
- package/dist/cjs/base-entity.d.ts.map +1 -1
- package/dist/cjs/base-entity.js.map +1 -1
- package/dist/cjs/domain-event.d.ts +10 -12
- package/dist/cjs/domain-event.d.ts.map +1 -1
- package/dist/cjs/domain-event.js +5 -16
- package/dist/cjs/domain-event.js.map +1 -1
- package/dist/cjs/index.d.ts +1 -2
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +0 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/repository/base-repository.d.ts +2 -2
- package/dist/cjs/types/domain-event.d.ts +7 -13
- package/dist/cjs/types/domain-event.d.ts.map +1 -1
- package/dist/cjs/types/event-bus.d.ts +16 -0
- package/dist/cjs/types/event-bus.d.ts.map +1 -0
- package/dist/cjs/types/event-bus.js +3 -0
- package/dist/cjs/types/event-bus.js.map +1 -0
- package/dist/cjs/types/index.d.ts +1 -0
- package/dist/cjs/types/index.d.ts.map +1 -1
- package/dist/cjs/types/index.js +1 -0
- package/dist/cjs/types/index.js.map +1 -1
- package/dist/esm/base-entity.d.ts +2 -3
- package/dist/esm/base-entity.d.ts.map +1 -1
- package/dist/esm/base-entity.js.map +1 -1
- package/dist/esm/domain-event.d.ts +10 -12
- package/dist/esm/domain-event.d.ts.map +1 -1
- package/dist/esm/domain-event.js +5 -16
- package/dist/esm/domain-event.js.map +1 -1
- package/dist/esm/index.d.ts +1 -2
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +0 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/repository/base-repository.d.ts +2 -2
- package/dist/esm/types/domain-event.d.ts +7 -13
- package/dist/esm/types/domain-event.d.ts.map +1 -1
- package/dist/esm/types/event-bus.d.ts +16 -0
- package/dist/esm/types/event-bus.d.ts.map +1 -0
- package/dist/esm/types/event-bus.js +2 -0
- package/dist/esm/types/event-bus.js.map +1 -0
- package/dist/esm/types/index.d.ts +1 -0
- package/dist/esm/types/index.d.ts.map +1 -1
- package/dist/esm/types/index.js +1 -0
- package/dist/esm/types/index.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/base-entity.d.ts +2 -3
- package/dist/types/base-entity.d.ts.map +1 -1
- package/dist/types/domain-event.d.ts +10 -12
- package/dist/types/domain-event.d.ts.map +1 -1
- package/dist/types/index.d.ts +1 -2
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/repository/base-repository.d.ts +2 -2
- package/dist/types/types/domain-event.d.ts +7 -13
- package/dist/types/types/domain-event.d.ts.map +1 -1
- package/dist/types/types/event-bus.d.ts +16 -0
- package/dist/types/types/event-bus.d.ts.map +1 -0
- package/dist/types/types/index.d.ts +1 -0
- package/dist/types/types/index.d.ts.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,750 +1,593 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @woltz/rich-domain
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A TypeScript library for Domain-Driven Design with Standard Schema validation, automatic change tracking, and enterprise-ready repositories.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
[](https://www.npmjs.com/package/@woltz/rich-domain)
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
6
7
|
|
|
7
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
-
//
|
|
49
|
-
const
|
|
50
|
-
id: z.custom<Id>(
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
-
|
|
58
|
-
|
|
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
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
console.log(`Usuário criado: ${entity.name}`);
|
|
73
|
-
},
|
|
62
|
+
publish(): void {
|
|
63
|
+
this.props.published = true;
|
|
64
|
+
}
|
|
74
65
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
}
|
|
80
|
-
return true;
|
|
81
|
-
},
|
|
66
|
+
get title() {
|
|
67
|
+
return this.props.title;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
82
70
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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
|
-
|
|
108
|
-
this.props.
|
|
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.
|
|
104
|
+
### 2. Automatic Change Tracking
|
|
117
105
|
|
|
118
|
-
|
|
106
|
+
Changes are tracked automatically - no manual tracking needed:
|
|
119
107
|
|
|
120
108
|
```typescript
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
const userRepo = new InMemoryRepository<User>();
|
|
124
|
-
|
|
125
|
-
// Salvar
|
|
109
|
+
// Load existing user
|
|
126
110
|
const user = new User({
|
|
127
|
-
|
|
128
|
-
email: "
|
|
129
|
-
|
|
130
|
-
|
|
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
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
//
|
|
150
|
-
|
|
151
|
-
|
|
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
|
-
|
|
148
|
+
### 3. Type-Safe Queries with Criteria
|
|
155
149
|
|
|
156
|
-
|
|
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
|
-
|
|
172
|
-
|
|
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
|
-
//
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
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
|
-
|
|
189
|
-
|
|
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
|
-
|
|
193
|
-
|
|
194
|
-
|
|
169
|
+
// Use with repository
|
|
170
|
+
const result = await userRepository.find(criteria);
|
|
171
|
+
// result: PaginatedResult<User>
|
|
195
172
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
173
|
+
console.log(result.data); // User[]
|
|
174
|
+
console.log(result.meta); // { page: 1, pageSize: 20, total: 100, totalPages: 5 }
|
|
175
|
+
```
|
|
199
176
|
|
|
200
|
-
|
|
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
|
-
|
|
206
|
-
const persistence = await this.prisma.user.findMany();
|
|
207
|
-
return this.mapper.toDomainList!(persistence);
|
|
208
|
-
}
|
|
179
|
+
Abstract your persistence layer:
|
|
209
180
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
const orderBy = this.buildOrderBy(criteria);
|
|
213
|
-
const pagination = criteria.getPagination();
|
|
181
|
+
```typescript
|
|
182
|
+
import { IRepository, Criteria } from "@woltz/rich-domain";
|
|
214
183
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
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
|
-
|
|
229
|
-
|
|
230
|
-
|
|
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
|
-
|
|
234
|
-
|
|
235
|
-
return count > 0;
|
|
210
|
+
async findById(id: string): Promise<User | null> {
|
|
211
|
+
// Your query logic
|
|
236
212
|
}
|
|
237
213
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
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
|
-
|
|
244
|
-
|
|
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
|
-
|
|
261
|
-
|
|
262
|
-
```typescript
|
|
263
|
-
import { Criteria } from "rich-domain";
|
|
233
|
+
## Advanced Features
|
|
264
234
|
|
|
265
|
-
|
|
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
|
-
|
|
237
|
+
Add validation and side effects at key points:
|
|
307
238
|
|
|
308
239
|
```typescript
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
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
|
-
###
|
|
267
|
+
### Domain Events
|
|
268
|
+
|
|
269
|
+
Communicate across aggregate boundaries:
|
|
336
270
|
|
|
337
271
|
```typescript
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
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
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
//
|
|
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
|
-
//
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
303
|
+
// After saving
|
|
304
|
+
await orderRepository.save(order);
|
|
305
|
+
await order.dispatchAll(eventBus);
|
|
306
|
+
order.clearEvents();
|
|
363
307
|
```
|
|
364
308
|
|
|
365
|
-
|
|
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
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
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
|
-
|
|
377
|
-
|
|
378
|
-
|
|
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
|
-
|
|
381
|
-
|
|
333
|
+
|
|
334
|
+
get amount() {
|
|
335
|
+
return this.props.amount;
|
|
382
336
|
}
|
|
383
337
|
|
|
384
|
-
|
|
385
|
-
return this.
|
|
338
|
+
get currency() {
|
|
339
|
+
return this.props.currency;
|
|
386
340
|
}
|
|
387
341
|
}
|
|
388
342
|
|
|
389
|
-
const
|
|
390
|
-
|
|
391
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
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
|
-
|
|
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
|
-
|
|
422
|
-
newId.equals(existingId); // false
|
|
423
|
-
existingId.equals("user-123"); // true
|
|
354
|
+
### Prisma
|
|
424
355
|
|
|
425
|
-
|
|
426
|
-
|
|
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
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
user.
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
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
|
|
376
|
+
protected async onUpdate(user: User, changes: AggregateChanges): Promise<void> {
|
|
377
|
+
// Automatic batch operations handling
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
```
|
|
448
381
|
|
|
449
|
-
|
|
450
|
-
const history = user.getHistory();
|
|
451
|
-
// [{ path: 'name', previousValue: 'João', currentValue: 'Maria', timestamp: ... }]
|
|
382
|
+
### TypeORM
|
|
452
383
|
|
|
453
|
-
|
|
384
|
+
```bash
|
|
385
|
+
npm install @woltz/rich-domain-typeorm
|
|
454
386
|
```
|
|
455
387
|
|
|
456
|
-
## Domain Events
|
|
457
|
-
|
|
458
388
|
```typescript
|
|
459
|
-
import {
|
|
389
|
+
import { TypeORMRepository } from "@woltz/rich-domain-typeorm";
|
|
460
390
|
|
|
461
|
-
|
|
462
|
-
|
|
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
|
-
|
|
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
|
-
|
|
474
|
-
user.addDomainEvent(new UserCreatedEvent(user.id, user.name));
|
|
398
|
+
Bootstrap projects and generate domain code:
|
|
475
399
|
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
}
|
|
400
|
+
```bash
|
|
401
|
+
npm install -g @woltz/rich-domain-cli
|
|
479
402
|
|
|
480
|
-
|
|
481
|
-
|
|
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
|
-
|
|
488
|
-
|
|
489
|
-
bus.subscribe(UserCreatedEvent, new SendWelcomeEmailHandler());
|
|
406
|
+
# Generate domain from Prisma schema
|
|
407
|
+
rich-domain generate --schema prisma/schema.prisma
|
|
490
408
|
|
|
491
|
-
|
|
492
|
-
|
|
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
|
-
##
|
|
413
|
+
## API Reference
|
|
497
414
|
|
|
498
|
-
###
|
|
415
|
+
### Core Classes
|
|
499
416
|
|
|
500
|
-
|
|
501
|
-
const user = new User({
|
|
502
|
-
name: "J", // Muito curto
|
|
503
|
-
email: "invalid",
|
|
504
|
-
});
|
|
505
|
-
// throws ValidationError
|
|
506
|
-
```
|
|
417
|
+
#### `Id`
|
|
507
418
|
|
|
508
|
-
|
|
419
|
+
Unique identifier for entities:
|
|
509
420
|
|
|
510
421
|
```typescript
|
|
511
|
-
|
|
512
|
-
|
|
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
|
-
|
|
524
|
-
|
|
525
|
-
|
|
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
|
-
|
|
430
|
+
#### `Entity<T>`
|
|
530
431
|
|
|
531
|
-
|
|
432
|
+
Base class for entities:
|
|
532
433
|
|
|
533
434
|
```typescript
|
|
534
|
-
|
|
535
|
-
|
|
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
|
-
|
|
539
|
-
|
|
540
|
-
```typescript
|
|
541
|
-
import * as v from "valibot";
|
|
542
|
-
const schema = v.object({ ... });
|
|
543
|
-
```
|
|
443
|
+
#### `Aggregate<T>`
|
|
544
444
|
|
|
545
|
-
|
|
445
|
+
Root entity with change tracking:
|
|
546
446
|
|
|
547
447
|
```typescript
|
|
548
|
-
|
|
549
|
-
|
|
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
|
-
|
|
459
|
+
#### `ValueObject<T>`
|
|
553
460
|
|
|
554
|
-
|
|
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
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
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
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
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
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
487
|
+
orderBy<K extends FieldPath<T>>(
|
|
488
|
+
field: K,
|
|
489
|
+
direction: "asc" | "desc"
|
|
490
|
+
): this;
|
|
491
|
+
|
|
599
492
|
// Pagination
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
493
|
+
limit(limit: number): this;
|
|
494
|
+
offset(offset: number): this;
|
|
495
|
+
paginate(page: number, pageSize: number): this;
|
|
496
|
+
|
|
603
497
|
// Search
|
|
604
|
-
search(
|
|
605
|
-
|
|
606
|
-
//
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
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
|
-
###
|
|
508
|
+
### Exception Handling
|
|
616
509
|
|
|
617
|
-
|
|
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
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
513
|
+
import {
|
|
514
|
+
ValidationError,
|
|
515
|
+
DomainError,
|
|
516
|
+
EntityNotFoundError,
|
|
517
|
+
DuplicateEntityError,
|
|
518
|
+
ConcurrencyError,
|
|
519
|
+
RepositoryError
|
|
520
|
+
} from "@woltz/rich-domain";
|
|
642
521
|
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
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
|
-
|
|
533
|
+
## Package Format
|
|
653
534
|
|
|
654
|
-
|
|
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
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
toJson(): DeepJsonResult<T>;
|
|
537
|
+
```javascript
|
|
538
|
+
// CommonJS
|
|
539
|
+
const { Id, Entity, Aggregate } = require('@woltz/rich-domain');
|
|
665
540
|
|
|
666
|
-
|
|
667
|
-
|
|
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
|
-
|
|
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
|
-
|
|
677
|
-
abstract class ValueObject<T> {
|
|
678
|
-
protected readonly props: T;
|
|
551
|
+
## Documentation
|
|
679
552
|
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
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
|
-
|
|
689
|
-
import { InMemoryRepository } from "rich-domain";
|
|
559
|
+
## Examples
|
|
690
560
|
|
|
691
|
-
|
|
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
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
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
|
-
##
|
|
569
|
+
## Ecosystem
|
|
710
570
|
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
571
|
+
| Package | Description | Version |
|
|
572
|
+
|---------|-------------|---------|
|
|
573
|
+
| [@woltz/rich-domain](https://www.npmjs.com/package/@woltz/rich-domain) | Core library | [](https://www.npmjs.com/package/@woltz/rich-domain) |
|
|
574
|
+
| [@woltz/rich-domain-prisma](https://www.npmjs.com/package/@woltz/rich-domain-prisma) | Prisma adapter | [](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 | [](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 | [](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 | [](https://www.npmjs.com/package/@woltz/rich-domain-cli) |
|
|
716
578
|
|
|
717
|
-
##
|
|
579
|
+
## Contributing
|
|
718
580
|
|
|
719
|
-
|
|
581
|
+
Contributions are welcome! Please read our [Contributing Guide](./CONTRIBUTING.md) for details.
|
|
720
582
|
|
|
721
|
-
##
|
|
583
|
+
## License
|
|
722
584
|
|
|
723
|
-
MIT
|
|
585
|
+
MIT © [Tarcisio Andrade](https://github.com/tarcisioandrade)
|
|
724
586
|
|
|
725
587
|
## Links
|
|
726
588
|
|
|
727
|
-
- [
|
|
728
|
-
- [
|
|
729
|
-
- [
|
|
730
|
-
|
|
731
|
-
|
|
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)
|