@haskou/value-objects 2.10.1 β†’ 2.11.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 (2) hide show
  1. package/README.md +70 -252
  2. package/package.json +32 -4
package/README.md CHANGED
@@ -3,292 +3,110 @@
3
3
  [![npm version](https://img.shields.io/npm/v/@haskou/value-objects.svg)](https://www.npmjs.com/package/@haskou/value-objects)
4
4
  [![license](https://img.shields.io/npm/l/@haskou/value-objects.svg)](LICENSE.txt)
5
5
 
6
- # Value Objects
6
+ ## Documentation
7
7
 
8
- A TypeScript lightweight library for creating safe, immutable,
9
- and validated **Value Objects**. Perfect for applications that require **Domain-Driven Design (DDD)** and **type safety**.
8
+ Full documentation is available at **https://haskou.github.io/value-objects/**.
10
9
 
11
- ## πŸš€ Quick Start
10
+ The documentation includes installation, quick start, examples, error handling, serialization notes, and one reference page per exported class.
12
11
 
13
- ```bash
14
- npm install @haskou/value-objects
15
- ```
12
+ Reusable agent instructions and engineering skills are available at **https://github.com/haskou/ddd-engineer-skills**.
16
13
 
17
- ```typescript
18
- import { Email, PositiveNumber, Color, Hour } from '@haskou/value-objects';
14
+ # Value Objects
19
15
 
20
- // βœ… Automatic validation on construction
21
- const email = new Email('user@example.com'); // Valid
22
- const price = new PositiveNumber(29.99); // Valid
23
- const color = new Color('#FF0000'); // Valid
24
- const hour = new Hour('09:30'); // Valid
16
+ A TypeScript library for validated, immutable primitive wrappers and small utility value objects.
25
17
 
26
- // ❌ Immediate errors with invalid values
27
- const badEmail = new Email('not-an-email'); // Error!
28
- const badPrice = new PositiveNumber(-5); // Error!
29
- ```
18
+ It provides ready-to-use objects for strings, numbers, identifiers, dates, coordinates, hashes, media, collections, and cryptographic payload helpers. Validation happens when an object is created, so code receiving one of these objects can rely on its shape.
30
19
 
31
- ## πŸ€” Why use Value Objects?
20
+ ## Installation
32
21
 
33
- **Without Value Objects:**
34
- ```typescript
35
- function createUser(email: string, age: number): void {
36
- // Are they valid? We don't know until runtime
37
- if (!email.includes('@')) throw new Error('Invalid email');
38
- if (age <= 0) throw new Error('Invalid age');
39
- // Creation stuff
40
- }
22
+ ```bash
23
+ npm install @haskou/value-objects
41
24
  ```
42
25
 
43
- **With Value Objects:**
44
- ```typescript
45
- function createUser(email: Email, age: PositiveNumber): void {
46
- // If we get here, values ARE valid
47
- // Creation stuff
48
- }
26
+ ```bash
27
+ yarn add @haskou/value-objects
49
28
  ```
50
29
 
51
- This prevents defensive programming and if-else validations
52
- since it implements Null Object pattern (see technical documentation).
53
- Also value-objects can be modified easily keeping the same logic along
54
- your application.
55
-
56
- ## πŸ“¦ Available Value Objects
57
-
58
- ### πŸ”€ Basic
59
- - **`StringValueObject`** - Strings with length validation
60
- - **`Password`** - Password strings with length and complexity validation
61
- - **`NumberValueObject`** - Numbers with math operations
62
- - **`Integer`** - Whole numbers
63
- - **`PositiveNumber`** - Positive numbers (> 0)
64
-
65
- ### πŸ†” Identifiers
66
- - **`ShortId`** - MongoDB ObjectId strings (24‑character hex)
67
- - **`UUID`** - Universally Unique Identifier (v4)
68
-
69
- ### ✨ Specialized
70
- - **`Email`** - Email addresses with automatic validation
71
- - **`Color`** - Hex colors (#FF0000)
72
-
73
- ### πŸ•’ Time
74
- - **`Hour`** - Time in 24h format (09:30)
75
- - **`Year`** - Years with leap year calculations
76
- - **`CalendarDay`** - Calendar days
77
- - **`Day`** - Days of month (1-31)
78
- - **`DayOfWeek`** - Days of the week
79
- - **`Month`** - Months (1-12)
80
- - **`MonthOfYear`** - Month/year combinations
81
- - **`Timestamp`** - Timestamps with operations
82
- - **`TimestampInterval`** - Time intervals
83
- - **`Duration`** - Duration in milliseconds
84
-
85
- ### 🌍 Coordinates
86
- - **`Latitude`** - Latitude (-90 to 90)
87
- - **`Longitude`** - Longitude (-180 to 180)
88
- - **`Coordinates`** - Coordinate pairs
89
-
90
- ### πŸ” Cryptography
91
- - **`KeyPair`** - Ed25519 key pair generation for signing and verification
92
- - **`PrivateKey`** - Ed25519 private key (PEM format) for signing; also accepted by asymmetric payload decryption for data addressed to the matching key pair
93
- - **`PublicKey`** - Ed25519 public key (PEM format) for signature verification; also accepted by asymmetric payload encryption to address data to the matching key pair
94
- - **`Signature`** - Base64-encoded Ed25519 digital signature
95
- - **`EncryptedPayload`** - Base container for dot-separated encrypted payload formats
96
- - **`AsymmetricEncryptedPayload`** - Payload encrypted for an Ed25519/X25519 recipient key pair
97
- - **`SymmetricEncryptedPayload`** - Payload encrypted with a symmetric AES-256-GCM key
98
- - **`SymmetricKey`** - 32-byte AES-256-GCM key, randomly generated or deterministically derived from a `Password` and salt with scrypt
99
- - **`EncryptedPrivateKey`** - Password-protected private key encryption using scrypt + AES-256-GCM
100
- - **`EncryptedKeyPair`** - Key pair with encrypted private key
101
-
102
- Technical notes:
103
-
104
- - Asymmetric payload encryption is a classical hybrid scheme. It generates an ephemeral
105
- X25519 key pair, converts the recipient Ed25519 key material to Montgomery
106
- form for X25519 key agreement, derives a shared secret, then derives a
107
- 256-bit AES key with HKDF-SHA256 and encrypts the payload with AES-256-GCM.
108
- - The current asymmetric payload format is
109
- `v2.x25519-hkdf-sha256-aes-256-gcm.ephemeralPublicKey.iv.cipherText.tag`,
110
- with Base64-encoded key, IV, ciphertext, and tag fields. The previous
111
- `ephemeralPublicKey.iv.cipherText.tag` format still decrypts for backward
112
- compatibility.
113
- - The v2 asymmetric header is authenticated as AES-GCM AAD. HKDF uses
114
- `ephemeralPublicKey || recipientPublicKey` as salt and
115
- `@haskou/value-objects/asymmetric-payload/v2` as domain-separated info.
116
- - Symmetric payload encryption uses a 32-byte AES-256-GCM key directly. The
117
- payload format is `v1.aes-256-gcm.iv.cipherText.tag`, with Base64-encoded
118
- IV, ciphertext, and tag fields.
119
- - `SymmetricKey.generate()` creates a random 256-bit key.
120
- `SymmetricKey.fromPasswordUsingOwasp()` derives a 256-bit key with the OWASP
121
- scrypt profile used by this package for new password-derived keys.
122
- `SymmetricKey.fromPassword()` keeps its original defaults for backward
123
- compatibility with existing encrypted data.
124
- - A 32-byte key provides the AES-256 key size. Real security depends on using
125
- either a high-entropy random key or a strong password with a unique,
126
- non-empty salt. AES-GCM also requires that IVs do not repeat for the same key;
127
- the library generates a random 96-bit IV for each encryption.
128
- - Asymmetric payload encryption is intended for small payloads and is currently
129
- capped at 1 MiB before encryption. Symmetric payload encryption is capped at
130
- 8 MiB before encryption.
131
- - New encrypted private keys use `v3.scrypt.N16384.r8.p5` with a 16-byte salt,
132
- then AES-256-GCM with a 12-byte IV and 16-byte authentication tag. The older
133
- v2 scrypt profile and legacy PBKDF2 format still decrypt, and
134
- `needsReEncryption()` marks them for upgrade. The v3 header is authenticated
135
- as AES-GCM AAD.
136
- - This is not a post-quantum cryptography scheme. Ed25519 and X25519 are
137
- classical elliptic-curve primitives, so neither signatures nor payload key
138
- agreement are post-quantum secure.
139
- - Payload encryption uses fresh ephemeral key material and a random IV per
140
- encryption, but captured payloads can still be decrypted later if the
141
- recipient private key is compromised. Treat it as recipient-addressed
142
- encryption with ciphertext integrity, not as sender-authenticated encryption
143
- or a forward-secret transport protocol.
144
- - The payload encryption format is library-specific, not HPKE, and has not been
145
- independently audited as a protocol.
146
-
147
- ### πŸ“Ž Media
148
- - **`Media`** - Binary/string content with Buffer, size, and Base64 helpers
149
-
150
- ### #️ Hashes
151
- - **`MD5Hash`** - MD5 hashes with helpers
152
- - **`SHA256Hash`** - SHA‑256 hashes with helpers
153
- - **`SHA512Hash`** - SHA‑512 hashes with helpers
154
-
155
- ### πŸ“ Other
156
- - **`Enum`** - Base class for typed enumerations
157
- - **`UniqueObjectArray`** - Iterable collection that keeps comparable items unique
158
-
159
- ## πŸ’‘ Basic Examples
30
+ ## Quick start
160
31
 
161
32
  ```typescript
162
- // Validated strings
163
- const name = new StringValueObject('John Doe');
164
- const code = new StringValueObject('ABC', 3); // max 3 characters
165
-
166
- // Numbers with operations
167
- const price = new NumberValueObject(29.99);
168
- const discount = new NumberValueObject(5.00);
169
- const total = price.subtract(discount); // 24.99
33
+ import { Color, Email, Hour, PositiveNumber } from '@haskou/value-objects';
170
34
 
171
- // Emails
172
35
  const email = new Email('user@example.com');
173
- console.log(email.getDomain()); // 'example.com'
36
+ const price = new PositiveNumber(29.99);
37
+ const color = new Color('#FF0000');
38
+ const hour = new Hour('09:30');
174
39
 
175
- // Colors
176
- const red = new Color('#FF0000');
177
- const blue = Color.BLUE; // Predefined color
40
+ console.log(email.valueOf()); // 'user@example.com'
41
+ console.log(price.valueOf()); // 29.99
42
+ console.log(color.toString()); // '#FF0000'
43
+ console.log(hour.addMinutes(45).toString()); // '10:15'
44
+ ```
178
45
 
179
- // Coordinates
180
- const latitude = new Latitude(40.7128); // New York
181
- const longitude = new Longitude(-74.0060);
182
- const coords = new Coordinates(latitude, longitude);
46
+ Invalid values throw specific errors.
183
47
 
184
- // Time
185
- const hour = new Hour('09:30');
186
- const year = new Year(2024);
187
- console.log(year.isLeapYear()); // true
188
-
189
- // IDs
190
- const id = ShortId.generate(); // 69ad70897364ee0d1406b1d0
191
- const uuid = UUID.generate(); // 3fd4c04a-8e73-4e10-aef3-f491b32ec538
192
-
193
- // Hashes
194
- const md5 = MD5Hash.from('hello'); // 5d41402abc4b2a76b9719d911017c592
195
- const sha256 = SHA256Hash.from('hello'); // 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824
196
- const sha512 = SHA512Hash.from('hello'); // 9b71d224bd62f3785d96d46ad3ea3d73319bfbc2890caadae2dff72519673ca72323c3d99ba5c11d7c7acc6e14b8c5da0c4663475c2e5c3adef46f73bcdec043
197
- console.log(md5.toBase64());
198
-
199
- // Crypto
200
- const keyPair = await KeyPair.generate(); // Ed25519 key pair
201
- const signature = keyPair.sign('hello world'); // Sign a message
202
- keyPair.isValidSignature('hello world', signature); // true
203
-
204
- // Encrypted key pairs
205
- const cryptoPassword = new Password('Secure-password-123!');
206
- const encrypted = await keyPair.encryptKeyPair(cryptoPassword);
207
- const sig = await encrypted.sign('message', cryptoPassword);
208
- encrypted.isValidSignature('message', sig); // true
209
-
210
- // Symmetric payload encryption
211
- const symmetricKey = SymmetricKey.generate();
212
- const symmetricPayload = symmetricKey.encrypt('secret data');
213
- const plaintext = symmetricKey.decrypt(symmetricPayload);
214
- console.log(plaintext.toString()); // 'secret data'
215
-
216
- // Deterministic key derivation from a password and explicit salt
217
- const derivedKey = await SymmetricKey.fromPasswordUsingOwasp(
218
- cryptoPassword,
219
- { salt: 'stable-application-salt' },
220
- );
221
-
222
- // Media
223
- const media = new Media('hello world');
224
- console.log(media.getSize()); // 11
225
- console.log(media.getBase64()); // 'aGVsbG8gd29ybGQ='
226
- console.log(media.getBuffer()); // <Buffer 68 65 6c 6c 6f ...>
227
-
228
- // Unique collections
229
- const weekdays = UniqueObjectArray.fromArray([
230
- DayOfWeek.MONDAY,
231
- DayOfWeek.TUESDAY,
232
- DayOfWeek.MONDAY,
233
- ]);
234
- console.log(weekdays.length()); // 2
235
- console.log(weekdays.toArray().map((day) => day.toString())); // ['monday', 'tuesday']
48
+ ```typescript
49
+ import { Email, InvalidEmailError } from '@haskou/value-objects';
50
+
51
+ try {
52
+ new Email('not-an-email');
53
+ } catch (error) {
54
+ if (error instanceof InvalidEmailError) {
55
+ console.error('Invalid email');
56
+ }
57
+ }
236
58
  ```
237
59
 
238
- ## πŸ“š Technical Documentation
239
-
240
- Need more details? Check the complete documentation:
60
+ ## Available categories
241
61
 
242
- **[πŸ“– TECHNICAL_DOCUMENTATION.md](./TECHNICAL_DOCUMENTATION.md)**
62
+ | Category | Examples |
63
+ | --- | --- |
64
+ | Base | `ValueObject`, `NullObject`, `Enum` |
65
+ | Strings | `StringValueObject`, `Password`, `Email`, `Color` |
66
+ | Numbers | `NumberValueObject`, `Integer`, `PositiveNumber` |
67
+ | IDs | `ShortId`, `UUID` |
68
+ | Time | `Timestamp`, `CalendarDay`, `Hour`, `Duration`, `MonthOfYear` |
69
+ | Coordinates | `Latitude`, `Longitude`, `Coordinates` |
70
+ | Hashes | `MD5Hash`, `SHA256Hash`, `SHA512Hash` |
71
+ | Media | `Media` |
72
+ | Collections | `UniqueObjectArray` |
73
+ | Crypto helpers | `KeyPair`, `PrivateKey`, `PublicKey`, `SymmetricKey`, encrypted payload objects |
243
74
 
244
- Includes:
245
- - βœ… Complete API for all Value Objects
246
- - βœ… Advanced usage examples
247
- - βœ… Error handling
248
- - βœ… Design principles (immutability, null safety, etc.)
249
- - βœ… Composition and extensibility patterns
75
+ See the complete API reference in the documentation: https://haskou.github.io/value-objects/reference/
250
76
 
251
- ## πŸ› οΈ Development
77
+ ## Documentation development
252
78
 
253
79
  ```bash
254
- # Install dependencies
255
- npm install
80
+ # Start local docs server
81
+ yarn docs:dev
256
82
 
257
- # Run tests
258
- npm test
83
+ # Build static documentation
84
+ yarn docs:build
259
85
 
260
- # Build
261
- npm run build
86
+ # Preview the built site
87
+ yarn docs:preview
262
88
  ```
263
89
 
264
- ## 🌿 Release Branches
265
-
266
- Publishing is handled by CI when a pull request is merged into the default
267
- branch (`master`, or `main` after a branch rename). Use these branch prefixes
268
- to choose the npm version bump:
90
+ ## Package development
269
91
 
270
- - `fix/*` - patch release
271
- - `feat/*` - minor release
272
- - `break/*` - major release
273
-
274
- Branches without one of these prefixes still run CI, but they do not publish
275
- to npm. Publishing uses npm Trusted Publishing from the `ci.yml` workflow.
276
- After a successful publish, CI commits the release version to the default
277
- branch, creates the matching `vX.Y.Z` Git tag, and publishes a compact GitHub
278
- Release with the PR title, npm package link, release branch, and PR notes.
92
+ ```bash
93
+ yarn install
94
+ yarn test
95
+ yarn build
96
+ ```
279
97
 
280
- ## 🀝 Contributing
98
+ ## Release branches
281
99
 
282
- 1. Fork the repository
283
- 2. Create a branch: `git checkout -b feat/my-feature`
284
- 3. Make your changes and add tests
285
- 4. Run tests: `npm test`
286
- 5. Submit a pull request
100
+ Publishing is handled by CI when a pull request is merged into the default branch.
287
101
 
288
- ## πŸ“„ License
102
+ | Branch prefix | npm bump |
103
+ | --- | --- |
104
+ | `fix/*` | Patch |
105
+ | `feat/*` | Minor |
106
+ | `break/*` | Major |
289
107
 
290
- MIT License - see the [LICENSE](LICENSE.txt) file for details.
108
+ Branches without one of these prefixes still run CI, but they do not publish to npm.
291
109
 
292
- ---
110
+ ## License
293
111
 
294
- **Made with ❀️ and TypeScript**
112
+ MIT. See [LICENSE.txt](LICENSE.txt).
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "@haskou/value-objects",
3
- "version": "2.10.1",
3
+ "version": "2.11.0",
4
+ "description": "TypeScript value objects for validated, immutable primitive values and small domain-safe data structures.",
4
5
  "main": "dist/index.js",
5
6
  "types": "dist/index.d.ts",
6
7
  "exports": {
@@ -19,6 +20,28 @@
19
20
  "engines": {
20
21
  "node": ">=20.19.0"
21
22
  },
23
+ "keywords": [
24
+ "value-object",
25
+ "value-objects",
26
+ "typescript",
27
+ "validation",
28
+ "validated-types",
29
+ "immutable",
30
+ "immutability",
31
+ "type-safety",
32
+ "domain-primitives",
33
+ "primitive-obsession",
34
+ "ddd",
35
+ "domain-driven-design",
36
+ "email-validation",
37
+ "uuid",
38
+ "hash",
39
+ "crypto",
40
+ "aes-256-gcm",
41
+ "ed25519",
42
+ "x25519",
43
+ "scrypt"
44
+ ],
22
45
  "publishConfig": {
23
46
  "access": "public"
24
47
  },
@@ -29,14 +52,17 @@
29
52
  "bugs": {
30
53
  "url": "https://github.com/haskou/value-objects/issues"
31
54
  },
32
- "homepage": "https://github.com/haskou/value-objects#readme",
55
+ "homepage": "https://haskou.github.io/value-objects/",
33
56
  "scripts": {
34
57
  "lint": "eslint ./src --ext .ts",
35
58
  "lint:fix": "eslint ./src --ext .ts --fix",
36
59
  "test": "yarn lint && yarn test:unit",
37
60
  "test:unit": "jest --runInBand",
38
61
  "test:coverage": "NODE_ENV=test jest --collect-coverage",
39
- "build": "rm -rf dist/ && tsc"
62
+ "build": "rm -rf dist/ && tsc",
63
+ "docs:dev": "vitepress dev docs",
64
+ "docs:build": "vitepress build docs",
65
+ "docs:preview": "vitepress build docs && vitepress preview docs"
40
66
  },
41
67
  "dependencies": {
42
68
  "@noble/ciphers": "^2.2.0",
@@ -60,6 +86,8 @@
60
86
  "jest-extended": "^7.0.0",
61
87
  "prettier": "^3.8.4",
62
88
  "ts-jest": "^29.4.11",
63
- "typescript": "^6.0.3"
89
+ "typescript": "^6.0.3",
90
+ "vitepress": "^1.6.4",
91
+ "vue": "^3.5.22"
64
92
  }
65
93
  }