@libs-for-dev/nestjs-ddd-library 1.1.1 → 1.2.1
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 +124 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.mjs +1 -1
- package/package.json +10 -5
package/README.md
CHANGED
|
@@ -10,6 +10,9 @@ A comprehensive library providing Domain-Driven Design (DDD) building blocks for
|
|
|
10
10
|
- **Value Objects**: Immutable objects that contain attributes but have no conceptual identity with examples:
|
|
11
11
|
- **UUID**: Built-in UUID v7 generation and validation
|
|
12
12
|
- **Email**: Email value object with validation
|
|
13
|
+
- **Money**: Money value object with amount and currency (ISO 4217) validation
|
|
14
|
+
- **PhoneNumber**: Phone number value object with mobile phone validation
|
|
15
|
+
- **Url**: URL value object with protocol validation
|
|
13
16
|
|
|
14
17
|
## Installation
|
|
15
18
|
|
|
@@ -74,8 +77,65 @@ if (emailResult.isOk()) {
|
|
|
74
77
|
}
|
|
75
78
|
```
|
|
76
79
|
|
|
80
|
+
#### Money
|
|
81
|
+
|
|
82
|
+
```typescript
|
|
83
|
+
import { Money } from '@libs-for-dev/nestjs-ddd-library';
|
|
84
|
+
|
|
85
|
+
// Create a Money value object
|
|
86
|
+
const moneyResult = Money.create(100.50, 'USD');
|
|
87
|
+
|
|
88
|
+
if (moneyResult.isOk()) {
|
|
89
|
+
const money = moneyResult.unwrap();
|
|
90
|
+
console.log(money.amount); // 100.50
|
|
91
|
+
console.log(money.currency); // USD
|
|
92
|
+
console.log(money.value); // { amount: 100.50, currency: 'USD' }
|
|
93
|
+
} else {
|
|
94
|
+
// Handle error
|
|
95
|
+
console.error(moneyResult.unwrapErr());
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
#### PhoneNumber
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
import { PhoneNumber } from '@libs-for-dev/nestjs-ddd-library';
|
|
103
|
+
|
|
104
|
+
// Create a PhoneNumber value object
|
|
105
|
+
const phoneResult = PhoneNumber.create('+1234567890');
|
|
106
|
+
|
|
107
|
+
if (phoneResult.isOk()) {
|
|
108
|
+
const phone = phoneResult.unwrap();
|
|
109
|
+
console.log(phone.value); // +1234567890
|
|
110
|
+
} else {
|
|
111
|
+
// Handle error
|
|
112
|
+
console.error(phoneResult.unwrapErr());
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
#### Url
|
|
117
|
+
|
|
118
|
+
```typescript
|
|
119
|
+
import { Url } from '@libs-for-dev/nestjs-ddd-library';
|
|
120
|
+
|
|
121
|
+
// Create a Url value object
|
|
122
|
+
const urlResult = Url.create('https://example.com');
|
|
123
|
+
|
|
124
|
+
if (urlResult.isOk()) {
|
|
125
|
+
const url = urlResult.unwrap();
|
|
126
|
+
console.log(url.value); // https://example.com
|
|
127
|
+
} else {
|
|
128
|
+
// Handle error
|
|
129
|
+
console.error(urlResult.unwrapErr());
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
77
133
|
### Entities
|
|
78
134
|
|
|
135
|
+
`AbstractEntity` provides immutable properties through `DeepReadonly`. All properties, including nested objects, are readonly and cannot be modified directly. Changes must be made through entity domain methods.
|
|
136
|
+
|
|
137
|
+
#### Basic Usage
|
|
138
|
+
|
|
79
139
|
```typescript
|
|
80
140
|
import { AbstractEntity, Uuid } from '@libs-for-dev/nestjs-ddd-library';
|
|
81
141
|
|
|
@@ -118,6 +178,70 @@ user.updateName('Jane Doe');
|
|
|
118
178
|
console.log(user.props.name); // Jane Doe
|
|
119
179
|
```
|
|
120
180
|
|
|
181
|
+
#### Readonly Properties with Nested Objects
|
|
182
|
+
|
|
183
|
+
Properties returned by `props` getter are deeply readonly, including nested objects. This ensures immutability and prevents accidental modifications:
|
|
184
|
+
|
|
185
|
+
```typescript
|
|
186
|
+
import { AbstractEntity, Uuid } from '@libs-for-dev/nestjs-ddd-library';
|
|
187
|
+
|
|
188
|
+
interface UserProfileProps {
|
|
189
|
+
personal: {
|
|
190
|
+
firstName: string;
|
|
191
|
+
lastName: string;
|
|
192
|
+
};
|
|
193
|
+
settings: {
|
|
194
|
+
theme: string;
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
class UserProfile extends AbstractEntity<Uuid, UserProfileProps> {
|
|
199
|
+
public static create(id: Uuid, props: UserProfileProps): UserProfile {
|
|
200
|
+
return new UserProfile(id, props);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
public updateFirstName(firstName: string): void {
|
|
204
|
+
this.propsData = {
|
|
205
|
+
props: {
|
|
206
|
+
...this.props,
|
|
207
|
+
personal: {
|
|
208
|
+
...this.props.personal,
|
|
209
|
+
firstName
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Usage
|
|
217
|
+
const profile = UserProfile.create(Uuid.generate(), {
|
|
218
|
+
personal: {
|
|
219
|
+
firstName: 'John',
|
|
220
|
+
lastName: 'Doe'
|
|
221
|
+
},
|
|
222
|
+
settings: {
|
|
223
|
+
theme: 'dark'
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
// All properties are readonly, including nested objects
|
|
228
|
+
// This will throw an error at runtime:
|
|
229
|
+
// profile.props.personal.firstName = 'Jane'; // ❌ Error: Cannot assign to read-only property
|
|
230
|
+
|
|
231
|
+
// This will also throw an error:
|
|
232
|
+
// profile.props.settings.theme = 'light'; // ❌ Error: Cannot assign to read-only property
|
|
233
|
+
|
|
234
|
+
// Changes must be made through domain methods
|
|
235
|
+
profile.updateFirstName('Jane');
|
|
236
|
+
console.log(profile.props.personal.firstName); // Jane
|
|
237
|
+
|
|
238
|
+
// Previous references remain unchanged (immutability)
|
|
239
|
+
const oldProps = profile.props;
|
|
240
|
+
profile.updateFirstName('Bob');
|
|
241
|
+
console.log(oldProps.personal.firstName); // Jane (unchanged)
|
|
242
|
+
console.log(profile.props.personal.firstName); // Bob (updated)
|
|
243
|
+
```
|
|
244
|
+
|
|
121
245
|
## Domain Events
|
|
122
246
|
|
|
123
247
|
The library integrates with NestJS CQRS to support domain events:
|
package/dist/index.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
let e=require(`class-validator`),t=require(`oxide.ts`),n=require(`fast-equals`),r=require(`ui7`),i=require(`@nestjs/cqrs`);var a=class extends Error{static name=`NotAMoneyError`;constructor(e,t){super(`Amount ${e} with currency ${t} is not valid money`)}},o=class extends Error{static name=`NotAPhoneNumberError`;constructor(e){super(`${e} is not a valid phone number`)}},s=class extends Error{static name=`NotAnEmailError`;constructor(e){super(`${e} is not a valid email`)}},c=class extends Error{static name=`NotAnUrlError`;constructor(e){super(`${e} is not a valid URL`)}},l=class extends Error{static name=`NotAnUuidError`;constructor(e){super(`${e} is not a valid UUID`)}},u=class{constructor(e){this.props=e}equals(e){return this.constructor.name===e.constructor.name?(0,n.deepEqual)(e.props,this.props):!1}},d=class n extends u{get value(){return this.props.value}static create(e){return this.isValid(e)?(0,t.Ok)(new n({value:e})):(0,t.Err)(new s(e))}static isValid(t){return(0,e.isEmail)(t)}},f=class e extends u{get amount(){return this.props.amount}get currency(){return this.props.currency}get value(){return{amount:this.amount,currency:this.currency}}static create(n,r){return this.isValid(n,r)?(0,t.Ok)(new e({amount:n,currency:r})):(0,t.Err)(new a(n,r))}static isValid(e,t){return!(e<0||!Number.isFinite(e)||!/^[A-Z]{3}$/u.test(t))}},p=class n extends u{get value(){return this.props.value}static create(e){return this.isValid(e)?(0,t.Ok)(new n({value:e})):(0,t.Err)(new o(e))}static isValid(t){return(0,e.isMobilePhone)(t)}},m=class n extends u{get value(){return this.props.value}static create(e){return this.isValid(e)?(0,t.Ok)(new n({value:e})):(0,t.Err)(new c(e))}static isValid(t){return(0,e.isURL)(t,{require_protocol:!0})}},h=class n extends u{get value(){return this.props.value}static create(r){return(0,e.isUUID)(r)?(0,t.Ok)(new n({value:r})):(0,t.Err)(new l(r))}static generate(){return new n({value:(0,r.v7)()})}};const g=e=>{let t=Object.getOwnPropertyNames(e);for(let n of t){let t=e[n];t
|
|
1
|
+
let e=require(`class-validator`),t=require(`oxide.ts`),n=require(`fast-equals`),r=require(`ui7`),i=require(`@nestjs/cqrs`);var a=class extends Error{static name=`NotAMoneyError`;constructor(e,t){super(`Amount ${e} with currency ${t} is not valid money`)}},o=class extends Error{static name=`NotAPhoneNumberError`;constructor(e){super(`${e} is not a valid phone number`)}},s=class extends Error{static name=`NotAnEmailError`;constructor(e){super(`${e} is not a valid email`)}},c=class extends Error{static name=`NotAnUrlError`;constructor(e){super(`${e} is not a valid URL`)}},l=class extends Error{static name=`NotAnUuidError`;constructor(e){super(`${e} is not a valid UUID`)}},u=class{constructor(e){this.props=e}equals(e){return this.constructor.name===e.constructor.name?(0,n.deepEqual)(e.props,this.props):!1}},d=class n extends u{get value(){return this.props.value}static create(e){return this.isValid(e)?(0,t.Ok)(new n({value:e})):(0,t.Err)(new s(e))}static isValid(t){return(0,e.isEmail)(t)}},f=class e extends u{get amount(){return this.props.amount}get currency(){return this.props.currency}get value(){return{amount:this.amount,currency:this.currency}}static create(n,r){return this.isValid(n,r)?(0,t.Ok)(new e({amount:n,currency:r})):(0,t.Err)(new a(n,r))}static isValid(e,t){return!(e<0||!Number.isFinite(e)||!/^[A-Z]{3}$/u.test(t))}},p=class n extends u{get value(){return this.props.value}static create(e){return this.isValid(e)?(0,t.Ok)(new n({value:e})):(0,t.Err)(new o(e))}static isValid(t){return(0,e.isMobilePhone)(t)}},m=class n extends u{get value(){return this.props.value}static create(e){return this.isValid(e)?(0,t.Ok)(new n({value:e})):(0,t.Err)(new c(e))}static isValid(t){return(0,e.isURL)(t,{require_protocol:!0})}},h=class n extends u{get value(){return this.props.value}static create(r){return(0,e.isUUID)(r)?(0,t.Ok)(new n({value:r})):(0,t.Err)(new l(r))}static generate(){return new n({value:(0,r.v7)()})}};const g=e=>typeof e==`object`&&!!e&&typeof e!=`function`,_=e=>{let t=Object.getOwnPropertyNames(e);for(let n of t){let t=e[n];g(t)&&_(t)}return Object.freeze(e)};var v=class extends i.AggregateRoot{id;get props(){return _(this.propsData.props)}propsData;constructor(e,t){super(),this.id=e,this.propsData={props:t}}equals(e){return this.constructor.name===e.constructor.name?this.id===e.id:!1}};exports.AbstractEntity=v,exports.AbstractValueObject=u,exports.Email=d,exports.Money=f,exports.NotAMoneyError=a,exports.NotAPhoneNumberError=o,exports.NotAnEmailError=s,exports.NotAnUrlError=c,exports.NotAnUuidError=l,exports.PhoneNumber=p,exports.Url=m,exports.Uuid=h,exports.deepReadonly=_;
|
package/dist/index.d.cts
CHANGED
|
@@ -85,7 +85,7 @@ declare class Uuid extends AbstractValueObject<string> {
|
|
|
85
85
|
}
|
|
86
86
|
//#endregion
|
|
87
87
|
//#region src/deep-readonly.d.ts
|
|
88
|
-
type DeepReadonly<T> = Readonly<{ [K in keyof T]: T[K] extends (number | string | symbol) ? Readonly<T[K]> : T[K] extends (infer A)[] ? readonly DeepReadonly<A>[] : DeepReadonly<T[K]> }>;
|
|
88
|
+
type DeepReadonly<T> = Readonly<{ [K in keyof T]: T[K] extends ((...arguments_: any[]) => any) ? T[K] : T[K] extends (number | string | symbol) ? Readonly<T[K]> : T[K] extends (infer A)[] ? readonly DeepReadonly<A>[] : DeepReadonly<T[K]> }>;
|
|
89
89
|
declare const deepReadonly: <T>(object: T) => DeepReadonly<T>;
|
|
90
90
|
//#endregion
|
|
91
91
|
//#region src/abstract-entity.d.ts
|
package/dist/index.d.mts
CHANGED
|
@@ -85,7 +85,7 @@ declare class Uuid extends AbstractValueObject<string> {
|
|
|
85
85
|
}
|
|
86
86
|
//#endregion
|
|
87
87
|
//#region src/deep-readonly.d.ts
|
|
88
|
-
type DeepReadonly<T> = Readonly<{ [K in keyof T]: T[K] extends (number | string | symbol) ? Readonly<T[K]> : T[K] extends (infer A)[] ? readonly DeepReadonly<A>[] : DeepReadonly<T[K]> }>;
|
|
88
|
+
type DeepReadonly<T> = Readonly<{ [K in keyof T]: T[K] extends ((...arguments_: any[]) => any) ? T[K] : T[K] extends (number | string | symbol) ? Readonly<T[K]> : T[K] extends (infer A)[] ? readonly DeepReadonly<A>[] : DeepReadonly<T[K]> }>;
|
|
89
89
|
declare const deepReadonly: <T>(object: T) => DeepReadonly<T>;
|
|
90
90
|
//#endregion
|
|
91
91
|
//#region src/abstract-entity.d.ts
|
package/dist/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{isEmail as e,isMobilePhone as t,isURL as n,isUUID as r}from"class-validator";import{Err as i,Ok as a}from"oxide.ts";import{deepEqual as o}from"fast-equals";import{v7 as s}from"ui7";import{AggregateRoot as c}from"@nestjs/cqrs";var l=class extends Error{static name=`NotAMoneyError`;constructor(e,t){super(`Amount ${e} with currency ${t} is not valid money`)}},u=class extends Error{static name=`NotAPhoneNumberError`;constructor(e){super(`${e} is not a valid phone number`)}},d=class extends Error{static name=`NotAnEmailError`;constructor(e){super(`${e} is not a valid email`)}},f=class extends Error{static name=`NotAnUrlError`;constructor(e){super(`${e} is not a valid URL`)}},p=class extends Error{static name=`NotAnUuidError`;constructor(e){super(`${e} is not a valid UUID`)}},m=class{constructor(e){this.props=e}equals(e){return this.constructor.name===e.constructor.name?o(e.props,this.props):!1}},h=class t extends m{get value(){return this.props.value}static create(e){return this.isValid(e)?a(new t({value:e})):i(new d(e))}static isValid(t){return e(t)}},g=class e extends m{get amount(){return this.props.amount}get currency(){return this.props.currency}get value(){return{amount:this.amount,currency:this.currency}}static create(t,n){return this.isValid(t,n)?a(new e({amount:t,currency:n})):i(new l(t,n))}static isValid(e,t){return!(e<0||!Number.isFinite(e)||!/^[A-Z]{3}$/u.test(t))}},_=class e extends m{get value(){return this.props.value}static create(t){return this.isValid(t)?a(new e({value:t})):i(new u(t))}static isValid(e){return t(e)}},v=class e extends m{get value(){return this.props.value}static create(t){return this.isValid(t)?a(new e({value:t})):i(new f(t))}static isValid(e){return n(e,{require_protocol:!0})}},y=class e extends m{get value(){return this.props.value}static create(t){return r(t)?a(new e({value:t})):i(new p(t))}static generate(){return new e({value:s()})}};const b=e=>{let t=Object.getOwnPropertyNames(e);for(let n of t){let t=e[n];t
|
|
1
|
+
import{isEmail as e,isMobilePhone as t,isURL as n,isUUID as r}from"class-validator";import{Err as i,Ok as a}from"oxide.ts";import{deepEqual as o}from"fast-equals";import{v7 as s}from"ui7";import{AggregateRoot as c}from"@nestjs/cqrs";var l=class extends Error{static name=`NotAMoneyError`;constructor(e,t){super(`Amount ${e} with currency ${t} is not valid money`)}},u=class extends Error{static name=`NotAPhoneNumberError`;constructor(e){super(`${e} is not a valid phone number`)}},d=class extends Error{static name=`NotAnEmailError`;constructor(e){super(`${e} is not a valid email`)}},f=class extends Error{static name=`NotAnUrlError`;constructor(e){super(`${e} is not a valid URL`)}},p=class extends Error{static name=`NotAnUuidError`;constructor(e){super(`${e} is not a valid UUID`)}},m=class{constructor(e){this.props=e}equals(e){return this.constructor.name===e.constructor.name?o(e.props,this.props):!1}},h=class t extends m{get value(){return this.props.value}static create(e){return this.isValid(e)?a(new t({value:e})):i(new d(e))}static isValid(t){return e(t)}},g=class e extends m{get amount(){return this.props.amount}get currency(){return this.props.currency}get value(){return{amount:this.amount,currency:this.currency}}static create(t,n){return this.isValid(t,n)?a(new e({amount:t,currency:n})):i(new l(t,n))}static isValid(e,t){return!(e<0||!Number.isFinite(e)||!/^[A-Z]{3}$/u.test(t))}},_=class e extends m{get value(){return this.props.value}static create(t){return this.isValid(t)?a(new e({value:t})):i(new u(t))}static isValid(e){return t(e)}},v=class e extends m{get value(){return this.props.value}static create(t){return this.isValid(t)?a(new e({value:t})):i(new f(t))}static isValid(e){return n(e,{require_protocol:!0})}},y=class e extends m{get value(){return this.props.value}static create(t){return r(t)?a(new e({value:t})):i(new p(t))}static generate(){return new e({value:s()})}};const b=e=>typeof e==`object`&&!!e&&typeof e!=`function`,x=e=>{let t=Object.getOwnPropertyNames(e);for(let n of t){let t=e[n];b(t)&&x(t)}return Object.freeze(e)};var S=class extends c{id;get props(){return x(this.propsData.props)}propsData;constructor(e,t){super(),this.id=e,this.propsData={props:t}}equals(e){return this.constructor.name===e.constructor.name?this.id===e.id:!1}};export{S as AbstractEntity,m as AbstractValueObject,h as Email,g as Money,l as NotAMoneyError,u as NotAPhoneNumberError,d as NotAnEmailError,f as NotAnUrlError,p as NotAnUuidError,_ as PhoneNumber,v as Url,y as Uuid,x as deepReadonly};
|
package/package.json
CHANGED
|
@@ -1,17 +1,22 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@libs-for-dev/nestjs-ddd-library",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.1",
|
|
4
4
|
"description": "NestJS DDD library",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ddd-library",
|
|
7
7
|
"nestjs"
|
|
8
8
|
],
|
|
9
|
+
"homepage": "https://gitlab.com/libs-for-dev/nestjs/nestjs-ddd-library",
|
|
9
10
|
"bugs": "https://gitlab.com/libs-for-dev/nestjs/nestjs-ddd-library/-/issues",
|
|
10
|
-
"repository":
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "https://gitlab.com/libs-for-dev/nestjs/nestjs-ddd-library.git"
|
|
14
|
+
},
|
|
11
15
|
"license": "MIT",
|
|
12
16
|
"author": {
|
|
13
17
|
"name": "libs-for-dev"
|
|
14
18
|
},
|
|
19
|
+
"sideEffects": false,
|
|
15
20
|
"type": "module",
|
|
16
21
|
"exports": {
|
|
17
22
|
".": {
|
|
@@ -39,7 +44,7 @@
|
|
|
39
44
|
"test:unit": "vitest run"
|
|
40
45
|
},
|
|
41
46
|
"devDependencies": {
|
|
42
|
-
"@libs-for-dev/eslint-rules": "2.
|
|
47
|
+
"@libs-for-dev/eslint-rules": "2.5.0",
|
|
43
48
|
"@nestjs/common": "^11.1.11",
|
|
44
49
|
"@nestjs/core": "^11.1.11",
|
|
45
50
|
"@nestjs/cqrs": "^11.0.3",
|
|
@@ -53,11 +58,11 @@
|
|
|
53
58
|
"eslint": "9.39.2",
|
|
54
59
|
"fast-equals": "^6.0.0",
|
|
55
60
|
"jiti": "^2.6.1",
|
|
56
|
-
"knip": "^5.
|
|
61
|
+
"knip": "^5.79.0",
|
|
57
62
|
"oxide.ts": "^1.1.0",
|
|
58
63
|
"reflect-metadata": "^0.2.2",
|
|
59
64
|
"scriptlint": "^3.0.0",
|
|
60
|
-
"tsdown": "0.
|
|
65
|
+
"tsdown": "0.19.0-beta.2",
|
|
61
66
|
"typescript": "^5.9.3",
|
|
62
67
|
"ui7": "^0.2.3",
|
|
63
68
|
"vitest": "^4.0.16",
|