drimion 0.1.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/LICENSE +7 -0
- package/README.md +955 -0
- package/dist/cli/index.js +1045 -0
- package/dist/cli/templates/aggregate.ts.hbs +22 -0
- package/dist/cli/templates/entity.ts.hbs +16 -0
- package/dist/cli/templates/repository.ts.hbs +24 -0
- package/dist/cli/templates/use-case.ts.hbs +20 -0
- package/dist/cli/templates/value-object.ts.hbs +16 -0
- package/dist/kernel/core/aggregate.ts +234 -0
- package/dist/kernel/core/entity.ts +348 -0
- package/dist/kernel/core/id.ts +207 -0
- package/dist/kernel/core/index.ts +5 -0
- package/dist/kernel/core/repository.ts +81 -0
- package/dist/kernel/core/value-object.ts +309 -0
- package/dist/kernel/events/browser-event-manager.ts +241 -0
- package/dist/kernel/events/domain-event.ts +76 -0
- package/dist/kernel/events/event-bus.ts +158 -0
- package/dist/kernel/events/event-context.ts +95 -0
- package/dist/kernel/events/event-manager.ts +20 -0
- package/dist/kernel/events/event-utils.ts +19 -0
- package/dist/kernel/events/index.ts +7 -0
- package/dist/kernel/events/server-event-manager.ts +169 -0
- package/dist/kernel/helpers/auto-mapper.ts +222 -0
- package/dist/kernel/helpers/domain-classes.ts +162 -0
- package/dist/kernel/helpers/domain-error.ts +52 -0
- package/dist/kernel/helpers/getters-setters.ts +385 -0
- package/dist/kernel/helpers/index.ts +7 -0
- package/dist/kernel/index.ts +73 -0
- package/dist/kernel/libs/crypto.ts +33 -0
- package/dist/kernel/libs/index.ts +5 -0
- package/dist/kernel/libs/iterator.ts +298 -0
- package/dist/kernel/libs/result.ts +252 -0
- package/dist/kernel/libs/utils.ts +260 -0
- package/dist/kernel/libs/validator.ts +353 -0
- package/dist/kernel/types/adapter.types.ts +26 -0
- package/dist/kernel/types/command.types.ts +37 -0
- package/dist/kernel/types/entity.types.ts +60 -0
- package/dist/kernel/types/event.types.ts +129 -0
- package/dist/kernel/types/index.ts +26 -0
- package/dist/kernel/types/iterator.types.ts +39 -0
- package/dist/kernel/types/result.types.ts +122 -0
- package/dist/kernel/types/uid.types.ts +18 -0
- package/dist/kernel/types/utils.types.ts +120 -0
- package/dist/kernel/types/value-object.types.ts +20 -0
- package/dist/kernel/utils/date.utils.ts +111 -0
- package/dist/kernel/utils/index.ts +32 -0
- package/dist/kernel/utils/number.utils.ts +341 -0
- package/dist/kernel/utils/object.utils.ts +61 -0
- package/dist/kernel/utils/string.utils.ts +128 -0
- package/dist/kernel/utils/type.utils.ts +33 -0
- package/package.json +59 -0
package/README.md
ADDED
|
@@ -0,0 +1,955 @@
|
|
|
1
|
+
# Drimion
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+

