@libs-for-dev/nestjs-ddd-library 1.1.0 → 1.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.
Files changed (38) hide show
  1. package/README.md +124 -0
  2. package/dist/index.cjs +1 -0
  3. package/dist/index.d.cts +103 -0
  4. package/dist/index.d.mts +103 -0
  5. package/dist/index.mjs +1 -0
  6. package/package.json +11 -5
  7. package/dist/abstract-entity.d.ts +0 -13
  8. package/dist/abstract-entity.js +0 -20
  9. package/dist/abstract-value-object.d.ts +0 -11
  10. package/dist/abstract-value-object.js +0 -13
  11. package/dist/index.d.ts +0 -3
  12. package/dist/index.js +0 -3
  13. package/dist/tests/deep-readonly.d.ts +0 -4
  14. package/dist/tests/deep-readonly.js +0 -10
  15. package/dist/value-objects/email.d.ts +0 -7
  16. package/dist/value-objects/email.js +0 -18
  17. package/dist/value-objects/errors/index.d.ts +0 -5
  18. package/dist/value-objects/errors/index.js +0 -5
  19. package/dist/value-objects/errors/not-a-money.error.d.ts +0 -4
  20. package/dist/value-objects/errors/not-a-money.error.js +0 -6
  21. package/dist/value-objects/errors/not-a-phone-number.error.d.ts +0 -4
  22. package/dist/value-objects/errors/not-a-phone-number.error.js +0 -6
  23. package/dist/value-objects/errors/not-an-email.error.d.ts +0 -4
  24. package/dist/value-objects/errors/not-an-email.error.js +0 -6
  25. package/dist/value-objects/errors/not-an-url.error.d.ts +0 -4
  26. package/dist/value-objects/errors/not-an-url.error.js +0 -6
  27. package/dist/value-objects/errors/not-an-uuid.error.d.ts +0 -4
  28. package/dist/value-objects/errors/not-an-uuid.error.js +0 -6
  29. package/dist/value-objects/index.d.ts +0 -6
  30. package/dist/value-objects/index.js +0 -6
  31. package/dist/value-objects/money.d.ts +0 -13
  32. package/dist/value-objects/money.js +0 -34
  33. package/dist/value-objects/phone-number.d.ts +0 -8
  34. package/dist/value-objects/phone-number.js +0 -18
  35. package/dist/value-objects/url.d.ts +0 -8
  36. package/dist/value-objects/url.js +0 -18
  37. package/dist/value-objects/uuid.d.ts +0 -7
  38. package/dist/value-objects/uuid.js +0 -19
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 ADDED
@@ -0,0 +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!==void 0&&typeof t==`object`&&g(t)}return Object.freeze(e)};var _=class extends i.AggregateRoot{id;get props(){return g(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=_,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=g;
@@ -0,0 +1,103 @@
1
+ import { Result } from "oxide.ts";
2
+ import { AggregateRoot, IEvent } from "@nestjs/cqrs";
3
+
4
+ //#region src/value-objects/errors/not-a-money.error.d.ts
5
+ declare class NotAMoneyError extends Error {
6
+ static readonly name = "NotAMoneyError";
7
+ constructor(amount: number, currency: string);
8
+ }
9
+ //#endregion
10
+ //#region src/value-objects/errors/not-a-phone-number.error.d.ts
11
+ declare class NotAPhoneNumberError extends Error {
12
+ static readonly name = "NotAPhoneNumberError";
13
+ constructor(phoneNumber: string);
14
+ }
15
+ //#endregion
16
+ //#region src/value-objects/errors/not-an-email.error.d.ts
17
+ declare class NotAnEmailError extends Error {
18
+ static readonly name = "NotAnEmailError";
19
+ constructor(email: string);
20
+ }
21
+ //#endregion
22
+ //#region src/value-objects/errors/not-an-url.error.d.ts
23
+ declare class NotAnUrlError extends Error {
24
+ static readonly name = "NotAnUrlError";
25
+ constructor(url: string);
26
+ }
27
+ //#endregion
28
+ //#region src/value-objects/errors/not-an-uuid.error.d.ts
29
+ declare class NotAnUuidError extends Error {
30
+ static readonly name = "NotAnUuidError";
31
+ constructor(uuid: string);
32
+ }
33
+ //#endregion
34
+ //#region src/abstract-value-object.d.ts
35
+ interface PrimitiveInterface<T extends Date | Primitives> {
36
+ value: T;
37
+ }
38
+ type Primitives = boolean | null | number | string;
39
+ type ValueObjectProps<T> = T extends Date | Primitives ? PrimitiveInterface<T> : T;
40
+ declare abstract class AbstractValueObject<T> {
41
+ readonly props: Readonly<ValueObjectProps<T>>;
42
+ protected constructor(props: Readonly<ValueObjectProps<T>>);
43
+ equals(valueObject: AbstractValueObject<T>): boolean;
44
+ }
45
+ //#endregion
46
+ //#region src/value-objects/email.d.ts
47
+ declare class Email extends AbstractValueObject<string> {
48
+ get value(): string;
49
+ static create(email: string): Result<Email, Error>;
50
+ private static isValid;
51
+ }
52
+ //#endregion
53
+ //#region src/value-objects/money.d.ts
54
+ interface MoneyPropsInterface {
55
+ amount: number;
56
+ currency: string;
57
+ }
58
+ declare class Money extends AbstractValueObject<MoneyPropsInterface> {
59
+ get amount(): number;
60
+ get currency(): string;
61
+ get value(): MoneyPropsInterface;
62
+ static create(amount: number, currency: string): Result<Money, Error>;
63
+ private static isValid;
64
+ }
65
+ //#endregion
66
+ //#region src/value-objects/phone-number.d.ts
67
+ declare class PhoneNumber extends AbstractValueObject<string> {
68
+ get value(): string;
69
+ static create(phoneNumber: string): Result<PhoneNumber, NotAPhoneNumberError>;
70
+ private static isValid;
71
+ }
72
+ //#endregion
73
+ //#region src/value-objects/url.d.ts
74
+ declare class Url extends AbstractValueObject<string> {
75
+ get value(): string;
76
+ static create(url: string): Result<Url, NotAnUrlError>;
77
+ private static isValid;
78
+ }
79
+ //#endregion
80
+ //#region src/value-objects/uuid.d.ts
81
+ declare class Uuid extends AbstractValueObject<string> {
82
+ get value(): string;
83
+ static create(uuid: string): Result<Uuid, Error>;
84
+ static generate(): Uuid;
85
+ }
86
+ //#endregion
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]> }>;
89
+ declare const deepReadonly: <T>(object: T) => DeepReadonly<T>;
90
+ //#endregion
91
+ //#region src/abstract-entity.d.ts
92
+ interface EntityPropsInterface<T> {
93
+ props: T;
94
+ }
95
+ declare abstract class AbstractEntity<Id, Props, Event extends IEvent = IEvent> extends AggregateRoot<Event> {
96
+ readonly id: Id;
97
+ get props(): DeepReadonly<Props>;
98
+ protected propsData: EntityPropsInterface<Props>;
99
+ protected constructor(id: Id, props: Props);
100
+ equals(object: AbstractEntity<Id, Props>): boolean;
101
+ }
102
+ //#endregion
103
+ export { AbstractEntity, AbstractValueObject, DeepReadonly, Email, EntityPropsInterface, Money, MoneyPropsInterface, NotAMoneyError, NotAPhoneNumberError, NotAnEmailError, NotAnUrlError, NotAnUuidError, PhoneNumber, PrimitiveInterface, Primitives, Url, Uuid, deepReadonly };
@@ -0,0 +1,103 @@
1
+ import { Result } from "oxide.ts";
2
+ import { AggregateRoot, IEvent } from "@nestjs/cqrs";
3
+
4
+ //#region src/value-objects/errors/not-a-money.error.d.ts
5
+ declare class NotAMoneyError extends Error {
6
+ static readonly name = "NotAMoneyError";
7
+ constructor(amount: number, currency: string);
8
+ }
9
+ //#endregion
10
+ //#region src/value-objects/errors/not-a-phone-number.error.d.ts
11
+ declare class NotAPhoneNumberError extends Error {
12
+ static readonly name = "NotAPhoneNumberError";
13
+ constructor(phoneNumber: string);
14
+ }
15
+ //#endregion
16
+ //#region src/value-objects/errors/not-an-email.error.d.ts
17
+ declare class NotAnEmailError extends Error {
18
+ static readonly name = "NotAnEmailError";
19
+ constructor(email: string);
20
+ }
21
+ //#endregion
22
+ //#region src/value-objects/errors/not-an-url.error.d.ts
23
+ declare class NotAnUrlError extends Error {
24
+ static readonly name = "NotAnUrlError";
25
+ constructor(url: string);
26
+ }
27
+ //#endregion
28
+ //#region src/value-objects/errors/not-an-uuid.error.d.ts
29
+ declare class NotAnUuidError extends Error {
30
+ static readonly name = "NotAnUuidError";
31
+ constructor(uuid: string);
32
+ }
33
+ //#endregion
34
+ //#region src/abstract-value-object.d.ts
35
+ interface PrimitiveInterface<T extends Date | Primitives> {
36
+ value: T;
37
+ }
38
+ type Primitives = boolean | null | number | string;
39
+ type ValueObjectProps<T> = T extends Date | Primitives ? PrimitiveInterface<T> : T;
40
+ declare abstract class AbstractValueObject<T> {
41
+ readonly props: Readonly<ValueObjectProps<T>>;
42
+ protected constructor(props: Readonly<ValueObjectProps<T>>);
43
+ equals(valueObject: AbstractValueObject<T>): boolean;
44
+ }
45
+ //#endregion
46
+ //#region src/value-objects/email.d.ts
47
+ declare class Email extends AbstractValueObject<string> {
48
+ get value(): string;
49
+ static create(email: string): Result<Email, Error>;
50
+ private static isValid;
51
+ }
52
+ //#endregion
53
+ //#region src/value-objects/money.d.ts
54
+ interface MoneyPropsInterface {
55
+ amount: number;
56
+ currency: string;
57
+ }
58
+ declare class Money extends AbstractValueObject<MoneyPropsInterface> {
59
+ get amount(): number;
60
+ get currency(): string;
61
+ get value(): MoneyPropsInterface;
62
+ static create(amount: number, currency: string): Result<Money, Error>;
63
+ private static isValid;
64
+ }
65
+ //#endregion
66
+ //#region src/value-objects/phone-number.d.ts
67
+ declare class PhoneNumber extends AbstractValueObject<string> {
68
+ get value(): string;
69
+ static create(phoneNumber: string): Result<PhoneNumber, NotAPhoneNumberError>;
70
+ private static isValid;
71
+ }
72
+ //#endregion
73
+ //#region src/value-objects/url.d.ts
74
+ declare class Url extends AbstractValueObject<string> {
75
+ get value(): string;
76
+ static create(url: string): Result<Url, NotAnUrlError>;
77
+ private static isValid;
78
+ }
79
+ //#endregion
80
+ //#region src/value-objects/uuid.d.ts
81
+ declare class Uuid extends AbstractValueObject<string> {
82
+ get value(): string;
83
+ static create(uuid: string): Result<Uuid, Error>;
84
+ static generate(): Uuid;
85
+ }
86
+ //#endregion
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]> }>;
89
+ declare const deepReadonly: <T>(object: T) => DeepReadonly<T>;
90
+ //#endregion
91
+ //#region src/abstract-entity.d.ts
92
+ interface EntityPropsInterface<T> {
93
+ props: T;
94
+ }
95
+ declare abstract class AbstractEntity<Id, Props, Event extends IEvent = IEvent> extends AggregateRoot<Event> {
96
+ readonly id: Id;
97
+ get props(): DeepReadonly<Props>;
98
+ protected propsData: EntityPropsInterface<Props>;
99
+ protected constructor(id: Id, props: Props);
100
+ equals(object: AbstractEntity<Id, Props>): boolean;
101
+ }
102
+ //#endregion
103
+ export { AbstractEntity, AbstractValueObject, DeepReadonly, Email, EntityPropsInterface, Money, MoneyPropsInterface, NotAMoneyError, NotAPhoneNumberError, NotAnEmailError, NotAnUrlError, NotAnUuidError, PhoneNumber, PrimitiveInterface, Primitives, Url, Uuid, deepReadonly };
package/dist/index.mjs ADDED
@@ -0,0 +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!==void 0&&typeof t==`object`&&b(t)}return Object.freeze(e)};var x=class extends c{id;get props(){return b(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{x 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,b as deepReadonly};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@libs-for-dev/nestjs-ddd-library",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "NestJS DDD library",
5
5
  "keywords": [
6
6
  "ddd-library",
@@ -13,13 +13,19 @@
13
13
  "name": "libs-for-dev"
14
14
  },
15
15
  "type": "module",
16
- "main": "dist/index.js",
17
- "types": "dist/index.d.ts",
16
+ "exports": {
17
+ ".": {
18
+ "import": "./dist/index.mjs",
19
+ "require": "./dist/index.cjs"
20
+ }
21
+ },
22
+ "main": "dist/index.mjs",
23
+ "types": "dist/index.d.mts",
18
24
  "files": [
19
25
  "dist"
20
26
  ],
21
27
  "scripts": {
22
- "build": "rm -rf ./dist && cti create ./src && tsc --project tsconfig.build.json && tsc-alias --project tsconfig.build.json && cti clean ./src",
28
+ "build": "rm -rf ./dist && cti create ./src && tsdown && cti clean ./src",
23
29
  "dev:quality": "yarn concurrently --timings --kill-others-on-fail 'yarn lint' 'yarn test'",
24
30
  "dev:test:watch": "vitest",
25
31
  "lint": "yarn concurrently --timings 'yarn:lint:*'",
@@ -51,7 +57,7 @@
51
57
  "oxide.ts": "^1.1.0",
52
58
  "reflect-metadata": "^0.2.2",
53
59
  "scriptlint": "^3.0.0",
54
- "tsc-alias": "^1.8.16",
60
+ "tsdown": "0.18.4",
55
61
  "typescript": "^5.9.3",
56
62
  "ui7": "^0.2.3",
57
63
  "vitest": "^4.0.16",
@@ -1,13 +0,0 @@
1
- import type { IEvent } from '@nestjs/cqrs';
2
- import { AggregateRoot } from '@nestjs/cqrs';
3
- import type { DeepReadonly } from './tests/deep-readonly';
4
- export interface EntityPropsInterface<T> {
5
- props: T;
6
- }
7
- export declare abstract class AbstractEntity<Id, Props, Event extends IEvent = IEvent> extends AggregateRoot<Event> {
8
- readonly id: Id;
9
- get props(): DeepReadonly<Props>;
10
- protected propsData: EntityPropsInterface<Props>;
11
- protected constructor(id: Id, props: Props);
12
- equals(object: AbstractEntity<Id, Props>): boolean;
13
- }
@@ -1,20 +0,0 @@
1
- import { AggregateRoot } from '@nestjs/cqrs';
2
- import { deepReadonly } from './tests/deep-readonly';
3
- export class AbstractEntity extends AggregateRoot {
4
- id;
5
- get props() {
6
- return deepReadonly(this.propsData.props);
7
- }
8
- propsData;
9
- constructor(id, props) {
10
- super();
11
- this.id = id;
12
- this.propsData = { props };
13
- }
14
- equals(object) {
15
- if (this.constructor.name !== object.constructor.name) {
16
- return false;
17
- }
18
- return this.id === object.id;
19
- }
20
- }
@@ -1,11 +0,0 @@
1
- export interface PrimitiveInterface<T extends Date | Primitives> {
2
- value: T;
3
- }
4
- export type Primitives = boolean | null | number | string;
5
- type ValueObjectProps<T> = T extends Date | Primitives ? PrimitiveInterface<T> : T;
6
- export declare abstract class AbstractValueObject<T> {
7
- readonly props: Readonly<ValueObjectProps<T>>;
8
- protected constructor(props: Readonly<ValueObjectProps<T>>);
9
- equals(valueObject: AbstractValueObject<T>): boolean;
10
- }
11
- export {};
@@ -1,13 +0,0 @@
1
- import { deepEqual } from 'fast-equals';
2
- export class AbstractValueObject {
3
- props;
4
- constructor(props) {
5
- this.props = props;
6
- }
7
- equals(valueObject) {
8
- if (this.constructor.name !== valueObject.constructor.name) {
9
- return false;
10
- }
11
- return deepEqual(valueObject.props, this.props);
12
- }
13
- }
package/dist/index.d.ts DELETED
@@ -1,3 +0,0 @@
1
- export * from './value-objects';
2
- export * from './abstract-entity';
3
- export * from './abstract-value-object';
package/dist/index.js DELETED
@@ -1,3 +0,0 @@
1
- export * from './value-objects';
2
- export * from './abstract-entity';
3
- export * from './abstract-value-object';
@@ -1,4 +0,0 @@
1
- export type DeepReadonly<T> = Readonly<{
2
- [K in keyof T]: T[K] extends (number | string | symbol) ? Readonly<T[K]> : T[K] extends (infer A)[] ? readonly DeepReadonly<A>[] : DeepReadonly<T[K]>;
3
- }>;
4
- export declare const deepReadonly: <T>(object: T) => DeepReadonly<T>;
@@ -1,10 +0,0 @@
1
- export const deepReadonly = (object) => {
2
- const propertyNames = Object.getOwnPropertyNames(object);
3
- for (const name of propertyNames) {
4
- const value = object[name];
5
- if (value !== undefined && typeof value === 'object') {
6
- deepReadonly(value);
7
- }
8
- }
9
- return Object.freeze(object);
10
- };
@@ -1,7 +0,0 @@
1
- import type { Result } from 'oxide.ts';
2
- import { AbstractValueObject } from '../abstract-value-object';
3
- export declare class Email extends AbstractValueObject<string> {
4
- get value(): string;
5
- static create(email: string): Result<Email, Error>;
6
- private static isValid;
7
- }
@@ -1,18 +0,0 @@
1
- import { isEmail } from 'class-validator';
2
- import { Err, Ok } from 'oxide.ts';
3
- import { AbstractValueObject } from '../abstract-value-object';
4
- import { NotAnEmailError } from '../value-objects/errors/not-an-email.error';
5
- export class Email extends AbstractValueObject {
6
- get value() {
7
- return this.props.value;
8
- }
9
- static create(email) {
10
- if (!this.isValid(email)) {
11
- return Err(new NotAnEmailError(email));
12
- }
13
- return Ok(new Email({ value: email }));
14
- }
15
- static isValid(email) {
16
- return isEmail(email);
17
- }
18
- }
@@ -1,5 +0,0 @@
1
- export * from './not-a-money.error';
2
- export * from './not-a-phone-number.error';
3
- export * from './not-an-email.error';
4
- export * from './not-an-url.error';
5
- export * from './not-an-uuid.error';
@@ -1,5 +0,0 @@
1
- export * from './not-a-money.error';
2
- export * from './not-a-phone-number.error';
3
- export * from './not-an-email.error';
4
- export * from './not-an-url.error';
5
- export * from './not-an-uuid.error';
@@ -1,4 +0,0 @@
1
- export declare class NotAMoneyError extends Error {
2
- static readonly name = "NotAMoneyError";
3
- constructor(amount: number, currency: string);
4
- }
@@ -1,6 +0,0 @@
1
- export class NotAMoneyError extends Error {
2
- static name = 'NotAMoneyError';
3
- constructor(amount, currency) {
4
- super(`Amount ${amount} with currency ${currency} is not valid money`);
5
- }
6
- }
@@ -1,4 +0,0 @@
1
- export declare class NotAPhoneNumberError extends Error {
2
- static readonly name = "NotAPhoneNumberError";
3
- constructor(phoneNumber: string);
4
- }
@@ -1,6 +0,0 @@
1
- export class NotAPhoneNumberError extends Error {
2
- static name = 'NotAPhoneNumberError';
3
- constructor(phoneNumber) {
4
- super(`${phoneNumber} is not a valid phone number`);
5
- }
6
- }
@@ -1,4 +0,0 @@
1
- export declare class NotAnEmailError extends Error {
2
- static readonly name = "NotAnEmailError";
3
- constructor(email: string);
4
- }
@@ -1,6 +0,0 @@
1
- export class NotAnEmailError extends Error {
2
- static name = 'NotAnEmailError';
3
- constructor(email) {
4
- super(`${email} is not a valid email`);
5
- }
6
- }
@@ -1,4 +0,0 @@
1
- export declare class NotAnUrlError extends Error {
2
- static readonly name = "NotAnUrlError";
3
- constructor(url: string);
4
- }
@@ -1,6 +0,0 @@
1
- export class NotAnUrlError extends Error {
2
- static name = 'NotAnUrlError';
3
- constructor(url) {
4
- super(`${url} is not a valid URL`);
5
- }
6
- }
@@ -1,4 +0,0 @@
1
- export declare class NotAnUuidError extends Error {
2
- static readonly name = "NotAnUuidError";
3
- constructor(uuid: string);
4
- }
@@ -1,6 +0,0 @@
1
- export class NotAnUuidError extends Error {
2
- static name = 'NotAnUuidError';
3
- constructor(uuid) {
4
- super(`${uuid} is not a valid UUID`);
5
- }
6
- }
@@ -1,6 +0,0 @@
1
- export * from './errors';
2
- export * from './email';
3
- export * from './money';
4
- export * from './phone-number';
5
- export * from './url';
6
- export * from './uuid';
@@ -1,6 +0,0 @@
1
- export * from './errors';
2
- export * from './email';
3
- export * from './money';
4
- export * from './phone-number';
5
- export * from './url';
6
- export * from './uuid';
@@ -1,13 +0,0 @@
1
- import type { Result } from 'oxide.ts';
2
- import { AbstractValueObject } from '../abstract-value-object';
3
- export interface MoneyPropsInterface {
4
- amount: number;
5
- currency: string;
6
- }
7
- export declare class Money extends AbstractValueObject<MoneyPropsInterface> {
8
- get amount(): number;
9
- get currency(): string;
10
- get value(): MoneyPropsInterface;
11
- static create(amount: number, currency: string): Result<Money, Error>;
12
- private static isValid;
13
- }
@@ -1,34 +0,0 @@
1
- import { Err, Ok } from 'oxide.ts';
2
- import { AbstractValueObject } from '../abstract-value-object';
3
- import { NotAMoneyError } from '../value-objects/errors/not-a-money.error';
4
- export class Money extends AbstractValueObject {
5
- get amount() {
6
- return this.props.amount;
7
- }
8
- get currency() {
9
- return this.props.currency;
10
- }
11
- get value() {
12
- return {
13
- amount: this.amount,
14
- currency: this.currency,
15
- };
16
- }
17
- static create(amount, currency) {
18
- if (!this.isValid(amount, currency)) {
19
- return Err(new NotAMoneyError(amount, currency));
20
- }
21
- return Ok(new Money({ amount, currency }));
22
- }
23
- static isValid(amount, currency) {
24
- const minimumAmount = 0;
25
- if (amount < minimumAmount || !Number.isFinite(amount)) {
26
- return false;
27
- }
28
- const currencyRegex = /^[A-Z]{3}$/u;
29
- if (!currencyRegex.test(currency)) {
30
- return false;
31
- }
32
- return true;
33
- }
34
- }
@@ -1,8 +0,0 @@
1
- import type { Result } from 'oxide.ts';
2
- import { AbstractValueObject } from '../abstract-value-object';
3
- import { NotAPhoneNumberError } from '../value-objects/errors/not-a-phone-number.error';
4
- export declare class PhoneNumber extends AbstractValueObject<string> {
5
- get value(): string;
6
- static create(phoneNumber: string): Result<PhoneNumber, NotAPhoneNumberError>;
7
- private static isValid;
8
- }
@@ -1,18 +0,0 @@
1
- import { isMobilePhone } from 'class-validator';
2
- import { Err, Ok } from 'oxide.ts';
3
- import { AbstractValueObject } from '../abstract-value-object';
4
- import { NotAPhoneNumberError } from '../value-objects/errors/not-a-phone-number.error';
5
- export class PhoneNumber extends AbstractValueObject {
6
- get value() {
7
- return this.props.value;
8
- }
9
- static create(phoneNumber) {
10
- if (!this.isValid(phoneNumber)) {
11
- return Err(new NotAPhoneNumberError(phoneNumber));
12
- }
13
- return Ok(new PhoneNumber({ value: phoneNumber }));
14
- }
15
- static isValid(phoneNumber) {
16
- return isMobilePhone(phoneNumber);
17
- }
18
- }
@@ -1,8 +0,0 @@
1
- import type { Result } from 'oxide.ts';
2
- import { AbstractValueObject } from '../abstract-value-object';
3
- import { NotAnUrlError } from '../value-objects/errors/not-an-url.error';
4
- export declare class Url extends AbstractValueObject<string> {
5
- get value(): string;
6
- static create(url: string): Result<Url, NotAnUrlError>;
7
- private static isValid;
8
- }
@@ -1,18 +0,0 @@
1
- import { isURL } from 'class-validator';
2
- import { Err, Ok } from 'oxide.ts';
3
- import { AbstractValueObject } from '../abstract-value-object';
4
- import { NotAnUrlError } from '../value-objects/errors/not-an-url.error';
5
- export class Url extends AbstractValueObject {
6
- get value() {
7
- return this.props.value;
8
- }
9
- static create(url) {
10
- if (!this.isValid(url)) {
11
- return Err(new NotAnUrlError(url));
12
- }
13
- return Ok(new Url({ value: url }));
14
- }
15
- static isValid(url) {
16
- return isURL(url, { require_protocol: true });
17
- }
18
- }
@@ -1,7 +0,0 @@
1
- import type { Result } from 'oxide.ts';
2
- import { AbstractValueObject } from '../abstract-value-object';
3
- export declare class Uuid extends AbstractValueObject<string> {
4
- get value(): string;
5
- static create(uuid: string): Result<Uuid, Error>;
6
- static generate(): Uuid;
7
- }
@@ -1,19 +0,0 @@
1
- import { isUUID } from 'class-validator';
2
- import { Err, Ok } from 'oxide.ts';
3
- import { v7 } from 'ui7';
4
- import { AbstractValueObject } from '../abstract-value-object';
5
- import { NotAnUuidError } from '../value-objects/errors/not-an-uuid.error';
6
- export class Uuid extends AbstractValueObject {
7
- get value() {
8
- return this.props.value;
9
- }
10
- static create(uuid) {
11
- if (!isUUID(uuid)) {
12
- return Err(new NotAnUuidError(uuid));
13
- }
14
- return Ok(new Uuid({ value: uuid }));
15
- }
16
- static generate() {
17
- return new Uuid({ value: v7() });
18
- }
19
- }