@haskou/value-objects 7.0.3 → 7.1.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 (3) hide show
  1. package/LICENSE.txt +1 -1
  2. package/README.md +213 -68
  3. package/package.json +8 -8
package/LICENSE.txt CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) <year> Adam Veldhousen
3
+ Copyright (c) 2026 Haskō
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -1,23 +1,15 @@
1
1
  # @haskou/value-objects
2
2
 
3
+ > Validated, immutable Value Objects for TypeScript and Domain-Driven Design.
4
+
5
+ Replace primitive obsession and defensive validation with domain-safe values that validate themselves.
6
+
3
7
  [![CI](https://github.com/haskou/value-objects/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/haskou/value-objects/actions/workflows/ci.yml?query=branch%3Amaster)
4
8
  [![codecov](https://codecov.io/gh/haskou/value-objects/branch/master/graph/badge.svg)](https://codecov.io/gh/haskou/value-objects)
5
9
  [![npm version](https://img.shields.io/npm/v/@haskou/value-objects.svg)](https://www.npmjs.com/package/@haskou/value-objects)
6
- [![Renovate](https://img.shields.io/badge/renovate-enabled-brightgreen?logo=renovatebot)](https://docs.renovatebot.com/)
10
+ [![npm downloads](https://img.shields.io/npm/dw/@haskou/value-objects.svg)](https://www.npmjs.com/package/@haskou/value-objects)
7
11
  [![license](https://img.shields.io/npm/l/@haskou/value-objects.svg)](LICENSE.txt)
8
12
 
9
- A TypeScript library for validated, immutable primitive wrappers and small utility value objects.
10
-
11
- It provides ready-to-use objects for strings, numbers, identifiers, dates, coordinates, hashes, media, and collections. Validation happens when an object is created, so code receiving one of these objects can rely on its shape.
12
-
13
- The package publishes ESM and CommonJS entry points plus TypeScript declarations. Both entry points share one runtime implementation, so class identity and NullObject configuration stay consistent when imports and requires are mixed. It does not declare a Node.js engine requirement. Identifier generation uses the installed `uuid` dependency with platform-specific Node and browser implementations.
14
-
15
- ## Documentation
16
-
17
- Full documentation is available at **https://haskou.github.io/value-objects/**.
18
-
19
- The documentation includes installation, quick start, examples, error handling, serialization notes, and one reference page per exported class.
20
-
21
13
  ## Installation
22
14
 
23
15
  ```bash
@@ -28,102 +20,255 @@ npm install @haskou/value-objects
28
20
  yarn add @haskou/value-objects
29
21
  ```
30
22
 
31
- ## Quick start
23
+ ## Why?
24
+
25
+ Primitive values force every consumer to defend itself.
26
+
27
+ ```typescript
28
+ function sendEmail(email?: string | null) {
29
+ if (email === null || email === undefined) {
30
+ return;
31
+ }
32
+
33
+ if (email.length === 0) {
34
+ throw new Error('Email cannot be empty');
35
+ }
36
+
37
+ if (!email.includes('@')) {
38
+ throw new Error('Invalid email');
39
+ }
40
+
41
+ // finally use the email
42
+ }
43
+ ```
44
+
45
+ And another part of the application will eventually perform the same checks again, because `string` says nothing about what the value represents.
46
+
47
+ A Value Object moves those rules to the value itself:
32
48
 
33
49
  ```typescript
34
- import { Color, Email, Hour, PositiveNumber } from '@haskou/value-objects';
50
+ import { Email } from '@haskou/value-objects';
35
51
 
36
52
  const email = new Email('user@example.com');
37
- const price = new PositiveNumber(29.99);
38
- const color = new Color('#FF0000');
39
- const hour = new Hour('09:30');
40
53
 
41
- console.log(email.valueOf()); // 'user@example.com'
42
- console.log(price.valueOf()); // 29.99
43
- console.log(color.toString()); // '#FF0000'
44
- console.log(hour.addMinutes(45).toString()); // '10:15'
54
+ function sendEmail(email: Email) {
55
+ // already a valid Email
56
+ }
45
57
  ```
46
58
 
47
- Invalid values throw specific errors.
59
+ A non-null `Email` has already passed format validation, so consumers do not need to repeat those checks.
48
60
 
49
61
  ```typescript
50
- import { Email, InvalidEmailError } from '@haskou/value-objects';
62
+ new Email('not-an-email');
63
+ // throws InvalidEmailError
64
+ ```
51
65
 
52
- try {
53
- new Email('not-an-email');
54
- } catch (error) {
55
- if (error instanceof InvalidEmailError) {
56
- console.error('Invalid email');
66
+ The same idea applies to numbers, identifiers, dates, coordinates, hashes and your own domain concepts.
67
+
68
+ Validation happens when values enter the domain instead of being repeated everywhere they are used.
69
+
70
+ ## Build your own domain Value Objects
71
+
72
+ The built-in Value Objects are also intended to be extended.
73
+
74
+ If your domain has a `Username`, it should not remain a generic `string` with validation scattered around the application.
75
+
76
+ ```typescript
77
+ import {
78
+ assert,
79
+ NullObject,
80
+ StringValueObject,
81
+ } from '@haskou/value-objects';
82
+
83
+ export class Username extends StringValueObject {
84
+ constructor(value: string) {
85
+ super(value, 30);
86
+
87
+ if (NullObject.isNullObject(this)) return this;
88
+
89
+ assert(!this.isEmpty(), 'Username cannot be empty');
90
+ assert(value.length >= 3, 'Username must contain at least 3 characters');
91
+ assert(
92
+ /^[a-zA-Z0-9_]+$/.test(value),
93
+ 'Username contains invalid characters',
94
+ );
57
95
  }
58
96
  }
59
97
  ```
60
98
 
61
- ## Nullish construction
99
+ Now the rules belong to the domain type:
100
+
101
+ ```typescript
102
+ const username = new Username('haskou');
103
+
104
+ function createUser(username: Username) {
105
+ // no need to validate the username again
106
+ }
107
+ ```
108
+
109
+ 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.
110
+
111
+ ## Null without defensive programming
62
112
 
63
- Automatic `NullObject` creation remains enabled by default for compatibility. Applications that prefer strict construction can disable it globally:
113
+ Missing values can be represented through the `NullObject` pattern instead of propagating `null | undefined` checks throughout the domain.
64
114
 
65
115
  ```typescript
66
- import { StringValueObject, ValueObject } from '@haskou/value-objects';
116
+ import {
117
+ NullObject,
118
+ NumberValueObject,
119
+ } from '@haskou/value-objects';
120
+
121
+ const limit = new NumberValueObject(undefined as never);
122
+
123
+ NullObject.isNullObject(limit);
124
+ // true
125
+ ```
126
+
127
+ The object can safely represent absence and pass through the domain without requiring:
128
+
129
+ ```typescript
130
+ if (limit !== null && limit !== undefined) {
131
+ // ...
132
+ }
133
+ ```
134
+
135
+ But a `NullObject` does not silently pretend to contain a valid value.
136
+
137
+ Calling behaviour that requires that value fails explicitly:
138
+
139
+ ```typescript
140
+ const limit = new NumberValueObject(undefined as never);
141
+
142
+ limit.isGreaterThan(10);
143
+ // throws NullObjectError
144
+ ```
145
+
146
+ This lets missing values travel through the same domain abstraction while still failing immediately when code tries to actually use them.
147
+
148
+ Applications that prefer strict construction can disable automatic `NullObject` creation:
149
+
150
+ ```typescript
151
+ import {
152
+ NumberValueObject,
153
+ ValueObject,
154
+ } from '@haskou/value-objects';
67
155
 
68
156
  ValueObject.disableNullObjectCreation();
69
157
 
70
- new StringValueObject(undefined as never); // throws NullObjectCreationDisabledError
158
+ new NumberValueObject(undefined as never);
159
+ // throws NullObjectCreationDisabledError
160
+ ```
161
+
162
+ ## Assertions
71
163
 
72
- ValueObject.enableNullObjectCreation();
164
+ The library exports an `assert` helper for expressing invariants without deeply nested validation branches.
165
+
166
+ Instead of:
167
+
168
+ ```typescript
169
+ if (value !== undefined) {
170
+ if (value.length > 0) {
171
+ if (value.length <= 100) {
172
+ // ...
173
+ }
174
+ }
175
+ }
73
176
  ```
74
177
 
75
- The VitePress guide documents the default and strict modes in detail.
178
+ Use:
76
179
 
77
- ## Available categories
180
+ ```typescript
181
+ import { assert } from '@haskou/value-objects';
78
182
 
79
- | Category | Examples |
80
- | --- | --- |
81
- | Base | `ValueObject`, `NullObject`, `Enum` |
82
- | Strings | `StringValueObject`, `Password`, `Email`, `Color` |
83
- | Numbers | `NumberValueObject`, `Integer`, `PositiveNumber` |
84
- | IDs | `ShortId`, `UUID` |
85
- | Time | `Timestamp`, `CalendarDay`, `Hour`, `Duration`, `MonthOfYear` |
86
- | Coordinates | `Latitude`, `Longitude`, `Coordinates` |
87
- | Hashes | `MD5Hash`, `SHA256Hash`, `SHA512Hash` |
88
- | Media | `Media` |
89
- | Collections | `UniqueObjectArray` |
183
+ assert(value !== undefined, 'Value is required');
184
+ assert(value.length > 0, 'Value cannot be empty');
185
+ assert(value.length <= 100, 'Value is too long');
186
+ ```
90
187
 
91
- See the complete API reference in the documentation: https://haskou.github.io/value-objects/reference/
188
+ Failed assertions throw the supplied error, or a `DomainError` when given a string.
92
189
 
93
- ## Documentation development
190
+ Successful assertions use TypeScript assertion signatures, so the condition is narrowed afterwards.
94
191
 
95
- ```bash
96
- # Start local docs server
97
- yarn docs:dev
192
+ ## Quick start
98
193
 
99
- # Build static documentation
100
- yarn docs:build
194
+ ```typescript
195
+ import {
196
+ Color,
197
+ Email,
198
+ Hour,
199
+ PositiveNumber,
200
+ } from '@haskou/value-objects';
101
201
 
102
- # Preview the built site
103
- yarn docs:preview
202
+ const email = new Email('user@example.com');
203
+ const price = new PositiveNumber(29.99);
204
+ const color = new Color('#FF0000');
205
+ const hour = new Hour('09:30');
206
+
207
+ email.valueOf(); // 'user@example.com'
208
+ price.valueOf(); // 29.99
209
+ color.toString(); // '#FF0000'
210
+ hour.addMinutes(45).toString(); // '10:15'
211
+ ```
212
+
213
+ Validation errors use specific error types:
214
+
215
+ ```typescript
216
+ import {
217
+ Email,
218
+ InvalidEmailError,
219
+ } from '@haskou/value-objects';
220
+
221
+ try {
222
+ new Email('not-an-email');
223
+ } catch (error) {
224
+ if (error instanceof InvalidEmailError) {
225
+ // Handle invalid domain input.
226
+ }
227
+ }
104
228
  ```
105
229
 
106
- ## Package development
230
+ ## Available Value Objects
231
+
232
+ | Category | Examples |
233
+ | ----------- | ------------------------------------------------------------- |
234
+ | Base | `ValueObject`, `NullObject`, `Enum` |
235
+ | Strings | `StringValueObject`, `Password`, `Email`, `Color` |
236
+ | Numbers | `NumberValueObject`, `Integer`, `PositiveNumber` |
237
+ | Identifiers | `ShortId`, `UUID` |
238
+ | Date & time | `Timestamp`, `CalendarDay`, `Hour`, `Duration`, `MonthOfYear` |
239
+ | Coordinates | `Latitude`, `Longitude`, `Coordinates` |
240
+ | Hashes | `MD5Hash`, `SHA256Hash`, `SHA512Hash` |
241
+ | Media | `Media` |
242
+ | Collections | `UniqueObjectArray` |
243
+
244
+ The package is framework-agnostic and can be used in any TypeScript domain layer regardless of the framework, ORM or persistence technology around it.
245
+
246
+ ## Documentation
247
+
248
+ Full documentation:
249
+
250
+ https://haskou.github.io/value-objects/
251
+
252
+ API reference:
253
+
254
+ https://haskou.github.io/value-objects/reference/
255
+
256
+ ## Development
107
257
 
108
258
  ```bash
259
+ git clone https://github.com/haskou/value-objects.git
260
+ cd value-objects
261
+
109
262
  yarn install
110
263
  yarn test
111
264
  yarn build
112
265
  ```
113
266
 
114
- `yarn build` type-checks the production configuration, emits a CommonJS implementation and an ESM wrapper, generates declarations, and verifies both entry points share classes and configuration.
115
-
116
- ## Release branches
117
-
118
- Publishing is handled by CI when a pull request is merged into the default branch.
267
+ ## Contributing
119
268
 
120
- | Branch prefix | npm bump |
121
- | --- | --- |
122
- | `fix/*` | Patch |
123
- | `feat/*` | Minor |
124
- | `break/*` | Major |
269
+ Bug reports, feature requests and pull requests are welcome.
125
270
 
126
- Branches without one of these prefixes still run CI, but they do not publish to npm.
271
+ https://github.com/haskou/value-objects/issues
127
272
 
128
273
  ## License
129
274
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@haskou/value-objects",
3
- "version": "7.0.3",
3
+ "version": "7.1.1",
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",
@@ -71,16 +71,16 @@
71
71
  "@arethetypeswrong/core": "^0.18.5",
72
72
  "@haskou/eslint-config": "^0.2.2",
73
73
  "@types/jest": "^30.0.0",
74
- "@types/node": "^26.6.1",
75
- "eslint": "^10.10.0",
76
- "jest": "^30.5.1",
74
+ "@types/node": "^26.6.4",
75
+ "eslint": "^10.11.0",
76
+ "jest": "^30.5.2",
77
77
  "jest-extended": "^7.0.0",
78
- "prettier": "^3.9.7",
79
- "publint": "^0.3.24",
80
- "ts-jest": "^29.4.12",
78
+ "prettier": "^3.9.9",
79
+ "publint": "^0.3.25",
80
+ "ts-jest": "^29.4.14",
81
81
  "tsdown": "^0.23.0",
82
82
  "typescript": "^6.0.3",
83
83
  "vitepress": "^1.6.4",
84
- "vue": "^3.5.42"
84
+ "vue": "^3.5.43"
85
85
  }
86
86
  }