|
|
5
|
+
|
|
6
|
+
**Headless DDD primitives CLI for TypeScript**
|
|
7
|
+
|
|
8
|
+
A developer-first CLI tool for bringing Domain-Driven Design (DDD) primitives into any TypeScript project — copy the primitives, shape your architecture, and keep full control.
|
|
9
|
+
|
|
10
|
+
> ✅ No runtime dependency — everything is copied into your codebase. **The code is yours.**
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Table of Contents
|
|
15
|
+
|
|
16
|
+
- [Philosophy](#philosophy)
|
|
17
|
+
- [Why This Library?](#why-this-library)
|
|
18
|
+
- [Domain Concepts](#domain-concepts)
|
|
19
|
+
- [Domain Layers](#domain-layers)
|
|
20
|
+
- [Primitives at a Glance](#primitives-at-a-glance)
|
|
21
|
+
- [Getting Started](#getting-started)
|
|
22
|
+
- [Initial Setup](#initial-setup)
|
|
23
|
+
- [Configuration File](#configuration-file)
|
|
24
|
+
- [Recommended Structure](#recommended-structure)
|
|
25
|
+
- [Core Features](#core-features)
|
|
26
|
+
- [ID](#id)
|
|
27
|
+
- [Value Object](#value-object)
|
|
28
|
+
- [Entity](#entity)
|
|
29
|
+
- [Aggregate](#aggregate)
|
|
30
|
+
- [Repository](#repository)
|
|
31
|
+
- [Use Cases](#use-cases)
|
|
32
|
+
- [API Reference](#api-reference)
|
|
33
|
+
- [Core API](#core-api)
|
|
34
|
+
- [Helpers](#helpers)
|
|
35
|
+
- [Event System](#event-system)
|
|
36
|
+
- [CLI Reference](#cli-reference)
|
|
37
|
+
- [info](#info)
|
|
38
|
+
- [init](#init)
|
|
39
|
+
- [list](#list)
|
|
40
|
+
- [generate](#generate)
|
|
41
|
+
- [sync](#sync)
|
|
42
|
+
- [uninstall](#uninstall)
|
|
43
|
+
- [Roadmap](#roadmap)
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Philosophy
|
|
48
|
+
|
|
49
|
+
`drimion` does **NOT**:
|
|
50
|
+
|
|
51
|
+
- ❌ Enforce folder structure
|
|
52
|
+
- ❌ Dictate architecture (layered, modular, etc.)
|
|
53
|
+
- ❌ Lock you into patterns
|
|
54
|
+
|
|
55
|
+
Instead, it:
|
|
56
|
+
|
|
57
|
+
- ✅ Provides DDD building blocks (`Entity`, `Value Object`, `Aggregate`, etc.)
|
|
58
|
+
- ✅ Generates boilerplate **you can fully control**
|
|
59
|
+
- ✅ Copies source code **directly into your project**
|
|
60
|
+
|
|
61
|
+
> 💡 After installation — **the code is yours.**
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## Why This Library?
|
|
66
|
+
|
|
67
|
+
| Problem | Solution |
|
|
68
|
+
| --------------------------------------------------------- | ------------------------------------------------------------------------------ |
|
|
69
|
+
| Anemic domain models with no business logic | Rich `Entity` / `Aggregate` / `ValueObject` base classes |
|
|
70
|
+
| Throwing exceptions for validation failures | `Result<T>` monad — explicit success / error without try-catch |
|
|
71
|
+
| No standard way to track domain mutations | `Aggregate.emit()` + `pullEvents()` lifecycle |
|
|
72
|
+
| Boilerplate getters/setters with no invariant enforcement | `get()`, `set().to()`, `change()` with built-in validation hooks |
|
|
73
|
+
| Unstructured runtime errors with no domain context | First-class `DomainError` with `field` and `context` metadata |
|
|
74
|
+
| Serialization of nested domain objects | `toObject()` + `AutoMapper` |
|
|
75
|
+
| Event system tied to your domain model | Portable Event System — swap `EventBus`, use Redis, Kafka, BullMQ, or anything |
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## Domain Concepts
|
|
80
|
+
|
|
81
|
+
### Domain Layers
|
|
82
|
+
|
|
83
|
+
Domain-Driven Design organizes code into layers of responsibility. This library provides the building blocks for the **Domain Layer** and defines the contracts for the **Application** and **Infrastructure** layers.
|
|
84
|
+
|
|
85
|
+
```
|
|
86
|
+
┌─────────────────────────────────────────────────┐
|
|
87
|
+
│ Application Layer │
|
|
88
|
+
│ Use Cases · Commands · Queries · Event Handlers │
|
|
89
|
+
├──────────────────────────────────────────────────┤
|
|
90
|
+
│ Domain Layer │
|
|
91
|
+
│ Aggregates · Entities · Value Objects · Events │
|
|
92
|
+
│ Repositories (contracts) · Domain Services │
|
|
93
|
+
├──────────────────────────────────────────────────┤
|
|
94
|
+
│ Infrastructure Layer │
|
|
95
|
+
│ Repository Implementations · Adapters │
|
|
96
|
+
│ Event Bus / Message Queue / DB Drivers │
|
|
97
|
+
└──────────────────────────────────────────────────┘
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Key DDD principles this library embraces:
|
|
101
|
+
|
|
102
|
+
- **Rich domain models** — behavior lives inside domain classes, not in scattered services
|
|
103
|
+
- **Ubiquitous language** — class names and method names reflect your business domain
|
|
104
|
+
- **Bounded context** — each module is self-contained; entities in different contexts can share a name but have different behavior
|
|
105
|
+
- **Single responsibility** — non-cohesive behavior is delegated to other classes or raised as domain events
|
|
106
|
+
|
|
107
|
+
### Primitives at a Glance
|
|
108
|
+
|
|
109
|
+
| Primitive | Identity | Mutable | Emits Events | Purpose |
|
|
110
|
+
| ------------- | ----------- | ------------ | ------------ | ------------------------------------------------------ |
|
|
111
|
+
| `ValueObject` | ❌ By value | ❌ Immutable | ❌ | Domain concept defined entirely by its properties |
|
|
112
|
+
| `Entity` | ✅ By `id` | ✅ | ❌ | Domain object with stable identity and lifecycle |
|
|
113
|
+
| `Aggregate` | ✅ By `id` | ✅ | ✅ | Consistency boundary; single entry point for mutations |
|
|
114
|
+
| `ID` | — | — | — | Typed unique identifier (UUID or custom) |
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## Getting Started
|
|
119
|
+
|
|
120
|
+
### Initial Setup
|
|
121
|
+
|
|
122
|
+
Run directly with your preferred package manager — **no global install needed**:
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
# npm
|
|
126
|
+
npx drimion init
|
|
127
|
+
|
|
128
|
+
# yarn
|
|
129
|
+
yarn dlx drimion init
|
|
130
|
+
|
|
131
|
+
# pnpm
|
|
132
|
+
pnpm dlx drimion init
|
|
133
|
+
|
|
134
|
+
# bun
|
|
135
|
+
bunx --bun drimion init
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Running `init -y` will skip interactive prompts and install defaults and:
|
|
139
|
+
|
|
140
|
+
1. Copy the library core into your project under `src/lib/drimion`
|
|
141
|
+
2. Create a `drimion.config.ts` configuration file
|
|
142
|
+
3. Prepare your project for code generators
|
|
143
|
+
|
|
144
|
+
### Configuration File
|
|
145
|
+
|
|
146
|
+
Below is the default configuration file created for you.
|
|
147
|
+
|
|
148
|
+
```ts
|
|
149
|
+
// drimion.config.ts
|
|
150
|
+
export default {
|
|
151
|
+
drimion: {
|
|
152
|
+
// Where the library source files are installed
|
|
153
|
+
corePath: "src/lib/drimion",
|
|
154
|
+
|
|
155
|
+
// Import alias to use in your project (configure in tsconfig.json paths)
|
|
156
|
+
importAlias: "drimion",
|
|
157
|
+
|
|
158
|
+
// File naming convention for generated files
|
|
159
|
+
// Options: "kebab-case" | "snake_case" | "PascalCase"
|
|
160
|
+
naming: "kebab-case",
|
|
161
|
+
|
|
162
|
+
// Predefined output paths per generator type
|
|
163
|
+
// Used with the --target flag when running `generate`
|
|
164
|
+
targets: {
|
|
165
|
+
entity: {
|
|
166
|
+
// user: "src/modules/user/domain/entities"
|
|
167
|
+
},
|
|
168
|
+
valueObject: {
|
|
169
|
+
// user: "src/modules/user/domain/value-objects"
|
|
170
|
+
},
|
|
171
|
+
aggregate: {},
|
|
172
|
+
repository: {},
|
|
173
|
+
usecase: {},
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
};
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
#### `naming` — File Naming Convention
|
|
180
|
+
|
|
181
|
+
Controls how generated file names are formatted:
|
|
182
|
+
|
|
183
|
+
| Value | Example |
|
|
184
|
+
| ------------------------ | --------------------------------------------- |
|
|
185
|
+
| `kebab-case` _(default)_ | `user.entity.ts`, `pokemon-species.entity.ts` |
|
|
186
|
+
| `snake_case` | `user.entity.ts`, `pokemon_species.entity.ts` |
|
|
187
|
+
| `PascalCase` | `User.entity.ts`, `PokemonSpecies.entity.ts` |
|
|
188
|
+
|
|
189
|
+
#### `targets` — Predefined Output Paths
|
|
190
|
+
|
|
191
|
+
Targets are named shortcuts to output directories, used with `--target` when generating code. Instead of typing the full path each time, define it once in config:
|
|
192
|
+
|
|
193
|
+
```ts
|
|
194
|
+
targets: {
|
|
195
|
+
entity: {
|
|
196
|
+
user: "src/modules/user/domain/entities",
|
|
197
|
+
order: "src/modules/order/domain/entities",
|
|
198
|
+
},
|
|
199
|
+
valueObject: {
|
|
200
|
+
shared: "src/shared/domain/value-objects",
|
|
201
|
+
},
|
|
202
|
+
}
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
Then use it with:
|
|
206
|
+
|
|
207
|
+
```bash
|
|
208
|
+
npx drimion generate entity --name=User --target=user
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
### Recommended Structure
|
|
212
|
+
|
|
213
|
+
This library is **headless** — it does not enforce any folder structure. Below is a suggestion based on DDD conventions:
|
|
214
|
+
|
|
215
|
+
```
|
|
216
|
+
src/
|
|
217
|
+
├── lib/
|
|
218
|
+
│ └── drimion/ ← library source lives here (yours to modify)
|
|
219
|
+
│
|
|
220
|
+
└── modules/
|
|
221
|
+
└── [module-name]/
|
|
222
|
+
├── domain/
|
|
223
|
+
│ ├── aggregates/ ← Aggregate subclasses
|
|
224
|
+
│ ├── entities/ ← Entity subclasses
|
|
225
|
+
│ ├── value-objects/ ← ValueObject subclasses
|
|
226
|
+
│ ├── events/ ← BaseDomainEvent subclasses (optional)
|
|
227
|
+
│ └── interfaces/ ← Repository contracts, IEventBus, etc.
|
|
228
|
+
├── application/
|
|
229
|
+
│ ├── use-cases/ ← ICommand / IQuery implementations
|
|
230
|
+
│ └── services/ ← Application services, event handlers
|
|
231
|
+
└── infrastructure/
|
|
232
|
+
├── repositories/ ← BaseRepository implementations
|
|
233
|
+
└── adapters/ ← Adapter / IAdapter implementations
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
---
|
|
237
|
+
|
|
238
|
+
## Core Features
|
|
239
|
+
|
|
240
|
+
A high-level overview of the DDD building blocks this library provides. For full API details, see [API Reference](#api-reference).
|
|
241
|
+
|
|
242
|
+
### ID
|
|
243
|
+
|
|
244
|
+
Every entity and aggregate needs a stable unique identity. `ID` wraps a UUID string and carries one extra piece of metadata: whether the ID was **freshly generated** or **restored from persistence** — a distinction that matters when deciding whether to insert or update in a repository.
|
|
245
|
+
|
|
246
|
+
```typescript
|
|
247
|
+
const id = ID.create(); // new UUID, isNew() → true
|
|
248
|
+
const existing = ID.create("x"); // from DB, isNew() → false
|
|
249
|
+
const short = ID.short(); // 16-char short ID
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
### Value Object
|
|
253
|
+
|
|
254
|
+
A **Value Object** is a domain concept with no identity of its own — it is defined entirely by its properties. `Money(100, 'USD')` and another `Money(100, 'USD')` are the same thing. They are **immutable** by design: setters are disabled, and any mutation returns a new instance.
|
|
255
|
+
|
|
256
|
+
Use value objects to model domain concepts like `Money`, `Email`, `Address`, `DateRange`, `Quantity`, and any other concept where value matters more than identity.
|
|
257
|
+
|
|
258
|
+
```typescript
|
|
259
|
+
class Money extends ValueObject<MoneyProps> {
|
|
260
|
+
public static isValidProps({ amount, currency }: MoneyProps): boolean {
|
|
261
|
+
return (
|
|
262
|
+
this.validator.number(amount).isPositive() &&
|
|
263
|
+
this.validator.string(currency).hasLengthEqualTo(3)
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
public add(other: Money): Money {
|
|
268
|
+
return Money.create({
|
|
269
|
+
amount: this.get("amount") + other.get("amount"),
|
|
270
|
+
currency: this.get("currency"),
|
|
271
|
+
}).value();
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const price = Money.create({ amount: 100, currency: "USD" });
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
### Entity
|
|
279
|
+
|
|
280
|
+
An **Entity** is a domain object with a stable, unique identity. Unlike value objects, two entities with different IDs are never equal — even if all their properties match. Entities track `createdAt` and `updatedAt` automatically, and any mutation refreshes `updatedAt`.
|
|
281
|
+
|
|
282
|
+
Use entities to model domain objects that have a lifecycle: `User`, `Product`, `Invoice`, `Ticket`, etc.
|
|
283
|
+
|
|
284
|
+
```typescript
|
|
285
|
+
class User extends Entity<UserProps> {
|
|
286
|
+
public static isValidProps(props: UserProps): boolean {
|
|
287
|
+
return (
|
|
288
|
+
this.validator.isString(props.name) &&
|
|
289
|
+
this.validator.isString(props.email)
|
|
290
|
+
);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
public rename(name: string): void {
|
|
294
|
+
this.change("name", name, (v) => v.length > 0);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const result = User.create({ name: "Alice", email: "alice@example.com" });
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
### Aggregate
|
|
302
|
+
|
|
303
|
+
An **Aggregate** is an Entity that acts as the **consistency boundary** of a domain model — the single entry point for all mutations within its boundary. It extends Entity with one key addition: the ability to record **domain events** via `emit()`.
|
|
304
|
+
|
|
305
|
+
After persisting the aggregate, the application layer drains the event queue with `pullEvents()` and hands events off to whichever transport it uses.
|
|
306
|
+
|
|
307
|
+
```typescript
|
|
308
|
+
class Order extends Aggregate<OrderProps> {
|
|
309
|
+
public place(): void {
|
|
310
|
+
this.change("status", "placed");
|
|
311
|
+
this.emit({ type: "order:placed", payload: { total: this.get("total") } });
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
public ship(): void {
|
|
315
|
+
if (this.get("status") !== "placed") {
|
|
316
|
+
throw new DomainError("Order must be placed before shipping.", {
|
|
317
|
+
context: "Order",
|
|
318
|
+
field: "status",
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
this.change("status", "shipped");
|
|
322
|
+
this.emit({ type: "order:shipped" });
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// Somewhere within your Application layer
|
|
327
|
+
order.place();
|
|
328
|
+
await repo.save(order); // persist first
|
|
329
|
+
await bus.publishAll(order.pullEvents()); // then publish
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
> ⚠️ **Always `save()` before `pullEvents()`** — pulling before persistence risks delivering events for a state that was never committed.
|
|
333
|
+
|
|
334
|
+
### Repository
|
|
335
|
+
|
|
336
|
+
A **Repository** is the persistence contract for an aggregate. The interface is defined in the **domain layer** and implemented in the **infrastructure layer** — keeping your domain code free of storage concerns.
|
|
337
|
+
|
|
338
|
+
```typescript
|
|
339
|
+
class UserRepository extends BaseRepository<User> {
|
|
340
|
+
async findById(id: string): Promise<IResult<User, string>> {
|
|
341
|
+
/* ... */
|
|
342
|
+
}
|
|
343
|
+
async save(user: User): Promise<IResult<void, string>> {
|
|
344
|
+
/* ... */
|
|
345
|
+
}
|
|
346
|
+
async delete(id: string): Promise<IResult<void, string>> {
|
|
347
|
+
/* ... */
|
|
348
|
+
}
|
|
349
|
+
async exists(id: string): Promise<boolean> {
|
|
350
|
+
/* ... */
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
### Use Cases
|
|
356
|
+
|
|
357
|
+
Use cases are the entry points to your application logic. This library provides two interfaces to model them: `ICommand` for operations that mutate state, and `IQuery` for read-only operations.
|
|
358
|
+
|
|
359
|
+
```typescript
|
|
360
|
+
import type { ICommand, IQuery } from "drimion";
|
|
361
|
+
|
|
362
|
+
// Mutates state — returns Result
|
|
363
|
+
class PlaceOrderUseCase implements ICommand<PlaceOrderInput, Order> {
|
|
364
|
+
async execute(input: PlaceOrderInput): Promise<IResult<Order, string>> {
|
|
365
|
+
const order = Order.init({
|
|
366
|
+
customerId: input.customerId,
|
|
367
|
+
status: "pending",
|
|
368
|
+
total: input.total,
|
|
369
|
+
});
|
|
370
|
+
order.place();
|
|
371
|
+
await this.repo.save(order);
|
|
372
|
+
await this.bus.publishAll(order.pullEvents());
|
|
373
|
+
return Result.success(order);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// Read-only — returns data without side effects
|
|
378
|
+
class GetOrderUseCase implements IQuery<string, Order> {
|
|
379
|
+
async execute(id: string): Promise<IResult<Order, string>> {
|
|
380
|
+
return this.repo.findById(id);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
---
|
|
386
|
+
|
|
387
|
+
## API Reference
|
|
388
|
+
|
|
389
|
+
### Core API
|
|
390
|
+
|
|
391
|
+
#### ID
|
|
392
|
+
|
|
393
|
+
```typescript
|
|
394
|
+
// Create
|
|
395
|
+
ID.create(); // new UUID — isNew() → true
|
|
396
|
+
ID.create("existing-id"); // from value — isNew() → false
|
|
397
|
+
ID.short(); // 16-char short ID — isNew() → true
|
|
398
|
+
ID.short("existing-id"); // short from value — isNew() → false
|
|
399
|
+
|
|
400
|
+
// Read
|
|
401
|
+
id.value(); // string value
|
|
402
|
+
id.isNew(); // true if auto-generated
|
|
403
|
+
id.isShort(); // true if 16 chars
|
|
404
|
+
|
|
405
|
+
// Compare
|
|
406
|
+
id.isEqual(other); // value equality
|
|
407
|
+
id.deepEqual(other); // deep JSON equality
|
|
408
|
+
|
|
409
|
+
// Clone
|
|
410
|
+
id.clone(); // same value, isNew() → false
|
|
411
|
+
id.cloneAsNew(); // same value, isNew() → true
|
|
412
|
+
id.createdAt(); // Date this ID instance was created
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
#### Value Object
|
|
416
|
+
|
|
417
|
+
```typescript
|
|
418
|
+
// Factory
|
|
419
|
+
MyVO.create(props) // → IResult<MyVO> — safe, does not throw
|
|
420
|
+
MyVO.isValid(props) // → boolean — alias for isValidProps()
|
|
421
|
+
MyVO.init(props) // → MyVO — throws DomainError if invalid (useful only for testing or seeding, prefer `.create()` instead.)
|
|
422
|
+
|
|
423
|
+
// Read
|
|
424
|
+
vo.get('key') // read a property
|
|
425
|
+
vo.get('value') // read primitive VOs (string, number, etc.)
|
|
426
|
+
vo.getRaw() // frozen snapshot of all props
|
|
427
|
+
vo.toObject() // serialized plain object (deeply frozen)
|
|
428
|
+
vo.toObject(adapter) // serialized via Adapter or IAdapter
|
|
429
|
+
|
|
430
|
+
// Compare & Clone
|
|
431
|
+
vo.isEqual(other) // structural equality by value
|
|
432
|
+
vo.clone(props?) // new instance, optionally with overrides
|
|
433
|
+
|
|
434
|
+
// Override in subclass
|
|
435
|
+
static isValidProps(props): boolean // construction validation
|
|
436
|
+
validation(value, key): boolean // per-key invariant on change()
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
#### Entity
|
|
440
|
+
|
|
441
|
+
```typescript
|
|
442
|
+
// Factory
|
|
443
|
+
MyEntity.create(props) // → IResult<MyEntity> — safe, does not throw
|
|
444
|
+
MyEntity.init(props) // → MyEntity — throws DomainError if invalid
|
|
445
|
+
MyEntity.isValid(props) // → boolean
|
|
446
|
+
|
|
447
|
+
// Identity
|
|
448
|
+
entity.id // UID<string>
|
|
449
|
+
entity.id.value() // UUID string
|
|
450
|
+
entity.isNew() // true if id was auto-generated
|
|
451
|
+
entity.hashCode() // '[Entity@ClassName]:uuid'
|
|
452
|
+
|
|
453
|
+
// Read
|
|
454
|
+
entity.get('key') // read a property (throws if getters disabled or key missing)
|
|
455
|
+
entity.getRaw() // frozen snapshot of all props
|
|
456
|
+
entity.toObject() // serialized plain object
|
|
457
|
+
entity.toObject(adapter)
|
|
458
|
+
|
|
459
|
+
// Mutate (each refreshes updatedAt)
|
|
460
|
+
entity.set('key').to(value, validation?) // fluent setter
|
|
461
|
+
entity.change('key', value, validation?) // direct setter
|
|
462
|
+
|
|
463
|
+
// Compare & Clone
|
|
464
|
+
entity.isEqual(other) // same class + same id + same props
|
|
465
|
+
entity.clone(props?) // new instance with same id, optionally with overrides
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
#### Aggregate
|
|
469
|
+
|
|
470
|
+
Inherits all Entity API, plus:
|
|
471
|
+
|
|
472
|
+
```typescript
|
|
473
|
+
// Emit a domain event (inside domain methods)
|
|
474
|
+
this.emit({ type: 'event:name', payload: { ... } })
|
|
475
|
+
this.emit(new MyDomainEvent(...)) // BaseDomainEvent subclass
|
|
476
|
+
|
|
477
|
+
// Event queue
|
|
478
|
+
aggregate.eventCount // number of pending events
|
|
479
|
+
aggregate.peekEvents() // ReadonlyArray<DomainEvent> — inspect without draining
|
|
480
|
+
aggregate.pullEvents() // ReadonlyArray<DomainEvent> — drains the queue
|
|
481
|
+
aggregate.clearEvents() // discard all pending events; returns count cleared
|
|
482
|
+
|
|
483
|
+
// Clone (events not copied by default)
|
|
484
|
+
aggregate.clone(props?) // without events
|
|
485
|
+
aggregate.clone({ ...props, withEvents: true }) // carry events over
|
|
486
|
+
|
|
487
|
+
aggregate.hashCode() // '[Aggregate@ClassName]:uuid'
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
#### Repository
|
|
491
|
+
|
|
492
|
+
```typescript
|
|
493
|
+
abstract class BaseRepository<T extends Entity, ID = UID> {
|
|
494
|
+
abstract findById(id: ID): Promise<IResult<T, string>>;
|
|
495
|
+
abstract save(entity: T): Promise<IResult<void, string>>;
|
|
496
|
+
abstract delete(id: ID): Promise<IResult<void, string>>;
|
|
497
|
+
abstract exists(id: ID): Promise<boolean>;
|
|
498
|
+
}
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
#### Adapters
|
|
502
|
+
|
|
503
|
+
```typescript
|
|
504
|
+
// Synchronous — implement adaptOne (and optionally adaptMany)
|
|
505
|
+
interface Adapter<A, B> {
|
|
506
|
+
adaptOne(item: A): B;
|
|
507
|
+
adaptMany?(items: A[]): B[];
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
// Result-wrapped — implement build
|
|
511
|
+
interface IAdapter<F, T, E = void, M = void> {
|
|
512
|
+
build(target: F): IResult<T, E, M>;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
// Usage
|
|
516
|
+
entity.toObject(new MyAdapter());
|
|
517
|
+
```
|
|
518
|
+
|
|
519
|
+
### Helpers
|
|
520
|
+
|
|
521
|
+
#### Result
|
|
522
|
+
|
|
523
|
+
`Result<T, E, M>` is a typed monad for representing operation outcomes — eliminating uncontrolled `throw` in domain and application code.
|
|
524
|
+
|
|
525
|
+
```typescript
|
|
526
|
+
// Create
|
|
527
|
+
Result.success(); // void success
|
|
528
|
+
Result.success(value); // success with payload
|
|
529
|
+
Result.success(value, meta); // success with payload + metadata
|
|
530
|
+
Result.error("message"); // failure with error
|
|
531
|
+
Result.error("message", meta); // failure with metadata
|
|
532
|
+
|
|
533
|
+
// Inspect
|
|
534
|
+
result.isSuccess(); // boolean
|
|
535
|
+
result.isError(); // boolean
|
|
536
|
+
result.isNull(); // true if void success or failure
|
|
537
|
+
result.value(); // T | null
|
|
538
|
+
result.error(); // E | null
|
|
539
|
+
result.metaData(); // M
|
|
540
|
+
|
|
541
|
+
// Combine multiple results — fails at first failure
|
|
542
|
+
Result.combine([r1, r2, r3]); // → IResult
|
|
543
|
+
|
|
544
|
+
// Iterate results
|
|
545
|
+
Result.iterate([r1, r2, r3]); // → IIterator<IResult>
|
|
546
|
+
|
|
547
|
+
// Execute a command conditionally
|
|
548
|
+
result.execute(command).on("success");
|
|
549
|
+
result.execute(command).withData(data).on("error");
|
|
550
|
+
|
|
551
|
+
// Serialize
|
|
552
|
+
result.toObject();
|
|
553
|
+
// { isSuccess, isError, data, error, metaData }
|
|
554
|
+
```
|
|
555
|
+
|
|
556
|
+
#### DomainClasses
|
|
557
|
+
|
|
558
|
+
Fluent builder for creating multiple domain instances in a batch. The combined `result` fails if any single entry fails.
|
|
559
|
+
|
|
560
|
+
```typescript
|
|
561
|
+
const { result, data } = DomainClasses.prepare(Name, { value: "Alice" })
|
|
562
|
+
.prepare(Email, { value: "alice@example.com" })
|
|
563
|
+
.prepare(Age, { value: 30 })
|
|
564
|
+
.create();
|
|
565
|
+
|
|
566
|
+
if (result.isSuccess()) {
|
|
567
|
+
const name = data.next().value() as Name;
|
|
568
|
+
const email = data.next().value() as Email;
|
|
569
|
+
const age = data.next().value() as Age;
|
|
570
|
+
}
|
|
571
|
+
```
|
|
572
|
+
|
|
573
|
+
#### DomainError
|
|
574
|
+
|
|
575
|
+
Structured domain error with `field` and `context` metadata. Thrown automatically by the library when invariants are violated; throw it manually from your domain methods.
|
|
576
|
+
|
|
577
|
+
```typescript
|
|
578
|
+
throw new DomainError("Amount must be positive", {
|
|
579
|
+
field: "amount", // the prop that caused the violation
|
|
580
|
+
context: "Money", // the domain class where it originated
|
|
581
|
+
});
|
|
582
|
+
// Message: '[Money] Amount must be positive'
|
|
583
|
+
// error.field → 'amount'
|
|
584
|
+
// error.context → 'Money'
|
|
585
|
+
```
|
|
586
|
+
|
|
587
|
+
Thrown automatically by:
|
|
588
|
+
|
|
589
|
+
- `set().to()` / `change()` — validation fails or setters disabled
|
|
590
|
+
- `get()` — getters disabled or key missing
|
|
591
|
+
- `Entity` constructor — props is not a plain object
|
|
592
|
+
- `init()` — `isValidProps()` returns `false`
|
|
593
|
+
- Event managers — event name missing `context:EventName` format
|
|
594
|
+
|
|
595
|
+
#### Iterator
|
|
596
|
+
|
|
597
|
+
Bi-directional sequential traversal over a collection.
|
|
598
|
+
|
|
599
|
+
```typescript
|
|
600
|
+
// Create
|
|
601
|
+
Iterator.create({ initialData, restartOnFinish?, returnCurrentOnReversion? })
|
|
602
|
+
|
|
603
|
+
// Navigate
|
|
604
|
+
iter.next() // move forward, return item
|
|
605
|
+
iter.prev() // move backward, return item
|
|
606
|
+
iter.hasNext() // boolean
|
|
607
|
+
iter.hasPrev() // boolean
|
|
608
|
+
iter.first() // peek first item (no cursor move)
|
|
609
|
+
iter.last() // peek last item (no cursor move)
|
|
610
|
+
iter.toFirst() // reset cursor to before first item
|
|
611
|
+
iter.toLast() // reset cursor to after last item
|
|
612
|
+
|
|
613
|
+
// Mutate
|
|
614
|
+
iter.add(item) // alias for addToEnd
|
|
615
|
+
iter.addToEnd(item)
|
|
616
|
+
iter.addToStart(item) // resets cursor
|
|
617
|
+
iter.removeFirst()
|
|
618
|
+
iter.removeLast()
|
|
619
|
+
iter.removeItem(item) // by JSON equality, adjusts cursor
|
|
620
|
+
iter.clear()
|
|
621
|
+
|
|
622
|
+
// Export
|
|
623
|
+
iter.toArray() // copy as plain array
|
|
624
|
+
iter.clone() // new Iterator with same items and config
|
|
625
|
+
iter.total() // item count
|
|
626
|
+
iter.isEmpty() // boolean
|
|
627
|
+
```
|
|
628
|
+
|
|
629
|
+
#### Validators & Utils
|
|
630
|
+
|
|
631
|
+
Built-in `validator` and `util` instances inherited by all domain classes, accessible as both instance and static members.
|
|
632
|
+
|
|
633
|
+
**Type guards:**
|
|
634
|
+
|
|
635
|
+
```typescript
|
|
636
|
+
this.validator.isString(v) this.validator.isNumber(v)
|
|
637
|
+
this.validator.isBoolean(v) this.validator.isDate(v)
|
|
638
|
+
this.validator.isNull(v) this.validator.isUndefined(v)
|
|
639
|
+
this.validator.isArray(v) this.validator.isObject(v) // plain objects only
|
|
640
|
+
this.validator.isFunction(v) this.validator.isSymbol(v)
|
|
641
|
+
this.validator.isID(v) this.validator.isEntity(v)
|
|
642
|
+
this.validator.isAggregate(v) this.validator.isValueObject(v)
|
|
643
|
+
```
|
|
644
|
+
|
|
645
|
+
**String checks:** `hasLengthBetweenOrEqual(min, max)` · `hasLengthGreaterThan(n)` · `hasLengthLessOrEqualTo(n)` · `hasLengthEqualTo(n)` · `isEmpty()` · `hasSpecialChar()` · `hasOnlyNumbers()` · `hasOnlyLetters()` · `match(regex)` · `isEqual(str)` · `includes(str)`
|
|
646
|
+
|
|
647
|
+
**Number checks:** `isPositive()` · `isNegative()` · `isGreaterThan(n)` · `isGreaterOrEqualTo(n)` · `isLessThan(n)` · `isLessOrEqualTo(n)` · `isBetween(min, max)` · `isBetweenOrEqual(min, max)` · `isEqualTo(n)` · `isInteger()` · `isSafeInteger()` · `isEven()`
|
|
648
|
+
|
|
649
|
+
**Date checks:** `isAfterNow()` · `isBeforeNow()` · `isAfterThan(d)` · `isAfterOrEqualTo(d)` · `isBeforeThan(d)` · `isBeforeOrEqualTo(d)` · `isBetween(start, end)` · `isWeekend()`
|
|
650
|
+
|
|
651
|
+
**Date utils:** `util.date(d).add(n).days/weeks/months/hours/minutes()` · `util.date(d).subtract(n).days/weeks/months/hours/minutes()`
|
|
652
|
+
|
|
653
|
+
**Number utils** (floating-point safe): `util.number(n).sum(v)` · `.subtract(v)` · `.multiplyBy(v)` · `.divideBy(v, { fractionDigits? })`
|
|
654
|
+
|
|
655
|
+
**String utils:** `util.string(s).removeSpaces()` · `.removeNumbers()` · `.removeSpecialChars()` · `.removeChar(c)` · `.replace(c).to(v)`
|
|
656
|
+
|
|
657
|
+
### Event System
|
|
658
|
+
|
|
659
|
+
The Event System is a **standalone, portable package** — completely decoupled from your domain model. The `Aggregate` only collects events via `emit()`; it has no awareness of how those events get delivered.
|
|
660
|
+
|
|
661
|
+
```
|
|
662
|
+
Aggregate (emits) ──→ pullEvents() ──→ [ Your Transport ]
|
|
663
|
+
│
|
|
664
|
+
┌───────────────────┼──────────────────────┐
|
|
665
|
+
▼ ▼ ▼
|
|
666
|
+
EventBus Redis / Kafka Custom IEventBus
|
|
667
|
+
(in-process) (distributed) (anything)
|
|
668
|
+
```
|
|
669
|
+
|
|
670
|
+
#### Domain Event
|
|
671
|
+
|
|
672
|
+
Domain events record what happened inside an aggregate boundary. They are plain, serializable data with no handlers and no side effects.
|
|
673
|
+
|
|
674
|
+
```typescript
|
|
675
|
+
// DomainEvent shape
|
|
676
|
+
interface DomainEvent<TPayload = unknown> {
|
|
677
|
+
readonly type: string; // 'order:placed' — required
|
|
678
|
+
readonly aggregateId?: string; // auto-filled by emit()
|
|
679
|
+
readonly aggregateName?: string; // auto-filled by emit()
|
|
680
|
+
readonly occurredAt?: Date; // auto-filled by emit()
|
|
681
|
+
readonly payload?: TPayload;
|
|
682
|
+
}
|
|
683
|
+
```
|
|
684
|
+
|
|
685
|
+
> **Naming convention:** past-tense, `aggregate:fact` — e.g. `order:placed`, `user:email-changed`, `payment:failed`.
|
|
686
|
+
|
|
687
|
+
**Inline style** (recommended for most cases):
|
|
688
|
+
|
|
689
|
+
```typescript
|
|
690
|
+
this.emit({ type: "order:placed", payload: { total: 100, customerId: "c-1" } });
|
|
691
|
+
```
|
|
692
|
+
|
|
693
|
+
**OOP style** — `BaseDomainEvent` (when you need `instanceof` checks or reusability):
|
|
694
|
+
|
|
695
|
+
```typescript
|
|
696
|
+
class OrderPlacedEvent extends BaseDomainEvent<OrderPlacedPayload> {
|
|
697
|
+
static readonly type = "order:placed" as const;
|
|
698
|
+
|
|
699
|
+
constructor(aggregateId: string, payload: OrderPlacedPayload) {
|
|
700
|
+
super(OrderPlacedEvent.type, aggregateId, "Order", payload);
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
// Then, in your aggregate
|
|
705
|
+
this.emit(
|
|
706
|
+
new OrderPlacedEvent(this.id.value(), { total: 100, customerId: "c-1" }),
|
|
707
|
+
);
|
|
708
|
+
|
|
709
|
+
// Subscriber
|
|
710
|
+
if (event instanceof OrderPlacedEvent) {
|
|
711
|
+
/* ... */
|
|
712
|
+
}
|
|
713
|
+
```
|
|
714
|
+
|
|
715
|
+
#### Event Bus
|
|
716
|
+
|
|
717
|
+
`EventBus` is the built-in in-process pub-sub. Zero config, works in Node.js and browsers.
|
|
718
|
+
|
|
719
|
+
```typescript
|
|
720
|
+
const bus = new EventBus();
|
|
721
|
+
|
|
722
|
+
bus.subscribe("order:placed", async (event) => {
|
|
723
|
+
/* ... */
|
|
724
|
+
});
|
|
725
|
+
|
|
726
|
+
await repo.save(order);
|
|
727
|
+
await bus.publishAll(order.pullEvents());
|
|
728
|
+
```
|
|
729
|
+
|
|
730
|
+
| Method | Description |
|
|
731
|
+
| ----------------------- | -------------------------------------------------------- |
|
|
732
|
+
| `subscribe(type, fn)` | Register a subscriber for an event type |
|
|
733
|
+
| `unsubscribe(type)` | Remove all subscribers for a type; returns count removed |
|
|
734
|
+
| `publish(event)` | Publish a single event |
|
|
735
|
+
| `publishAll(events)` | Publish an ordered array of events |
|
|
736
|
+
| `subscriberCount(type)` | Count subscribers for a type |
|
|
737
|
+
| `clear()` | Remove all subscribers |
|
|
738
|
+
|
|
739
|
+
Multiple subscribers for the same type run independently. Errors are collected and re-thrown as a single `AggregateError` after all subscribers have run.
|
|
740
|
+
|
|
741
|
+
**Bring your own transport** — implement `IEventBus`:
|
|
742
|
+
|
|
743
|
+
```typescript
|
|
744
|
+
class RedisEventBus implements IEventBus {
|
|
745
|
+
async publish(event: DomainEvent): Promise<void> {
|
|
746
|
+
await redis.xadd(event.type, "*", "data", JSON.stringify(event));
|
|
747
|
+
}
|
|
748
|
+
async publishAll(events: ReadonlyArray<DomainEvent>): Promise<void> {
|
|
749
|
+
for (const event of events) await this.publish(event);
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
const bus: IEventBus = new RedisEventBus();
|
|
754
|
+
await bus.publishAll(order.pullEvents()); // no domain code changes needed
|
|
755
|
+
```
|
|
756
|
+
|
|
757
|
+
#### Event Context
|
|
758
|
+
|
|
759
|
+
For platform-native dispatch (browser `CustomEvent` or Node.js `EventEmitter`), `EventContext` auto-detects the runtime and returns the appropriate manager:
|
|
760
|
+
|
|
761
|
+
```typescript
|
|
762
|
+
const manager = EventContext.resolve();
|
|
763
|
+
// → ServerEventManager in Node.js / Bun / Deno
|
|
764
|
+
// → BrowserEventManager in browsers
|
|
765
|
+
```
|
|
766
|
+
|
|
767
|
+
**`ServerEventManager`** — Node.js singleton backed by `EventEmitter`:
|
|
768
|
+
|
|
769
|
+
```typescript
|
|
770
|
+
const manager = ServerEventManager.instance();
|
|
771
|
+
manager.subscribe("user:registered", handler);
|
|
772
|
+
manager.dispatchEvent("user:registered", payload);
|
|
773
|
+
manager.exists("user:registered"); // true
|
|
774
|
+
manager.removeEvent("user:registered"); // true
|
|
775
|
+
```
|
|
776
|
+
|
|
777
|
+
**`BrowserEventManager`** — Browser singleton backed by `CustomEvent`, with `sessionStorage` persistence:
|
|
778
|
+
|
|
779
|
+
```typescript
|
|
780
|
+
const manager = BrowserEventManager.instance(window);
|
|
781
|
+
manager.subscribe("cart:item-added", handler);
|
|
782
|
+
manager.dispatchEvent("cart:item-added", { productId: "p-1" });
|
|
783
|
+
```
|
|
784
|
+
|
|
785
|
+
Both managers support **wildcard dispatch**:
|
|
786
|
+
|
|
787
|
+
```typescript
|
|
788
|
+
manager.dispatchEvent("order:*"); // dispatches all events matching 'order:*'
|
|
789
|
+
```
|
|
790
|
+
|
|
791
|
+
> **Event name format:** all managers require `context:EventName` format (e.g. `order:placed`). A `DomainError` is thrown on invalid names.
|
|
792
|
+
|
|
793
|
+
---
|
|
794
|
+
|
|
795
|
+
## CLI Reference
|
|
796
|
+
|
|
797
|
+
### info
|
|
798
|
+
|
|
799
|
+
Display CLI version and a full command reference.
|
|
800
|
+
|
|
801
|
+
```bash
|
|
802
|
+
npx drimion info
|
|
803
|
+
```
|
|
804
|
+
|
|
805
|
+
### init
|
|
806
|
+
|
|
807
|
+
Initialize the library in your project.
|
|
808
|
+
|
|
809
|
+
```bash
|
|
810
|
+
npx drimion init # interactive
|
|
811
|
+
npx drimion init -y # skip all prompts
|
|
812
|
+
```
|
|
813
|
+
|
|
814
|
+
**What it does:**
|
|
815
|
+
|
|
816
|
+
1. Copies library source files into `src/lib/drimion`
|
|
817
|
+
2. Creates `drimion.config.ts` in your project root
|
|
818
|
+
3. Prepares your project for generators
|
|
819
|
+
|
|
820
|
+
**Flags:**
|
|
821
|
+
|
|
822
|
+
| Flag | Description |
|
|
823
|
+
| ---- | ------------------------------ |
|
|
824
|
+
| `-y` | Skip all prompts, use defaults |
|
|
825
|
+
|
|
826
|
+
### list
|
|
827
|
+
|
|
828
|
+
List all available code generators.
|
|
829
|
+
|
|
830
|
+
```bash
|
|
831
|
+
npx drimion list
|
|
832
|
+
```
|
|
833
|
+
|
|
834
|
+
```
|
|
835
|
+
Available generators:
|
|
836
|
+
|
|
837
|
+
• entity → Domain entity with identity and lifecycle
|
|
838
|
+
• value-object → Immutable value object
|
|
839
|
+
• aggregate → Aggregate root with domain events
|
|
840
|
+
• repository → Repository contract/implementation
|
|
841
|
+
• use-case → Application use case (command/query)
|
|
842
|
+
```
|
|
843
|
+
|
|
844
|
+
### generate
|
|
845
|
+
|
|
846
|
+
Generate a domain building block from a template.
|
|
847
|
+
|
|
848
|
+
```bash
|
|
849
|
+
npx drimion generate # fully interactive
|
|
850
|
+
npx drimion generate <type> --name=<Name> # with type and name
|
|
851
|
+
npx drimion generate <type> --name=<Name> --target=<t> # resolve path from config
|
|
852
|
+
npx drimion generate <type> --name=<Name> --location=<path> # explicit path
|
|
853
|
+
```
|
|
854
|
+
|
|
855
|
+
**Examples:**
|
|
856
|
+
|
|
857
|
+
```bash
|
|
858
|
+
# Generate with an explicit output path
|
|
859
|
+
npx drimion generate entity --name=User --location=src/modules/user/domain/entities
|
|
860
|
+
|
|
861
|
+
# Generate using a predefined target from drimion.config.ts
|
|
862
|
+
npx drimion generate value-object --name=Email --target=user
|
|
863
|
+
|
|
864
|
+
# Generate using aliases
|
|
865
|
+
npx drimion generate entity -n=User -l=src/modules/user/domain/entities
|
|
866
|
+
npx drimion generate entity -n User -l src/modules/user/domain/entities
|
|
867
|
+
npx drimion generate value-object -n Email -t user
|
|
868
|
+
|
|
869
|
+
# Fully interactive — prompts for type, name, and destination
|
|
870
|
+
npx drimion generate
|
|
871
|
+
```
|
|
872
|
+
|
|
873
|
+
**Flags:**
|
|
874
|
+
|
|
875
|
+
| Flag | Alias | Description |
|
|
876
|
+
| ------------ | ----- | ------------------------------------------------------------- |
|
|
877
|
+
| `--name` | `-n` | Name of the class to generate (auto-normalized to PascalCase) |
|
|
878
|
+
| `--target` | `-t` | Use a predefined path from `targets` in `drimion.config.ts` |
|
|
879
|
+
| `--location` | `-l` | Manually specify the output directory |
|
|
880
|
+
|
|
881
|
+
**Name normalization:**
|
|
882
|
+
|
|
883
|
+
All names are automatically normalized to **PascalCase** for class usage. The CLI confirms before generating:
|
|
884
|
+
|
|
885
|
+
| Input | Normalized |
|
|
886
|
+
| -------------- | ------------- |
|
|
887
|
+
| `user` | `User` |
|
|
888
|
+
| `user profile` | `UserProfile` |
|
|
889
|
+
| `user_profile` | `UserProfile` |
|
|
890
|
+
| `UserProfile` | `UserProfile` |
|
|
891
|
+
|
|
892
|
+
### sync
|
|
893
|
+
|
|
894
|
+
Update the library source files to the latest version.
|
|
895
|
+
|
|
896
|
+
```bash
|
|
897
|
+
npx drimion sync # interactive
|
|
898
|
+
npx drimion sync -f # force overwrite — no prompts, no backup
|
|
899
|
+
```
|
|
900
|
+
|
|
901
|
+
When a new version is detected, you are prompted:
|
|
902
|
+
|
|
903
|
+
```
|
|
904
|
+
A new version of library kernel is available.
|
|
905
|
+
|
|
906
|
+
What do you want to do?
|
|
907
|
+
|
|
908
|
+
1. Overwrite existing files
|
|
909
|
+
2. Skip update
|
|
910
|
+
3. Backup current version and install new
|
|
911
|
+
```
|
|
912
|
+
|
|
913
|
+
Choosing option 3 moves your current files to:
|
|
914
|
+
|
|
915
|
+
```
|
|
916
|
+
src/lib/drimion/__backup__/vX.X.X
|
|
917
|
+
```
|
|
918
|
+
|
|
919
|
+
This way, you can safely resolve your own changes to the newest version available.
|
|
920
|
+
|
|
921
|
+
> ⚠️ Add the backup directory to `.gitignore`:
|
|
922
|
+
>
|
|
923
|
+
> ```
|
|
924
|
+
> src/lib/drimion/__backup__/
|
|
925
|
+
> ```
|
|
926
|
+
|
|
927
|
+
**Flags:**
|
|
928
|
+
|
|
929
|
+
| Flag | Alias | Description |
|
|
930
|
+
| --------- | ----- | -------------------------------------------- |
|
|
931
|
+
| `--force` | `-f` | Overwrite everything — no prompts, no backup |
|
|
932
|
+
|
|
933
|
+
### uninstall
|
|
934
|
+
|
|
935
|
+
Uninstall the library completely.
|
|
936
|
+
|
|
937
|
+
```bash
|
|
938
|
+
npx drimion uninstall
|
|
939
|
+
```
|
|
940
|
+
|
|
941
|
+
This will locate both the config file and the library directory if exists and
|
|
942
|
+
remove it. So you can still remove the config file or library directory manually.
|
|
943
|
+
|
|
944
|
+
---
|
|
945
|
+
|
|
946
|
+
## Roadmap
|
|
947
|
+
|
|
948
|
+
- [ ] Template presets for common Value Objects
|
|
949
|
+
- [ ] Better sync diffing for resolving sync changes
|
|
950
|
+
|
|
951
|
+
---
|
|
952
|
+
|
|
953
|
+
## License
|
|
954
|
+
|
|
955
|
+
Licensed under the MIT License.
|