@haskou/value-objects 8.3.2 → 8.3.3
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 +122 -77
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
> Validated, immutable Value Objects for TypeScript and Domain-Driven Design.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Validate once, at the edge of your domain. Everything past that point receives a value that is already correct, with the behavior that belongs to it.
|
|
6
6
|
|
|
7
7
|
Dependency-less: zero runtime dependencies, for Node.js and browsers.
|
|
8
8
|
|
|
@@ -13,6 +13,21 @@ Dependency-less: zero runtime dependencies, for Node.js and browsers.
|
|
|
13
13
|
[](https://www.npmjs.com/package/@haskou/value-objects?activeTab=dependencies)
|
|
14
14
|
[](LICENSE.txt)
|
|
15
15
|
|
|
16
|
+
```typescript
|
|
17
|
+
import { Hour, Money, Timestamp } from '@haskou/value-objects';
|
|
18
|
+
|
|
19
|
+
const price = Money.fromString('19.99€').multiply(3);
|
|
20
|
+
const opens = new Hour('09:30').addMinutes(45);
|
|
21
|
+
|
|
22
|
+
JSON.stringify({ price, at: new Timestamp(1782218400000) });
|
|
23
|
+
// {"price":"59.97 EUR","at":1782218400000}
|
|
24
|
+
|
|
25
|
+
opens.toString();
|
|
26
|
+
// '10:15'
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Exact money arithmetic, time math and clean JSON, each type validating itself on construction.
|
|
30
|
+
|
|
16
31
|
## Installation
|
|
17
32
|
|
|
18
33
|
```bash
|
|
@@ -66,9 +81,7 @@ new Email('not-an-email');
|
|
|
66
81
|
// throws InvalidEmailError
|
|
67
82
|
```
|
|
68
83
|
|
|
69
|
-
The same idea applies to numbers, identifiers, dates, coordinates, hashes and your own domain concepts.
|
|
70
|
-
|
|
71
|
-
Validation happens when values enter the domain instead of being repeated everywhere they are used.
|
|
84
|
+
The same idea applies to numbers, identifiers, dates, money, coordinates, hashes and your own domain concepts.
|
|
72
85
|
|
|
73
86
|
## Build your own domain Value Objects
|
|
74
87
|
|
|
@@ -77,11 +90,7 @@ The built-in Value Objects are also intended to be extended.
|
|
|
77
90
|
If your domain has a `Username`, it should not remain a generic `string` with validation scattered around the application.
|
|
78
91
|
|
|
79
92
|
```typescript
|
|
80
|
-
import {
|
|
81
|
-
assert,
|
|
82
|
-
NullObject,
|
|
83
|
-
StringValueObject,
|
|
84
|
-
} from '@haskou/value-objects';
|
|
93
|
+
import { assert, NullObject, StringValueObject } from '@haskou/value-objects';
|
|
85
94
|
|
|
86
95
|
export class Username extends StringValueObject {
|
|
87
96
|
constructor(value: string) {
|
|
@@ -111,15 +120,107 @@ function createUser(username: Username) {
|
|
|
111
120
|
|
|
112
121
|
This is the main idea behind the library: use the provided Value Objects where they fit, and extend the base ones with your own domain invariants when they do not.
|
|
113
122
|
|
|
123
|
+
## Available Value Objects
|
|
124
|
+
|
|
125
|
+
| Category | Classes | What they give you |
|
|
126
|
+
| ----------- | ------------------------------------------------------------- | ----------------------------------------------------------------- |
|
|
127
|
+
| Base | `ValueObject`, `NullObject`, `Enum` | Immutability, equality by value, safe absence |
|
|
128
|
+
| Strings | `StringValueObject`, `Password`, `Email`, `Color` | Length, format and emptiness rules |
|
|
129
|
+
| Numbers | `NumberValueObject`, `Integer`, `PositiveNumber` | Range and integer guarantees |
|
|
130
|
+
| Money | `Money`, `Currency` | Exact arithmetic in minor units, no floating point errors |
|
|
131
|
+
| Identifiers | `ShortId`, `UUID` | Generated and validated identifiers |
|
|
132
|
+
| Date & time | `Timestamp`, `CalendarDay`, `Hour`, `Duration`, `MonthOfYear` | Arithmetic and comparison without hand-rolled `Date` code |
|
|
133
|
+
| Coordinates | `Latitude`, `Longitude`, `Coordinates` | Range-checked geographic points |
|
|
134
|
+
| Hashes | `MD5Hash`, `SHA256Hash`, `SHA512Hash` | Validated digests |
|
|
135
|
+
| Media | `Media` | Typed media references |
|
|
136
|
+
| Collections | `UniqueObjectArray` | Immutable collection without duplicates, by Value Object equality |
|
|
137
|
+
|
|
138
|
+
Every class has its own page in the [API reference](https://haskou.github.io/value-objects/reference/).
|
|
139
|
+
|
|
140
|
+
## How it compares
|
|
141
|
+
|
|
142
|
+
| | Value Objects (this package) | Zod / schema validators | Branded primitives | Hand-written classes |
|
|
143
|
+
| -------------------------------------------- | ---------------------------- | ----------------------- | --------------------------- | --------------------- |
|
|
144
|
+
| Validates on construction | Yes | Yes, at parse time | Only if you write the guard | Only if you write it |
|
|
145
|
+
| Carries behaviour (`addMinutes`, `multiply`) | Yes | No | No | Yes, but you write it |
|
|
146
|
+
| Equality by value and class | Built in | No | No | You write it |
|
|
147
|
+
| Immutable at runtime | Yes | No | No | You enforce it |
|
|
148
|
+
| Represents missing values | `NullObject` | `undefined` checks | `undefined` checks | You decide |
|
|
149
|
+
| Dependencies | None | Zod itself | None | None |
|
|
150
|
+
|
|
151
|
+
Choose schema validators to parse untrusted input shapes, and Value Objects for the domain types that input becomes. They combine well, see below.
|
|
152
|
+
|
|
153
|
+
## Integrations
|
|
154
|
+
|
|
155
|
+
### Zod
|
|
156
|
+
|
|
157
|
+
Parse at the boundary, then work with domain types.
|
|
158
|
+
|
|
159
|
+
```typescript
|
|
160
|
+
import { Email } from '@haskou/value-objects';
|
|
161
|
+
import { z } from 'zod';
|
|
162
|
+
|
|
163
|
+
const emailSchema = z.string().transform((value, context) => {
|
|
164
|
+
try {
|
|
165
|
+
return new Email(value);
|
|
166
|
+
} catch (error) {
|
|
167
|
+
context.addIssue({ code: 'custom', message: (error as Error).message });
|
|
168
|
+
|
|
169
|
+
return z.NEVER;
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
emailSchema.parse('user@example.com').valueOf(); // 'user@example.com'
|
|
174
|
+
emailSchema.safeParse('nope').success; // false
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### Persistence and APIs
|
|
178
|
+
|
|
179
|
+
Value Objects serialize to their primitive through `toJSON()` and `valueOf()`, and are rebuilt by passing that primitive back to the constructor. Persistence layers only need to store the primitive.
|
|
180
|
+
|
|
181
|
+
```typescript
|
|
182
|
+
const stored = JSON.stringify({ email: new Email('user@example.com') });
|
|
183
|
+
// {"email":"user@example.com"}
|
|
184
|
+
|
|
185
|
+
const email = new Email(JSON.parse(stored).email);
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
## Quick start
|
|
189
|
+
|
|
190
|
+
```typescript
|
|
191
|
+
import { Color, Email, Hour, PositiveNumber } from '@haskou/value-objects';
|
|
192
|
+
|
|
193
|
+
const email = new Email('user@example.com');
|
|
194
|
+
const price = new PositiveNumber(29.99);
|
|
195
|
+
const color = new Color('#FF0000');
|
|
196
|
+
const hour = new Hour('09:30');
|
|
197
|
+
|
|
198
|
+
email.valueOf(); // 'user@example.com'
|
|
199
|
+
price.valueOf(); // 29.99
|
|
200
|
+
color.toString(); // '#FF0000'
|
|
201
|
+
hour.addMinutes(45).toString(); // '10:15'
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
Validation errors use specific error types:
|
|
205
|
+
|
|
206
|
+
```typescript
|
|
207
|
+
import { Email, InvalidEmailError } from '@haskou/value-objects';
|
|
208
|
+
|
|
209
|
+
try {
|
|
210
|
+
new Email('not-an-email');
|
|
211
|
+
} catch (error) {
|
|
212
|
+
if (error instanceof InvalidEmailError) {
|
|
213
|
+
// Handle invalid domain input.
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
```
|
|
217
|
+
|
|
114
218
|
## Null without defensive programming
|
|
115
219
|
|
|
116
220
|
Missing values can be represented through the `NullObject` pattern instead of propagating `null | undefined` checks throughout the domain.
|
|
117
221
|
|
|
118
222
|
```typescript
|
|
119
|
-
import {
|
|
120
|
-
NullObject,
|
|
121
|
-
NumberValueObject,
|
|
122
|
-
} from '@haskou/value-objects';
|
|
223
|
+
import { NullObject, NumberValueObject } from '@haskou/value-objects';
|
|
123
224
|
|
|
124
225
|
const limit = new NumberValueObject(undefined as never);
|
|
125
226
|
|
|
@@ -151,10 +252,7 @@ This lets missing values travel through the same domain abstraction while still
|
|
|
151
252
|
Applications that prefer strict construction can disable automatic `NullObject` creation:
|
|
152
253
|
|
|
153
254
|
```typescript
|
|
154
|
-
import {
|
|
155
|
-
NumberValueObject,
|
|
156
|
-
ValueObject,
|
|
157
|
-
} from '@haskou/value-objects';
|
|
255
|
+
import { NumberValueObject, ValueObject } from '@haskou/value-objects';
|
|
158
256
|
|
|
159
257
|
ValueObject.disableNullObjectCreation();
|
|
160
258
|
|
|
@@ -192,59 +290,6 @@ Failed assertions throw the supplied error, or a `DomainError` when given a stri
|
|
|
192
290
|
|
|
193
291
|
Successful assertions use TypeScript assertion signatures, so the condition is narrowed afterwards.
|
|
194
292
|
|
|
195
|
-
## Quick start
|
|
196
|
-
|
|
197
|
-
```typescript
|
|
198
|
-
import {
|
|
199
|
-
Color,
|
|
200
|
-
Email,
|
|
201
|
-
Hour,
|
|
202
|
-
PositiveNumber,
|
|
203
|
-
} from '@haskou/value-objects';
|
|
204
|
-
|
|
205
|
-
const email = new Email('user@example.com');
|
|
206
|
-
const price = new PositiveNumber(29.99);
|
|
207
|
-
const color = new Color('#FF0000');
|
|
208
|
-
const hour = new Hour('09:30');
|
|
209
|
-
|
|
210
|
-
email.valueOf(); // 'user@example.com'
|
|
211
|
-
price.valueOf(); // 29.99
|
|
212
|
-
color.toString(); // '#FF0000'
|
|
213
|
-
hour.addMinutes(45).toString(); // '10:15'
|
|
214
|
-
```
|
|
215
|
-
|
|
216
|
-
Validation errors use specific error types:
|
|
217
|
-
|
|
218
|
-
```typescript
|
|
219
|
-
import {
|
|
220
|
-
Email,
|
|
221
|
-
InvalidEmailError,
|
|
222
|
-
} from '@haskou/value-objects';
|
|
223
|
-
|
|
224
|
-
try {
|
|
225
|
-
new Email('not-an-email');
|
|
226
|
-
} catch (error) {
|
|
227
|
-
if (error instanceof InvalidEmailError) {
|
|
228
|
-
// Handle invalid domain input.
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
```
|
|
232
|
-
|
|
233
|
-
## Available Value Objects
|
|
234
|
-
|
|
235
|
-
| Category | Examples |
|
|
236
|
-
| ----------- | ------------------------------------------------------------- |
|
|
237
|
-
| Base | `ValueObject`, `NullObject`, `Enum` |
|
|
238
|
-
| Strings | `StringValueObject`, `Password`, `Email`, `Color` |
|
|
239
|
-
| Numbers | `NumberValueObject`, `Integer`, `PositiveNumber` |
|
|
240
|
-
| Identifiers | `ShortId`, `UUID` |
|
|
241
|
-
| Date & time | `Timestamp`, `CalendarDay`, `Hour`, `Duration`, `MonthOfYear` |
|
|
242
|
-
| Coordinates | `Latitude`, `Longitude`, `Coordinates` |
|
|
243
|
-
| Money | `Money`, `Currency` |
|
|
244
|
-
| Hashes | `MD5Hash`, `SHA256Hash`, `SHA512Hash` |
|
|
245
|
-
| Media | `Media` |
|
|
246
|
-
| Collections | `UniqueObjectArray` |
|
|
247
|
-
|
|
248
293
|
The package is framework-agnostic and can be used in any TypeScript domain layer regardless of the framework, ORM or persistence technology around it.
|
|
249
294
|
|
|
250
295
|
## Documentation
|
|
@@ -257,7 +302,13 @@ API reference:
|
|
|
257
302
|
|
|
258
303
|
https://haskou.github.io/value-objects/reference/
|
|
259
304
|
|
|
260
|
-
##
|
|
305
|
+
## Contributing
|
|
306
|
+
|
|
307
|
+
Bug reports, feature requests and pull requests are welcome.
|
|
308
|
+
|
|
309
|
+
https://github.com/haskou/value-objects/issues
|
|
310
|
+
|
|
311
|
+
To work on the package locally:
|
|
261
312
|
|
|
262
313
|
```bash
|
|
263
314
|
git clone https://github.com/haskou/value-objects.git
|
|
@@ -268,12 +319,6 @@ yarn test
|
|
|
268
319
|
yarn build
|
|
269
320
|
```
|
|
270
321
|
|
|
271
|
-
## Contributing
|
|
272
|
-
|
|
273
|
-
Bug reports, feature requests and pull requests are welcome.
|
|
274
|
-
|
|
275
|
-
https://github.com/haskou/value-objects/issues
|
|
276
|
-
|
|
277
322
|
## License
|
|
278
323
|
|
|
279
324
|
MIT. See [LICENSE.txt](LICENSE.txt).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@haskou/value-objects",
|
|
3
|
-
"version": "8.3.
|
|
3
|
+
"version": "8.3.3",
|
|
4
4
|
"description": "TypeScript value objects for validated, immutable primitive values and small domain-safe data structures.",
|
|
5
5
|
"main": "./dist/index.cjs",
|
|
6
6
|
"module": "./dist/index.mjs",
|