drimion 0.1.12 → 0.2.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 CHANGED
@@ -28,6 +28,7 @@ A developer-first CLI tool for bringing Domain-Driven Design (DDD) primitives in
28
28
  - [Entity](#entity)
29
29
  - [Aggregate](#aggregate)
30
30
  - [Repository](#repository)
31
+ - [Specification](#specification)
31
32
  - [Use Cases](#use-cases)
32
33
  - [API Reference](#api-reference)
33
34
  - [Core API](#core-api)
@@ -83,7 +84,7 @@ Instead, it:
83
84
  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
 
85
86
  ```
86
- ┌─────────────────────────────────────────────────┐
87
+ ┌──────────────────────────────────────────────────┐
87
88
  │ Application Layer │
88
89
  │ Use Cases · Commands · Queries · Event Handlers │
89
90
  ├──────────────────────────────────────────────────┤
@@ -111,7 +112,6 @@ Key DDD principles this library embraces:
111
112
  | `ValueObject` | ❌ By value | ❌ Immutable | ❌ | Domain concept defined entirely by its properties |
112
113
  | `Entity` | ✅ By `id` | ✅ | ❌ | Domain object with stable identity and lifecycle |
113
114
  | `Aggregate` | ✅ By `id` | ✅ | ✅ | Consistency boundary; single entry point for mutations |
114
- | `ID` | — | — | — | Typed unique identifier (UUID or custom) |
115
115
 
116
116
  ---
117
117
 
@@ -132,7 +132,7 @@ yarn dlx drimion init
132
132
  pnpm dlx drimion init
133
133
 
134
134
  # bun
135
- bunx --bun drimion init
135
+ bun x drimion init
136
136
  ```
137
137
 
138
138
  Running `init -y` will skip interactive prompts and install defaults and:
@@ -336,14 +336,53 @@ await bus.publishAll(order.pullEvents()); // then publish
336
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
337
 
338
338
  ```typescript
339
- class UserRepository extends BaseRepository<User> {
340
- async findById(id: string): Promise<IResult<User, string>> {
339
+ // defined within the domain layer as port
340
+ interface IUserRepository extends BaseRepository<User> {
341
+ findById(id: UID): Promise<User | null>
342
+ save(user: User): Promise<void>
343
+ delete(id: UID): Promise<void>
344
+ exists(id: UID): Promise<boolean>
345
+ }
346
+
347
+ // defined within the infra layer as adapter
348
+ class UserRepository extends IUserRepository {
349
+ async findById(id: string): Promise<User> {
350
+ /* ... */
351
+ }
352
+ async save(user: User): Promise<void> {
353
+ /* ... */
354
+ }
355
+ async delete(id: string): Promise<void> {
356
+ /* ... */
357
+ }
358
+ async exists(id: string): Promise<boolean> {
359
+ /* ... */
360
+ }
361
+ }
362
+
363
+ class DrizzleORMUserRepository extends IUserRepository {
364
+ async findById(id: string): Promise<User> {
365
+ /* ... */
366
+ }
367
+ async save(user: User): Promise<void> {
368
+ /* ... */
369
+ }
370
+ async delete(id: string): Promise<void> {
341
371
  /* ... */
342
372
  }
343
- async save(user: User): Promise<IResult<void, string>> {
373
+ async exists(id: string): Promise<boolean> {
374
+ /* ... */
375
+ }
376
+ }
377
+
378
+ class PrismaORMUserRepository extends IUserRepository {
379
+ async findById(id: string): Promise<User> {
344
380
  /* ... */
345
381
  }
346
- async delete(id: string): Promise<IResult<void, string>> {
382
+ async save(user: User): Promise<void> {
383
+ /* ... */
384
+ }
385
+ async delete(id: string): Promise<void> {
347
386
  /* ... */
348
387
  }
349
388
  async exists(id: string): Promise<boolean> {
@@ -352,6 +391,59 @@ class UserRepository extends BaseRepository<User> {
352
391
  }
353
392
  ```
354
393
 
394
+ ### Specification
395
+
396
+ A **Specification** encapsulates a single, named business rule as a pure predicate. Instead of scattering `if` conditions across use cases and domain methods, you give each rule a name that lives in your ubiquitous language — and compose rules together without subclassing.
397
+
398
+ Three scenarios where specifications belong:
399
+
400
+ 1. **Validation** — guard Aggregate domain methods against invalid state transitions.
401
+ 2. **In-memory selection** — filter already-loaded collections by business criteria.
402
+ 3. **Construction criteria** — express what a valid candidate must look like before building something.
403
+
404
+ > ⚠️ Specifications are **not** intended to drive persistence queries. Repository methods should accept explicit parameters and delegate to the ORM/query builder directly — translating a spec into SQL couples domain rules to infrastructure.
405
+
406
+ ```typescript
407
+ // Define — implement `satisfiedBy`, not `isSatisfiedBy`
408
+ class MinimumOrderAmountSpec extends BaseSpecification<Order> {
409
+ constructor(private readonly minimum: number) { super(); }
410
+
411
+ protected satisfiedBy(order: Order): boolean {
412
+ return order.get('total') >= this.minimum;
413
+ }
414
+ }
415
+
416
+ class IsPaidSpec extends BaseSpecification<Order> {
417
+ protected satisfiedBy(order: Order): boolean {
418
+ return order.get('isPaid') === true;
419
+ }
420
+ }
421
+
422
+ class IsFraudSpec extends BaseSpecification<Order> {
423
+ protected satisfiedBy(order: Order): boolean {
424
+ return order.get('isFraud') === true;
425
+ }
426
+ }
427
+
428
+ // Compose — .and() / .or() / .not()
429
+ const eligibleForShipping = new MinimumOrderAmountSpec(100)
430
+ .and(new IsPaidSpec())
431
+ .and(new IsFraudSpec().not());
432
+
433
+ // Guard inside an Aggregate domain method
434
+ ship(): void {
435
+ const spec = new MinimumOrderAmountSpec(100).and(new IsPaidSpec());
436
+ if (!spec.isSatisfiedBy(this)) {
437
+ throw new DomainError('Order is not eligible for shipping', { context: 'Order' });
438
+ }
439
+ this.change('status', 'shipped');
440
+ this.emit({ type: 'order:shipped' });
441
+ }
442
+
443
+ // In-memory selection
444
+ const eligible = orders.filter(o => eligibleForShipping.isSatisfiedBy(o));
445
+ ```
446
+
355
447
  ### Use Cases
356
448
 
357
449
  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.
@@ -498,6 +590,26 @@ abstract class BaseRepository<T extends Entity, ID = UID> {
498
590
  }
499
591
  ```
500
592
 
593
+ #### Specification
594
+
595
+ ```typescript
596
+ // Extend BaseSpecification<T> and implement satisfiedBy()
597
+ class MySpec extends BaseSpecification<MyType> {
598
+ protected satisfiedBy(candidate: MyType): boolean { /* rule */ }
599
+ }
600
+
601
+ // Evaluate
602
+ spec.isSatisfiedBy(candidate) // → boolean
603
+
604
+ // Compose — each returns a new ISpecification<T>
605
+ spec.and(other) // both must be satisfied
606
+ spec.or(other) // either must be satisfied
607
+ spec.not() // negates this spec
608
+
609
+ // Chain freely
610
+ new ASpec().and(new BSpec()).or(new CSpec().not()).and(new DSpec())
611
+ ```
612
+
501
613
  #### Adapters
502
614
 
503
615
  ```typescript
@@ -592,44 +704,12 @@ Thrown automatically by:
592
704
  - `init()` — `isValidProps()` returns `false`
593
705
  - Event managers — event name missing `context:EventName` format
594
706
 
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
707
  #### Validators & Utils
630
708
 
631
709
  Built-in `validator` and `util` instances inherited by all domain classes, accessible as both instance and static members.
632
710
 
711
+
712
+
633
713
  **Type guards:**
634
714
 
635
715
  ```typescript
@@ -642,6 +722,18 @@ this.validator.isID(v) this.validator.isEntity(v)
642
722
  this.validator.isAggregate(v) this.validator.isValueObject(v)
643
723
  ```
644
724
 
725
+ > You can also import the instance directly from the kernel using the lowercase name exports
726
+
727
+ ```typescript
728
+ // import the instance instead of using it inside class through `this` context
729
+ import { validator, utils } from "{your-import-alias}"
730
+
731
+ function myFunctionOutsideOfClass() {
732
+ validator.string("Some string")
733
+ utils.string("some string")
734
+ }
735
+ ```
736
+
645
737
  **String checks:** `hasLengthBetweenOrEqual(min, max)` · `hasLengthGreaterThan(n)` · `hasLengthLessOrEqualTo(n)` · `hasLengthEqualTo(n)` · `isEmpty()` · `hasSpecialChar()` · `hasOnlyNumbers()` · `hasOnlyLetters()` · `match(regex)` · `isEqual(str)` · `includes(str)`
646
738
 
647
739
  **Number checks:** `isPositive()` · `isNegative()` · `isGreaterThan(n)` · `isGreaterOrEqualTo(n)` · `isLessThan(n)` · `isLessOrEqualTo(n)` · `isBetween(min, max)` · `isBetweenOrEqual(min, max)` · `isEqualTo(n)` · `isInteger()` · `isSafeInteger()` · `isEven()`
@@ -663,8 +755,8 @@ Aggregate (emits) ──→ pullEvents() ──→ [ Your Transport ]
663
755
 
664
756
  ┌───────────────────┼──────────────────────┐
665
757
  ▼ ▼ ▼
666
- EventBus Redis / Kafka Custom IEventBus
667
- (in-process) (distributed) (anything)
758
+ EventBus Redis / Kafka Custom IEventBus
759
+ (in-process) (distributed) (anything)
668
760
  ```
669
761
 
670
762
  #### Domain Event
@@ -952,4 +1044,4 @@ remove it. So you can still remove the config file or library directory manually
952
1044
 
953
1045
  ## License
954
1046
 
955
- Licensed under the MIT License.
1047
+ Licensed under the MIT License.
package/dist/cli/index.js CHANGED
@@ -219,7 +219,7 @@ function resolvePackageManager() {
219
219
  if (true) {
220
220
  return {
221
221
  __PKG_NAME__: "drimion",
222
- __PKG_VERSION__: "0.1.12",
222
+ __PKG_VERSION__: "0.2.0",
223
223
  __PKG_REPOSITORY__: "git+https://github.com/bramadl/drimion.git"
224
224
  };
225
225
  }
@@ -1,4 +1,6 @@
1
1
  import type { BaseRepository } from "@pokepulse/kernel";
2
2
  // import { {{className}} } from "./path-to-your-{{lowerName}}";
3
3
 
4
- export interface I{{className}}Repository extends BaseRepository<{{className}}> {}
4
+ export interface I{{className}}Repository extends BaseRepository<{{className}}> {
5
+ // add your custom method
6
+ }
@@ -9,16 +9,9 @@ export type {{className}}Output = {
9
9
  }
10
10
 
11
11
  export type {{className}}Metadata = {
12
- // define metadata - use never if no input needed or `AnyObject` for empty object
12
+ // define metadata - use never if no metadata needed
13
13
  }
14
14
 
15
- /**
16
- * @note
17
- * In order to avoid this TS error:
18
- * `A class can only implement an object type or intersection of object types with statically known members`.
19
- *
20
- * Decide wether you want to use `IQuery` or `ICommand` then change the `IUseCase` usage into one of them.
21
- */
22
15
  export class {{className}} implements IUseCase<{{className}}Input, {{className}}Output> {
23
16
  public constructor() {}
24
17
 
@@ -2,8 +2,8 @@
2
2
  /** biome-ignore-all lint/complexity/noThisInStatic: Required for polymorphic `this` in base factory (DDD pattern). */
3
3
 
4
4
  import { AutoMapper } from "../helpers/auto-mapper";
5
- import { DomainError } from "../helpers/domain-error";
6
5
  import { GettersAndSetters } from "../helpers/getters-setters";
6
+ import { DomainError } from "../libs/domain-error";
7
7
  import { Result } from "../libs/result";
8
8
  import type { Adapter, IAdapter } from "../types/adapter.types";
9
9
  import type {
@@ -1,5 +1,7 @@
1
1
  export { Aggregate } from "./aggregate";
2
+ export { BaseDomainEvent } from "./domain-event";
2
3
  export { Entity } from "./entity";
3
4
  export { ID, Id } from "./id";
4
5
  export { BaseRepository } from "./repository";
6
+ export { BaseSpecification } from "./specification";
5
7
  export { ValueObject } from "./value-object";
@@ -0,0 +1,180 @@
1
+ import type { ISpecification } from "../types/specification.types";
2
+
3
+ /**
4
+ * @description
5
+ * Abstract base class for all domain specifications.
6
+ *
7
+ * A `Specification` is a pure predicate — it answers one question: does this
8
+ * candidate satisfy the rule? Nothing more. Error messaging and failure context
9
+ * are concerns of the caller (domain method, application layer) via `DomainError`.
10
+ *
11
+ * Specifications are:
12
+ * - **Named** — each subclass carries a meaningful ubiquitous-language name.
13
+ * - **Composable** — combine via `.and()`, `.or()`, `.not()` without subclassing.
14
+ * - **Pure** — no side effects, no I/O, no error state.
15
+ *
16
+ * **Three use cases (Evans):**
17
+ * 1. **Validation** — guard Aggregate domain methods against invalid state transitions.
18
+ * 2. **In-memory selection** — filter already-loaded collections.
19
+ * 3. **Construction criteria** — express what a valid object must look like.
20
+ *
21
+ * Specifications are **not** intended to drive persistence queries. Repository
22
+ * methods should use explicit parameters and delegate to the ORM/query builder
23
+ * directly — translating specs to SQL couples domain rules to infrastructure.
24
+ *
25
+ * **Implementing a specification**
26
+ *
27
+ * Extend `BaseSpecification<T>` and implement the `protected satisfiedBy()` method.
28
+ * Never override `isSatisfiedBy` — the base class owns that method.
29
+ *
30
+ * @template T The type of the candidate being evaluated.
31
+ *
32
+ * @example
33
+ * ```typescript
34
+ * // 1. Define
35
+ * class MinimumOrderAmountSpec extends BaseSpecification<Order> {
36
+ * constructor(private readonly minimum: number) { super(); }
37
+ *
38
+ * protected satisfiedBy(order: Order): boolean {
39
+ * return order.get('total') >= this.minimum;
40
+ * }
41
+ * }
42
+ *
43
+ * // 2. Compose
44
+ * const eligible = new MinimumOrderAmountSpec(100)
45
+ * .and(new IsPaidSpec())
46
+ * .and(new IsFraudSpec().not());
47
+ *
48
+ * // 3. Guard inside an Aggregate domain method
49
+ * ship(): void {
50
+ * if (!new MinimumOrderAmountSpec(100).and(new IsPaidSpec()).isSatisfiedBy(this)) {
51
+ * throw new DomainError('Order is not eligible for shipping', { context: 'Order' });
52
+ * }
53
+ * this.change('status', 'shipped');
54
+ * this.emit({ type: 'order:shipped' });
55
+ * }
56
+ *
57
+ * // 4. In-memory selection
58
+ * const eligibleOrders = orders.filter(o => eligible.isSatisfiedBy(o));
59
+ * ```
60
+ */
61
+ export abstract class BaseSpecification<T> implements ISpecification<T> {
62
+ /**
63
+ * @description
64
+ * Marker used by `Validator.isSpecification()` to identify this instance
65
+ * without an `instanceof` check (avoids circular imports).
66
+ * @internal
67
+ */
68
+ protected readonly __kind = "Specification" as const;
69
+
70
+ /**
71
+ * @description
72
+ * Implement the business rule here.
73
+ *
74
+ * Called internally by `isSatisfiedBy`. Do **not** call this method directly.
75
+ *
76
+ * @param candidate The domain object to evaluate.
77
+ * @returns `true` if the candidate satisfies the rule; `false` otherwise.
78
+ */
79
+ protected abstract satisfiedBy(candidate: T): boolean;
80
+
81
+ /**
82
+ * @description
83
+ * Evaluates whether the candidate satisfies this specification.
84
+ *
85
+ * Delegates to `satisfiedBy()` — do not override this method in subclasses.
86
+ *
87
+ * @param candidate The domain object to evaluate.
88
+ * @returns `true` if satisfied; `false` otherwise.
89
+ */
90
+ public isSatisfiedBy(candidate: T): boolean {
91
+ return this.satisfiedBy(candidate);
92
+ }
93
+
94
+ /**
95
+ * @description
96
+ * Returns a new specification satisfied only when **both** this and `other`
97
+ * are satisfied (logical AND).
98
+ *
99
+ * @example
100
+ * ```typescript
101
+ * const spec = new MinimumAmountSpec(100).and(new IsPaidSpec());
102
+ * ```
103
+ */
104
+ public and(other: ISpecification<T>): ISpecification<T> {
105
+ return new AndSpecification(this, other as BaseSpecification<T>);
106
+ }
107
+
108
+ /**
109
+ * @description
110
+ * Returns a new specification satisfied when **either** this or `other`
111
+ * is satisfied (logical OR).
112
+ *
113
+ * @example
114
+ * ```typescript
115
+ * const spec = new IsVipSpec().or(new HasCouponSpec());
116
+ * ```
117
+ */
118
+ public or(other: ISpecification<T>): ISpecification<T> {
119
+ return new OrSpecification(this, other as BaseSpecification<T>);
120
+ }
121
+
122
+ /**
123
+ * @description
124
+ * Returns a new specification satisfied when this specification is **not**
125
+ * satisfied (logical NOT).
126
+ *
127
+ * @example
128
+ * ```typescript
129
+ * const spec = new IsFraudSpec().not();
130
+ * ```
131
+ */
132
+ public not(): ISpecification<T> {
133
+ return new NotSpecification(this);
134
+ }
135
+ }
136
+
137
+ // ─── Composite Implementations ───────────────────────────────────────────────
138
+
139
+ /** @internal */
140
+ class AndSpecification<T> extends BaseSpecification<T> {
141
+ constructor(
142
+ private readonly left: BaseSpecification<T>,
143
+ private readonly right: BaseSpecification<T>,
144
+ ) {
145
+ super();
146
+ }
147
+
148
+ protected satisfiedBy(candidate: T): boolean {
149
+ return (
150
+ this.left.isSatisfiedBy(candidate) && this.right.isSatisfiedBy(candidate)
151
+ );
152
+ }
153
+ }
154
+
155
+ /** @internal */
156
+ class OrSpecification<T> extends BaseSpecification<T> {
157
+ constructor(
158
+ private readonly left: BaseSpecification<T>,
159
+ private readonly right: BaseSpecification<T>,
160
+ ) {
161
+ super();
162
+ }
163
+
164
+ protected satisfiedBy(candidate: T): boolean {
165
+ return (
166
+ this.left.isSatisfiedBy(candidate) || this.right.isSatisfiedBy(candidate)
167
+ );
168
+ }
169
+ }
170
+
171
+ /** @internal */
172
+ class NotSpecification<T> extends BaseSpecification<T> {
173
+ constructor(private readonly wrapped: BaseSpecification<T>) {
174
+ super();
175
+ }
176
+
177
+ protected satisfiedBy(candidate: T): boolean {
178
+ return !this.wrapped.isSatisfiedBy(candidate);
179
+ }
180
+ }
@@ -2,8 +2,8 @@
2
2
  /** biome-ignore-all lint/complexity/noThisInStatic: Required for polymorphic `this` in base factory (DDD pattern). */
3
3
 
4
4
  import { AutoMapper } from "../helpers/auto-mapper";
5
- import { DomainError } from "../helpers/domain-error";
6
5
  import { GettersAndSetters } from "../helpers/getters-setters";
6
+ import { DomainError } from "../libs/domain-error";
7
7
  import { Result } from "../libs/result";
8
8
  import type { Adapter, IAdapter } from "../types/adapter.types";
9
9
  import type { IResult } from "../types/result.types";
@@ -1,4 +1,4 @@
1
- import { DomainError } from "../helpers";
1
+ import { DomainError } from "../libs";
2
2
 
3
3
  /**
4
4
  * @description
@@ -1,7 +1,5 @@
1
- export { BrowserEventManager, type WindowLike } from "./browser-event-manager";
2
- export { BaseDomainEvent } from "./domain-event";
1
+ export { BrowserEventManager } from "./browser-event-manager";
3
2
  export { EventBus } from "./event-bus";
4
3
  export { EventContext } from "./event-context";
5
4
  export { BaseEventManager } from "./event-manager";
6
- export { ValidateEventName } from "./event-utils";
7
5
  export { ServerEventManager } from "./server-event-manager";
@@ -1,4 +1,5 @@
1
1
  import { EventEmitter } from "node:events";
2
+
2
3
  import type { DomainEventPayload, EventEntry } from "../types/event.types";
3
4
  import { BaseEventManager } from "./event-manager";
4
5
  import { ValidateEventName } from "./event-utils";
@@ -5,7 +5,7 @@ import type { AnyObject } from "../types/utils.types";
5
5
  * @description
6
6
  * Defines the shape of data used for mapping an entity's properties.
7
7
  */
8
- export interface EntityAutoMapperPayload {
8
+ interface EntityAutoMapperPayload {
9
9
  id: string;
10
10
  createdAt: Date;
11
11
  updatedAt: Date;
@@ -1,16 +1,16 @@
1
1
  import { ID } from "../core/id";
2
+ import { DomainError } from "../libs/domain-error";
2
3
  import utils, { type Utils } from "../libs/utils";
3
4
  import validator, { type Validator } from "../libs/validator";
4
5
  import type { UID } from "../types/uid.types";
5
6
  import type { AnyObject } from "../types/utils.types";
6
- import { DomainError } from "./domain-error";
7
7
 
8
8
  /**
9
9
  * @description
10
10
  * Discriminator indicating which domain primitive this instance belongs to.
11
11
  * Used internally to apply different mutation behaviors for `ValueObject` vs `Entity`.
12
12
  */
13
- export type ParentKind = "ValueObject" | "Entity";
13
+ type ParentKind = "ValueObject" | "Entity";
14
14
 
15
15
  /**
16
16
  * @description
@@ -20,7 +20,7 @@ export type ParentKind = "ValueObject" | "Entity";
20
20
  * Disabling getters or setters can be useful when enforcing strict encapsulation
21
21
  * or building read-only / write-only domain primitives.
22
22
  */
23
- export interface GettersSettersConfig {
23
+ interface GettersSettersConfig {
24
24
  /**
25
25
  * @description
26
26
  * When `true`, calling `get()` on this instance will throw a `DomainError`.
@@ -245,10 +245,10 @@ export abstract class GettersAndSetters<Props> {
245
245
  * const raw = stringVo.get('value'); // 'hello'
246
246
  * ```
247
247
  */
248
- public get<Key extends keyof Props>(key: Key): Readonly<Props[Key]>;
248
+ public get<Key extends keyof Props>(key: Key): Props[Key];
249
249
  public get(
250
250
  key: "value",
251
- ): Readonly<"value" extends keyof Props ? Props["value"] : Props>;
251
+ ): "value" extends keyof Props ? Props["value"] : Props;
252
252
  /** biome-ignore lint/suspicious/noExplicitAny: TS cannot correlate overload branches with runtime narrowing. */
253
253
  public get(key: any): any {
254
254
  if (this.config.disableGetters) {
@@ -1,7 +1 @@
1
- export type { EntityAutoMapperPayload } from "./auto-mapper";
2
- export { AutoMapper } from "./auto-mapper";
3
- export type { CreateManyResult } from "./domain-classes";
4
- export { DomainClasses } from "./domain-classes";
5
- export { DomainError } from "./domain-error";
6
- export type { GettersSettersConfig, ParentKind } from "./getters-setters";
7
- export { GettersAndSetters } from "./getters-setters";
1
+ export { Iterator } from "./iterator";
@@ -1,77 +1,79 @@
1
1
  // ── Core domain primitives ─────────────────────────────────────────────────
2
- export { Aggregate } from "./core/aggregate";
3
- export { Entity } from "./core/entity";
4
- export { ID, Id } from "./core/id";
5
- export { BaseRepository } from "./core/repository";
6
- export { ValueObject } from "./core/value-object";
2
+ export {
3
+ Aggregate,
4
+ BaseDomainEvent,
5
+ BaseRepository,
6
+ BaseSpecification,
7
+ Entity,
8
+ ID,
9
+ Id,
10
+ ValueObject,
11
+ } from "./core";
12
+
7
13
  // ── Event system ───────────────────────────────────────────────────────────
8
- export type { WindowLike } from "./events/browser-event-manager";
9
- export { BrowserEventManager } from "./events/browser-event-manager";
10
- export { BaseDomainEvent } from "./events/domain-event";
11
- export { EventBus } from "./events/event-bus";
12
- export { EventContext } from "./events/event-context";
13
- export { BaseEventManager } from "./events/event-manager";
14
- export { ValidateEventName } from "./events/event-utils";
15
- export { ServerEventManager } from "./events/server-event-manager";
14
+ export {
15
+ BaseEventManager,
16
+ BrowserEventManager,
17
+ EventBus,
18
+ EventContext,
19
+ ServerEventManager,
20
+ } from "./events";
21
+
16
22
  // ── Helpers ────────────────────────────────────────────────────────────────
17
- export type { EntityAutoMapperPayload } from "./helpers/auto-mapper";
18
- export { AutoMapper } from "./helpers/auto-mapper";
19
- export type { CreateManyResult } from "./helpers/domain-classes";
20
- export { DomainClasses } from "./helpers/domain-classes";
21
- export { DomainError } from "./helpers/domain-error";
22
- export type {
23
- GettersSettersConfig,
24
- ParentKind,
25
- } from "./helpers/getters-setters";
26
- export { GettersAndSetters } from "./helpers/getters-setters";
23
+ export { Iterator } from "./helpers";
27
24
 
28
25
  // ── Libs ───────────────────────────────────────────────────────────────────
29
- export { Iterator } from "./libs/iterator";
30
- export { Result } from "./libs/result";
31
- export { Utils } from "./libs/utils";
32
- export { Validator } from "./libs/validator";
26
+ export {
27
+ DomainClasses,
28
+ DomainError,
29
+ Result,
30
+ Utils,
31
+ UUID,
32
+ utils,
33
+ Validator,
34
+ validator,
35
+ } from "./libs";
33
36
 
34
37
  // ── Types ──────────────────────────────────────────────────────────────────
35
- export type { Adapter, IAdapter } from "./types/adapter.types";
36
- export type { ICommand, IQuery, IUseCase } from "./types/command.types";
37
- export type {
38
- AggregateConstructor,
39
- EntityConstructor,
40
- EntityProps,
41
- IEntityProps,
42
- IEntitySettings,
43
- } from "./types/entity.types";
44
38
  export type {
39
+ Adapter,
40
+ AnyObject,
45
41
  DomainEvent,
46
- DomainEventPayload,
47
- EventEntry,
42
+ EventSubscriber,
43
+ IAdapter,
44
+ ICommand,
48
45
  IEventBus,
49
- } from "./types/event.types";
50
- export type { IIterator, IIteratorConfig } from "./types/iterator.types";
51
- export type {
52
- IResult,
53
- IResultExecuteFn,
54
- IResultHook,
55
- IResultObject,
56
- IResultOption,
57
- } from "./types/result.types";
58
- export type { UID } from "./types/uid.types";
59
- export type {
60
- AnyObject,
61
- BuiltIns,
62
- CalcOpt,
63
- Primitive,
46
+ IQuery,
47
+ ISpecification,
48
+ IUseCase,
64
49
  ReadonlyDeep,
65
- } from "./types/utils.types";
66
- export type {
67
- IValueObjectSettings,
68
- ValueObjectConstructor,
69
- } from "./types/value-object.types";
50
+ UID,
51
+ } from "./types";
70
52
 
71
53
  // ── Utils ──────────────────────────────────────────────────────────────────
72
- export { DeepFreeze, StableStringify } from "./utils/object.utils";
73
- export { InvalidPropsType } from "./utils/type.utils";
74
-
75
- import validator from "./libs/validator";
76
-
77
- export { validator };
54
+ export {
55
+ DecrementTime,
56
+ DeepFreeze,
57
+ Divide,
58
+ EnsureNumber,
59
+ Float,
60
+ IncrementTime,
61
+ IsNaN,
62
+ Multiply,
63
+ ONE_DAY,
64
+ ONE_HOUR,
65
+ ONE_MINUTE,
66
+ ONE_MONTH,
67
+ ONE_WEEK,
68
+ ONE_YEAR,
69
+ RemoveChars,
70
+ RemoveSpaces,
71
+ ReplaceChars,
72
+ StableStringify,
73
+ Stringify,
74
+ Subtract,
75
+ Sum,
76
+ ToDecimal,
77
+ ToLong,
78
+ ToPrecision,
79
+ } from "./utils";
@@ -1,7 +1,7 @@
1
- import { Iterator } from "../libs/iterator";
2
- import { Result } from "../libs/result";
1
+ import { Iterator } from "../helpers/iterator";
3
2
  import type { IIterator } from "../types/iterator.types";
4
3
  import type { IResult } from "../types/result.types";
4
+ import { Result } from "./result";
5
5
 
6
6
  /**
7
7
  * @description
@@ -10,7 +10,7 @@ import type { IResult } from "../types/result.types";
10
10
  * Contains both an iterator over each individual creation result and a combined
11
11
  * result that reflects the overall success or failure of the batch operation.
12
12
  */
13
- export interface CreateManyResult {
13
+ interface CreateManyResult {
14
14
  /**
15
15
  * @description
16
16
  * An iterator over each individual `Result` produced during the batch creation.
@@ -1,5 +1,11 @@
1
1
  export { UUID } from "./crypto";
2
- export { Iterator } from "./iterator";
2
+ export { DomainClasses } from "./domain-classes";
3
+ export { DomainError } from "./domain-error";
3
4
  export { Result } from "./result";
4
5
  export { Utils } from "./utils";
5
6
  export { Validator } from "./validator";
7
+
8
+ import utils from "./utils";
9
+ import validator from "./validator";
10
+
11
+ export { utils, validator };
@@ -1,3 +1,4 @@
1
+ import { Iterator } from "../helpers/iterator";
1
2
  import type { ICommand } from "../types/command.types";
2
3
  import type { IIterator } from "../types/iterator.types";
3
4
  import type {
@@ -8,7 +9,6 @@ import type {
8
9
  IResultOption,
9
10
  } from "../types/result.types";
10
11
  import type { AnyObject } from "../types/utils.types";
11
- import { Iterator } from "./iterator";
12
12
 
13
13
  /**
14
14
  * @description
@@ -1,9 +1,14 @@
1
- import type { CalcOpt } from "../types/utils.types";
2
1
  import { DecrementTime, IncrementTime } from "../utils/date.utils";
3
2
  import { Divide, Multiply, Subtract, Sum } from "../utils/number.utils";
4
3
  import { RemoveChars, RemoveSpaces, ReplaceChars } from "../utils/string.utils";
5
4
  import validator, { type Validator } from "./validator";
6
5
 
6
+ /**
7
+ * @description
8
+ * Options for calculations, such as specifying precision.
9
+ */
10
+ type CalcOpt = { fractionDigits: number };
11
+
7
12
  /**
8
13
  * @description
9
14
  * Utility class providing various helper methods for date, number, and string manipulations.
@@ -1,4 +1,5 @@
1
1
  import type { Aggregate, Entity, ID, ValueObject } from "../core";
2
+ import type { BaseSpecification } from "../core/specification";
2
3
  import type { AnyObject } from "../types/utils.types";
3
4
  import { Stringify } from "../utils/string.utils";
4
5
 
@@ -170,6 +171,18 @@ export class Validator {
170
171
  );
171
172
  }
172
173
 
174
+ /**
175
+ * @description
176
+ * Checks if the provided value is a `BaseSpecification` instance.
177
+ *
178
+ * @param props The value to check.
179
+ * @returns True if the value is a Specification, false otherwise.
180
+ */
181
+ public isSpecification(props: unknown): props is BaseSpecification<never> {
182
+ if (props === null || typeof props !== "object") return false;
183
+ return (props as AnyObject).__kind === "Specification";
184
+ }
185
+
173
186
  /**
174
187
  * @description
175
188
  * Checks if the provided value is a symbol.
@@ -32,6 +32,4 @@ export interface IQuery<Input = void, Output = void> {
32
32
  * @template Input The input type.
33
33
  * @template Output The output type.
34
34
  */
35
- export type IUseCase<Input = void, Output = void> =
36
- | ICommand<Input, Output>
37
- | IQuery<Input, Output>;
35
+ export type IUseCase<Input = void, Output = void> = ICommand<Input, Output>;
@@ -1,26 +1,6 @@
1
- export type {
2
- Adapter,
3
- IAdapter,
4
- } from "./adapter.types";
5
-
1
+ export type { Adapter, IAdapter } from "./adapter.types";
6
2
  export type { ICommand, IQuery, IUseCase } from "./command.types";
7
- export type { IEntityProps, IEntitySettings } from "./entity.types";
8
- export type { IIterator, IIteratorConfig } from "./iterator.types";
9
- export type {
10
- IResult,
11
- IResultExecuteFn,
12
- IResultHook,
13
- IResultObject,
14
- IResultOption,
15
- } from "./result.types";
16
-
3
+ export type { DomainEvent, EventSubscriber, IEventBus } from "./event.types";
4
+ export type { ISpecification } from "./specification.types";
17
5
  export type { UID } from "./uid.types";
18
- export type {
19
- AnyObject,
20
- BuiltIns,
21
- CalcOpt,
22
- Primitive,
23
- ReadonlyDeep,
24
- } from "./utils.types";
25
-
26
- export type { IValueObjectSettings } from "./value-object.types";
6
+ export type { AnyObject, ReadonlyDeep } from "./utils.types";
@@ -0,0 +1,45 @@
1
+ /**
2
+ * @description
3
+ * Core contract for the Specification pattern.
4
+ *
5
+ * A specification encapsulates a single named business rule that can be evaluated
6
+ * against a candidate object. The rule is reusable, composable, and lives in the
7
+ * domain layer — not scattered across use cases or services.
8
+ *
9
+ * @template T The type of the candidate being evaluated.
10
+ */
11
+ export interface ISpecification<T> {
12
+ /**
13
+ * @description
14
+ * Evaluates whether the candidate satisfies this specification.
15
+ *
16
+ * @param candidate The domain object to evaluate.
17
+ * @returns `true` if satisfied; `false` otherwise.
18
+ */
19
+ isSatisfiedBy(candidate: T): boolean;
20
+
21
+ /**
22
+ * @description
23
+ * Returns a new specification that is satisfied only when **both** this and
24
+ * the provided specification are satisfied (logical AND).
25
+ *
26
+ * @param other The specification to AND with.
27
+ */
28
+ and(other: ISpecification<T>): ISpecification<T>;
29
+
30
+ /**
31
+ * @description
32
+ * Returns a new specification that is satisfied when **either** this or the
33
+ * provided specification is satisfied (logical OR).
34
+ *
35
+ * @param other The specification to OR with.
36
+ */
37
+ or(other: ISpecification<T>): ISpecification<T>;
38
+
39
+ /**
40
+ * @description
41
+ * Returns a new specification that is satisfied when this specification is
42
+ * **not** satisfied (logical NOT).
43
+ */
44
+ not(): ISpecification<T>;
45
+ }
@@ -8,13 +8,7 @@ export type AnyObject = Record<string, unknown>;
8
8
  * @description
9
9
  * Built-in types that are excluded from recursive transformations.
10
10
  */
11
- export type BuiltIns = Primitive | Date | RegExp;
12
-
13
- /**
14
- * @description
15
- * Options for calculations, such as specifying precision.
16
- */
17
- export type CalcOpt = { fractionDigits: number };
11
+ type BuiltIns = Primitive | Date | RegExp;
18
12
 
19
13
  /**
20
14
  * @description
@@ -41,14 +35,7 @@ type HasMultipleCallSignatures<
41
35
  * @description
42
36
  * Primitive types that are not recursively transformed.
43
37
  */
44
- export type Primitive =
45
- | null
46
- | undefined
47
- | string
48
- | number
49
- | boolean
50
- | symbol
51
- | bigint;
38
+ type Primitive = null | undefined | string | number | boolean | symbol | bigint;
52
39
 
53
40
  /**
54
41
  * @description
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "drimion",
3
- "version": "0.1.12",
3
+ "version": "0.2.0",
4
4
  "description": "Headless DDD primitives CLI for TypeScript — own your domain",
5
5
  "keywords": [
6
6
  "ddd",
File without changes
File without changes
File without changes