@haskou/value-objects 1.0.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.
Files changed (85) hide show
  1. package/.editorconfig +15 -0
  2. package/.eslintignore +3 -0
  3. package/.eslintrc.json +218 -0
  4. package/.prettierrc.json +13 -0
  5. package/README.md +156 -0
  6. package/TECHNICAL_DOCUMENTATION.md +1091 -0
  7. package/jest.config.ts +30 -0
  8. package/package.json +49 -0
  9. package/src/errors/BaseError.ts +20 -0
  10. package/src/errors/DomainError.ts +12 -0
  11. package/src/errors/InvalidColorError.ts +7 -0
  12. package/src/errors/InvalidDayError.ts +7 -0
  13. package/src/errors/InvalidDayFormatError.ts +7 -0
  14. package/src/errors/InvalidEmailError.ts +7 -0
  15. package/src/errors/InvalidHourError.ts +7 -0
  16. package/src/errors/InvalidIntegerError.ts +7 -0
  17. package/src/errors/InvalidLatitudeError.ts +7 -0
  18. package/src/errors/InvalidLongitudeError.ts +7 -0
  19. package/src/errors/InvalidMinutesError.ts +7 -0
  20. package/src/errors/InvalidNumberError.ts +7 -0
  21. package/src/errors/InvalidPositiveNumberError.ts +7 -0
  22. package/src/errors/InvalidStringLengthError.ts +9 -0
  23. package/src/errors/InvalidTimestampIntervalError.ts +10 -0
  24. package/src/errors/NullObjectError.ts +8 -0
  25. package/src/errors/ValueNotInEnumError.ts +9 -0
  26. package/src/errors/index.ts +17 -0
  27. package/src/index.ts +5 -0
  28. package/src/interfaces/PrimitiveOf.ts +5 -0
  29. package/src/interfaces/index.ts +1 -0
  30. package/src/patterns/Assert.ts +15 -0
  31. package/src/patterns/NullObject.ts +60 -0
  32. package/src/patterns/ValueObject.ts +40 -0
  33. package/src/patterns/index.ts +3 -0
  34. package/src/types/Nullish.ts +1 -0
  35. package/src/types/Primitive.ts +1 -0
  36. package/src/types/index.ts +2 -0
  37. package/src/value-objects/Color.ts +39 -0
  38. package/src/value-objects/Email.ts +23 -0
  39. package/src/value-objects/Enum.ts +31 -0
  40. package/src/value-objects/Integer.ts +22 -0
  41. package/src/value-objects/NumberValueObject.ts +56 -0
  42. package/src/value-objects/PositiveNumber.ts +20 -0
  43. package/src/value-objects/StringValueObject.ts +27 -0
  44. package/src/value-objects/coordinates/Coordinates.ts +30 -0
  45. package/src/value-objects/coordinates/Latitude.ts +22 -0
  46. package/src/value-objects/coordinates/Longitude.ts +25 -0
  47. package/src/value-objects/coordinates/index.ts +3 -0
  48. package/src/value-objects/index.ts +9 -0
  49. package/src/value-objects/time/CalendarDay.ts +91 -0
  50. package/src/value-objects/time/Day.ts +17 -0
  51. package/src/value-objects/time/DayOfWeek.ts +60 -0
  52. package/src/value-objects/time/Duration.ts +142 -0
  53. package/src/value-objects/time/Hour.ts +105 -0
  54. package/src/value-objects/time/Month.ts +39 -0
  55. package/src/value-objects/time/MonthOfYear.ts +52 -0
  56. package/src/value-objects/time/Timestamp.ts +208 -0
  57. package/src/value-objects/time/TimestampInterval.ts +122 -0
  58. package/src/value-objects/time/Year.ts +27 -0
  59. package/src/value-objects/time/index.ts +10 -0
  60. package/tests/errors/BaseError.spec.ts +63 -0
  61. package/tests/errors/DomainError.spec.ts +52 -0
  62. package/tests/patterns/Assert.spec.ts +29 -0
  63. package/tests/patterns/NullObject.spec.ts +55 -0
  64. package/tests/setup.jest.ts +2 -0
  65. package/tests/value-objects/Color.spec.ts +214 -0
  66. package/tests/value-objects/Email.spec.ts +145 -0
  67. package/tests/value-objects/Enum.spec.ts +293 -0
  68. package/tests/value-objects/Integer.spec.ts +38 -0
  69. package/tests/value-objects/NumberValueObject.spec.ts +446 -0
  70. package/tests/value-objects/PositiveNumber.spec.ts +274 -0
  71. package/tests/value-objects/StringValueObject.spec.ts +135 -0
  72. package/tests/value-objects/coordinates/Coordinates.spec.ts +90 -0
  73. package/tests/value-objects/coordinates/Latitude.spec.ts +24 -0
  74. package/tests/value-objects/coordinates/Longitude.spec.ts +24 -0
  75. package/tests/value-objects/time/CalendarDay.spec.ts +182 -0
  76. package/tests/value-objects/time/Day.spec.ts +29 -0
  77. package/tests/value-objects/time/DayOfWeek.spec.ts +71 -0
  78. package/tests/value-objects/time/Duration.spec.ts +278 -0
  79. package/tests/value-objects/time/Hour.spec.ts +197 -0
  80. package/tests/value-objects/time/MonthOfYear.spec.ts +111 -0
  81. package/tests/value-objects/time/Timestamp.spec.ts +497 -0
  82. package/tests/value-objects/time/TimestampInterval.spec.ts +383 -0
  83. package/tests/value-objects/time/Year.spec.ts +48 -0
  84. package/tsconfig.jest.json +33 -0
  85. package/tsconfig.json +42 -0
