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.
Files changed (51) hide show
  1. package/LICENSE +7 -0
  2. package/README.md +955 -0
  3. package/dist/cli/index.js +1045 -0
  4. package/dist/cli/templates/aggregate.ts.hbs +22 -0
  5. package/dist/cli/templates/entity.ts.hbs +16 -0
  6. package/dist/cli/templates/repository.ts.hbs +24 -0
  7. package/dist/cli/templates/use-case.ts.hbs +20 -0
  8. package/dist/cli/templates/value-object.ts.hbs +16 -0
  9. package/dist/kernel/core/aggregate.ts +234 -0
  10. package/dist/kernel/core/entity.ts +348 -0
  11. package/dist/kernel/core/id.ts +207 -0
  12. package/dist/kernel/core/index.ts +5 -0
  13. package/dist/kernel/core/repository.ts +81 -0
  14. package/dist/kernel/core/value-object.ts +309 -0
  15. package/dist/kernel/events/browser-event-manager.ts +241 -0
  16. package/dist/kernel/events/domain-event.ts +76 -0
  17. package/dist/kernel/events/event-bus.ts +158 -0
  18. package/dist/kernel/events/event-context.ts +95 -0
  19. package/dist/kernel/events/event-manager.ts +20 -0
  20. package/dist/kernel/events/event-utils.ts +19 -0
  21. package/dist/kernel/events/index.ts +7 -0
  22. package/dist/kernel/events/server-event-manager.ts +169 -0
  23. package/dist/kernel/helpers/auto-mapper.ts +222 -0
  24. package/dist/kernel/helpers/domain-classes.ts +162 -0
  25. package/dist/kernel/helpers/domain-error.ts +52 -0
  26. package/dist/kernel/helpers/getters-setters.ts +385 -0
  27. package/dist/kernel/helpers/index.ts +7 -0
  28. package/dist/kernel/index.ts +73 -0
  29. package/dist/kernel/libs/crypto.ts +33 -0
  30. package/dist/kernel/libs/index.ts +5 -0
  31. package/dist/kernel/libs/iterator.ts +298 -0
  32. package/dist/kernel/libs/result.ts +252 -0
  33. package/dist/kernel/libs/utils.ts +260 -0
  34. package/dist/kernel/libs/validator.ts +353 -0
  35. package/dist/kernel/types/adapter.types.ts +26 -0
  36. package/dist/kernel/types/command.types.ts +37 -0
  37. package/dist/kernel/types/entity.types.ts +60 -0
  38. package/dist/kernel/types/event.types.ts +129 -0
  39. package/dist/kernel/types/index.ts +26 -0
  40. package/dist/kernel/types/iterator.types.ts +39 -0
  41. package/dist/kernel/types/result.types.ts +122 -0
  42. package/dist/kernel/types/uid.types.ts +18 -0
  43. package/dist/kernel/types/utils.types.ts +120 -0
  44. package/dist/kernel/types/value-object.types.ts +20 -0
  45. package/dist/kernel/utils/date.utils.ts +111 -0
  46. package/dist/kernel/utils/index.ts +32 -0
  47. package/dist/kernel/utils/number.utils.ts +341 -0
  48. package/dist/kernel/utils/object.utils.ts +61 -0
  49. package/dist/kernel/utils/string.utils.ts +128 -0
  50. package/dist/kernel/utils/type.utils.ts +33 -0
  51. package/package.json +59 -0
package/README.md ADDED
@@ -0,0 +1,955 @@
1
+ # Drimion
2
+
3
+ ![version](https://img.shields.io/npm/v/drimion?label=version)
4
+ ![license](https://img.shields.io/npm/l/drimion)
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.