@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.
Files changed (2) hide show
  1. package/README.md +122 -77
  2. 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
- Replace primitive obsession and defensive validation with domain-safe values that validate themselves.
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
  [![dependencies](https://img.shields.io/badge/dependencies-0-brightgreen.svg)](https://www.npmjs.com/package/@haskou/value-objects?activeTab=dependencies)
14
14
  [![license](https://img.shields.io/npm/l/@haskou/value-objects.svg)](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
- ## Development
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.2",
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",