package/.editorconfig ADDED
@@ -0,0 +1,15 @@
1
+ ################################################
2
+ # ╔═╗╔╦╗╦╔╦╗╔═╗╦═╗┌─┐┌─┐┌┐┌┌─┐┬┌─┐
3
+ # ║╣ ║║║ ║ ║ ║╠╦╝│ │ ││││├┤ ││ ┬
4
+ # o╚═╝═╩╝╩ ╩ ╚═╝╩╚═└─┘└─┘┘└┘└ ┴└─┘
5
+ ################################################
6
+ root = true
7
+
8
+ [*]
9
+ indent_style = space
10
+ indent_size = 2
11
+ end_of_line = lf
12
+ charset = utf-8
13
+ trim_trailing_whitespace = true
14
+ insert_final_newline = true
15
+ max_line_length = 80
package/.eslintignore ADDED
@@ -0,0 +1,3 @@
1
+ dist/
2
+ node_modules/
3
+ tests/
package/.eslintrc.json ADDED
@@ -0,0 +1,218 @@
1
+ {
2
+ "env": {
3
+ "es2022": true,
4
+ "node": true,
5
+ "jest": true
6
+ },
7
+ "parser": "@typescript-eslint/parser",
8
+ "plugins": [
9
+ "@typescript-eslint",
10
+ "prettier",
11
+ "unused-imports",
12
+ "sonarjs",
13
+ "perfectionist"
14
+ ],
15
+ "extends": [
16
+ "eslint:recommended",
17
+ "plugin:@typescript-eslint/eslint-recommended",
18
+ "plugin:@typescript-eslint/recommended",
19
+ "plugin:prettier/recommended"
20
+ ],
21
+ "parserOptions": {
22
+ "project": ["./tsconfig.json", "./tsconfig.jest.json"],
23
+ "sourceType": "module"
24
+ },
25
+ "rules": {
26
+ "lines-between-class-members": [
27
+ "error",
28
+ "always",
29
+ {
30
+ "exceptAfterSingleLine": true
31
+ }
32
+ ],
33
+ "padding-line-between-statements": [
34
+ "error",
35
+ {
36
+ "blankLine": "always",
37
+ "prev": "import",
38
+ "next": ["class", "export", "const", "let", "var"]
39
+ },
40
+ {
41
+ "blankLine": "always",
42
+ "prev": "*",
43
+ "next": ["return", "if"]
44
+ }
45
+ ],
46
+ "no-extra-boolean-cast": "error",
47
+ "@typescript-eslint/no-non-null-assertion": "off",
48
+ "@typescript-eslint/naming-convention": [
49
+ "error",
50
+ {
51
+ "selector": ["variable", "function"],
52
+ "format": ["camelCase", "PascalCase"],
53
+ "leadingUnderscore": "allow"
54
+ },
55
+ {
56
+ "selector": "interface",
57
+ "format": ["PascalCase"],
58
+ "custom": {
59
+ "regex": "^[A-Z]",
60
+ "match": true
61
+ }
62
+ }
63
+ ],
64
+ "no-unused-vars": "off",
65
+ "unused-imports/no-unused-imports": "error",
66
+ "unused-imports/no-unused-vars": [
67
+ "warn",
68
+ {
69
+ "vars": "all",
70
+ "varsIgnorePattern": "^_",
71
+ "args": "after-used",
72
+ "argsIgnorePattern": "^_"
73
+ }
74
+ ],
75
+ "@typescript-eslint/no-use-before-define": "warn",
76
+ "@typescript-eslint/no-inferrable-types": "off",
77
+ "@typescript-eslint/no-this-alias": "warn",
78
+ "@typescript-eslint/no-require-imports": "error",
79
+ "@typescript-eslint/unbound-method": "off",
80
+ "require-await": "off",
81
+ "@typescript-eslint/require-await": "error",
82
+ "@typescript-eslint/no-misused-promises": "off",
83
+ "@typescript-eslint/no-floating-promises": "error",
84
+ "@typescript-eslint/prefer-for-of": "error",
85
+ "@typescript-eslint/member-delimiter-style": "off",
86
+ "@typescript-eslint/no-unsafe-return": "error",
87
+ "@typescript-eslint/no-unsafe-call": "warn",
88
+ "@typescript-eslint/no-unsafe-argument": "warn",
89
+ "max-classes-per-file": ["error", 1],
90
+ "no-multiple-empty-lines": [
91
+ 1,
92
+ {
93
+ "max": 1,
94
+ "maxEOF": 0
95
+ }
96
+ ],
97
+ "max-len": [
98
+ "error",
99
+ {
100
+ "code": 80,
101
+ "ignoreStrings": true,
102
+ "ignoreRegExpLiterals": true,
103
+ "ignoreTemplateLiterals": true
104
+ }
105
+ ],
106
+ "@typescript-eslint/explicit-member-accessibility": [
107
+ "error",
108
+ {
109
+ "ignoredMethodNames": ["constructor"],
110
+ "accessibility": "explicit"
111
+ }
112
+ ],
113
+ "new-parens": "error",
114
+ "no-bitwise": "error",
115
+ "no-caller": "error",
116
+ "no-cond-assign": "error",
117
+ "no-console": "warn",
118
+ "no-debugger": "error",
119
+ "no-empty": "error",
120
+ "no-eval": "error",
121
+ "no-fallthrough": "off",
122
+ "no-invalid-this": "off",
123
+ "no-new-wrappers": "error",
124
+ "no-throw-literal": "error",
125
+ "no-trailing-spaces": "error",
126
+ "no-undef-init": "error",
127
+ "no-unsafe-finally": "error",
128
+ "no-unused-expressions": [
129
+ "error",
130
+ {
131
+ "allowShortCircuit": true
132
+ }
133
+ ],
134
+ "no-unused-labels": "error",
135
+ "object-shorthand": "error",
136
+ "one-var": ["error", "never"],
137
+ "radix": "error",
138
+ "spaced-comment": "error",
139
+ "use-isnan": "error",
140
+ "valid-typeof": "off",
141
+ "complexity": ["error", 8],
142
+ "@typescript-eslint/member-ordering": [
143
+ "error",
144
+ {
145
+ "default": [
146
+ "field",
147
+ "static-field",
148
+ "private-static-field",
149
+ "public-static-field",
150
+ "private-instance-field",
151
+ "public-instance-field",
152
+ "private-static-method",
153
+ "public-static-method",
154
+ "constructor",
155
+ "private-method",
156
+ "public-method"
157
+ ]
158
+ }
159
+ ],
160
+ "max-params": ["warn", 7],
161
+ "max-nested-callbacks": ["warn", 2],
162
+ "max-depth": ["warn", 3],
163
+ "sonarjs/prefer-single-boolean-return": "error",
164
+ "sonarjs/no-collapsible-if": "error",
165
+ "sonarjs/no-duplicated-branches": "error",
166
+ "sonarjs/no-identical-expressions": "warn",
167
+ "sonarjs/no-nested-switch": "warn",
168
+ "perfectionist/sort-objects": [
169
+ "warn",
170
+ {
171
+ "type": "natural",
172
+ "order": "asc"
173
+ }
174
+ ],
175
+ "perfectionist/sort-imports": [
176
+ "error",
177
+ {
178
+ "type": "natural",
179
+ "order": "asc"
180
+ }
181
+ ],
182
+ "sonarjs/cognitive-complexity": ["error", 10],
183
+ "@typescript-eslint/explicit-module-boundary-types": [
184
+ "error",
185
+ {
186
+ "allowedNames": ["toPrimitives"]
187
+ }
188
+ ],
189
+ "no-param-reassign": [
190
+ "warn",
191
+ {
192
+ "props": true
193
+ }
194
+ ],
195
+ "no-restricted-imports": [
196
+ "error",
197
+ {
198
+ "paths": [
199
+ {
200
+ "name": "../infrastructure",
201
+ "message": "Domain layer cannot import infrastructure."
202
+ }
203
+ ]
204
+ }
205
+ ]
206
+ },
207
+ "overrides": [
208
+ {
209
+ "files": ["*.spec.ts"],
210
+ "rules": {
211
+ "no-unsafe-argument": "off",
212
+ "max-statements": "off",
213
+ "max-lines-per-function": "off",
214
+ "max-nested-callbacks": ["warn", 4]
215
+ }
216
+ }
217
+ ]
218
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "tabWidth": 2,
3
+ "useTabs": false,
4
+ "printWidth": 80,
5
+ "singleQuote": true,
6
+ "trailingComma": "all",
7
+ "bracketSpacing": true,
8
+ "bracketSameLine": false,
9
+ "jsxBracketSameLine": false,
10
+ "semi": true,
11
+ "arrowParens": "always",
12
+ "endOfLine": "auto"
13
+ }
package/README.md ADDED
@@ -0,0 +1,156 @@
1
+ [![npm version](https://badge.fury.io/js/@haskou%2Fvalue-objects.svg)](https://badge.fury.io/js/@haskou%2Fvalue-objects)
2
+
3
+ # Value Objects
4
+
5
+ A TypeScript dependency-less library for creating safe, immutable,
6
+ and validated **Value Objects**. Perfect for applications that require **Domain-Driven Design (DDD)** and **type safety**.
7
+
8
+ ## 🚀 Quick Start
9
+
10
+ ```bash
11
+ npm install @haskou/value-objects
12
+ ```
13
+
14
+ ```typescript
15
+ import { Email, PositiveNumber, Color, Hour } from 'value-objects';
16
+
17
+ // ✅ Automatic validation on construction
18
+ const email = new Email('user@example.com'); // Valid
19
+ const price = new PositiveNumber(29.99); // Valid
20
+ const color = new Color('#FF0000'); // Valid
21
+ const hour = new Hour('09:30'); // Valid
22
+
23
+ // ❌ Immediate errors with invalid values
24
+ const badEmail = new Email('not-an-email'); // Error!
25
+ const badPrice = new PositiveNumber(-5); // Error!
26
+ ```
27
+
28
+ ## 🤔 Why use Value Objects?
29
+
30
+ **Without Value Objects:**
31
+ ```typescript
32
+ function createUser(email: string, age: number): void {
33
+ // Are they valid? We don't know until runtime
34
+ if (!email.includes('@')) throw new Error('Invalid email');
35
+ if (age <= 0) throw new Error('Invalid age');
36
+ // Creation stuff
37
+ }
38
+ ```
39
+
40
+ **With Value Objects:**
41
+ ```typescript
42
+ function createUser(email: Email, age: PositiveNumber): void {
43
+ // If we get here, values ARE valid
44
+ // Creation stuff
45
+ }
46
+ ```
47
+
48
+ This prevents defensive programming and if-else validations
49
+ since it implements Null Object pattern (see technical documentation).
50
+ Also value-objects can be modified easily keeping the same logic along
51
+ your application.
52
+
53
+ ## 📦 Available Value Objects
54
+
55
+ ### 🔤 Basic
56
+ - **`StringValueObject`** - Strings with length validation
57
+ - **`NumberValueObject`** - Numbers with math operations
58
+ - **`Integer`** - Whole numbers
59
+ - **`PositiveNumber`** - Positive numbers (> 0)
60
+
61
+ ### ✨ Specialized
62
+ - **`Email`** - Email addresses with automatic validation
63
+ - **`Color`** - Hex colors (#FF0000)
64
+
65
+ ### 🕒 Time
66
+ - **`Hour`** - Time in 24h format (09:30)
67
+ - **`Year`** - Years with leap year calculations
68
+ - **`CalendarDay`** - Calendar days
69
+ - **`Day`** - Days of month (1-31)
70
+ - **`DayOfWeek`** - Days of the week
71
+ - **`Month`** - Months (1-12)
72
+ - **`MonthOfYear`** - Month/year combinations
73
+ - **`Timestamp`** - Timestamps with operations
74
+ - **`TimestampInterval`** - Time intervals
75
+ - **`Duration`** - Duration in milliseconds
76
+
77
+ ### 🌍 Coordinates
78
+ - **`Latitude`** - Latitude (-90 to 90)
79
+ - **`Longitude`** - Longitude (-180 to 180)
80
+ - **`Coordinates`** - Coordinate pairs
81
+
82
+ ### 📝 Other
83
+ - **`Enum`** - Base class for typed enumerations
84
+
85
+ ## 💡 Basic Examples
86
+
87
+ ```typescript
88
+ // Validated strings
89
+ const name = new StringValueObject('John Doe');
90
+ const code = new StringValueObject('ABC', 3); // max 3 characters
91
+
92
+ // Numbers with operations
93
+ const price = new NumberValueObject(29.99);
94
+ const discount = new NumberValueObject(5.00);
95
+ const total = price.subtract(discount); // 24.99
96
+
97
+ // Emails
98
+ const email = new Email('user@example.com');
99
+ console.log(email.getDomain()); // 'example.com'
100
+
101
+ // Colors
102
+ const red = new Color('#FF0000');
103
+ const blue = Color.BLUE; // Predefined color
104
+
105
+ // Coordinates
106
+ const latitude = new Latitude(40.7128); // New York
107
+ const longitude = new Longitude(-74.0060);
108
+ const coords = new Coordinates(latitude, longitude);
109
+
110
+ // Time
111
+ const hour = new Hour('09:30');
112
+ const year = new Year(2024);
113
+ console.log(year.isLeapYear()); // true
114
+ ```
115
+
116
+ ## 📚 Technical Documentation
117
+
118
+ Need more details? Check the complete documentation:
119
+
120
+ **[📖 TECHNICAL_DOCUMENTATION.md](./TECHNICAL_DOCUMENTATION.md)**
121
+
122
+ Includes:
123
+ - ✅ Complete API for all Value Objects
124
+ - ✅ Advanced usage examples
125
+ - ✅ Error handling
126
+ - ✅ Design principles (immutability, null safety, etc.)
127
+ - ✅ Composition and extensibility patterns
128
+
129
+ ## 🛠️ Development
130
+
131
+ ```bash
132
+ # Install dependencies
133
+ npm install
134
+
135
+ # Run tests
136
+ npm test
137
+
138
+ # Build
139
+ npm run build
140
+ ```
141
+
142
+ ## 🤝 Contributing
143
+
144
+ 1. Fork the repository
145
+ 2. Create a branch: `git checkout -b my-feature`
146
+ 3. Make your changes and add tests
147
+ 4. Run tests: `npm test`
148
+ 5. Submit a pull request
149
+
150
+ ## 📄 License
151
+
152
+ MIT License - see the [LICENSE](LICENSE) file for details.
153
+
154
+ ---
155
+
156
+ **Made with ❤️ and TypeScript**