@vytches/ddd-value-objects 0.26.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 VytchesDDD
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/LLMGUIDE.md ADDED
@@ -0,0 +1,233 @@
1
+ # @vytches/ddd-value-objects - LLM Guide
2
+
3
+ ## Purpose
4
+
5
+ Provides `BaseValueObject` for immutable domain values and `EntityId` for
6
+ strongly-typed aggregate identifiers with built-in validation across four ID
7
+ strategies (UUID, integer, bigint, text).
8
+
9
+ ## Quick Start
10
+
11
+ ```typescript
12
+ import { BaseValueObject, EntityId } from '@vytches/ddd-value-objects';
13
+
14
+ // Create a value object
15
+ class Money extends BaseValueObject<number> {
16
+ static create(amount: number): Money {
17
+ const vo = new Money(amount);
18
+ if (!vo.validate(amount)) throw new Error('Amount must be non-negative');
19
+ return vo;
20
+ }
21
+ validate(value: unknown): boolean {
22
+ return typeof value === 'number' && value >= 0;
23
+ }
24
+ add(other: Money): Money {
25
+ return Money.create(this.getValue() + other.getValue());
26
+ }
27
+ }
28
+
29
+ // Create entity IDs
30
+ const id = EntityId.create(); // random UUID
31
+ const fromDb = EntityId.fromUUID('550e8400-e29b-41d4-a716-446655440000');
32
+ const intId = EntityId.fromInteger(42);
33
+ const textId = EntityId.fromText('order-slug');
34
+ ```
35
+
36
+ ## Key API
37
+
38
+ | Export | Kind | Description |
39
+ | ---------------------------- | ---------------- | --------------------------------------------------------------------------- |
40
+ | `BaseValueObject<T>` | abstract class | Immutable value holder; provides `equals`, `getValue`, `toString`, `toJSON` |
41
+ | `EntityId<T>` | class | Validated aggregate identifier; extends contracts `EntityId` |
42
+ | `EntityId.create()` | static method | Creates a new random-UUID `EntityId<string>` |
43
+ | `EntityId.fromUUID(v)` | static method | Parses and validates a UUID string |
44
+ | `EntityId.fromInteger(v)` | static method | Validates non-negative integer, stores as string |
45
+ | `EntityId.fromBigInt(v)` | static method | Validates bigint string/bigint, stores as string |
46
+ | `EntityId.fromText(v)` | static method | Validates non-empty text with safe characters |
47
+ | `EntityId.tryFromUUID(v)` | static method | Like `fromUUID` but returns `Result<EntityId, Error>` instead of throwing |
48
+ | `EntityId.tryFromInteger(v)` | static method | Like `fromInteger` but returns `Result<EntityId, Error>` |
49
+ | `EntityId.tryFromBigInt(v)` | static method | Like `fromBigInt` but returns `Result<EntityId, Error>` |
50
+ | `EntityId.tryFromText(v)` | static method | Like `fromText` but returns `Result<EntityId, Error>` |
51
+ | `EntityIdFactory` | class | **Deprecated.** Use `EntityId` static methods instead |
52
+ | `IEntityId<T>` | re-exported type | From `@vytches/ddd-contracts` — the interface |
53
+ | `IEntityIdFactory` | re-exported type | From `@vytches/ddd-contracts` — the factory interface |
54
+ | `IdType` | re-exported type | `'uuid' \| 'integer' \| 'text' \| 'bigint'` |
55
+
56
+ ### `BaseValueObject<T>` method reference
57
+
58
+ | Method | Description |
59
+ | ----------------------------------- | -------------------------------------------------- |
60
+ | `getValue(): T` | Returns the raw wrapped value |
61
+ | `equals(other): boolean` | Structural equality by raw value (`===`) |
62
+ | `toString(): string` | `String(value)` |
63
+ | `toJSON(): string` | `JSON.stringify(value)` |
64
+ | `abstract validate(value): boolean` | Must be implemented; called in subclass `create()` |
65
+
66
+ ## Patterns
67
+
68
+ ### Validated value object with factory
69
+
70
+ ```typescript
71
+ import { BaseValueObject } from '@vytches/ddd-value-objects';
72
+
73
+ class EmailAddress extends BaseValueObject<string> {
74
+ static create(raw: string): EmailAddress {
75
+ const vo = new EmailAddress(raw.toLowerCase().trim());
76
+ if (!vo.validate(raw)) {
77
+ throw new Error(`Invalid email: ${raw}`);
78
+ }
79
+ return vo;
80
+ }
81
+
82
+ validate(value: unknown): boolean {
83
+ return (
84
+ typeof value === 'string' && /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)
85
+ );
86
+ }
87
+ }
88
+
89
+ const email = EmailAddress.create('Alice@Example.com');
90
+ email.getValue(); // 'alice@example.com'
91
+ email.equals(EmailAddress.create('alice@example.com')); // true
92
+ ```
93
+
94
+ ### Using EntityId as aggregate key
95
+
96
+ ```typescript
97
+ import { EntityId } from '@vytches/ddd-value-objects';
98
+ import { AggregateRoot } from '@vytches/ddd-aggregates';
99
+ import type { IAggregateConstructorParams } from '@vytches/ddd-aggregates';
100
+
101
+ class Order extends AggregateRoot<string> {
102
+ constructor(params: IAggregateConstructorParams<string>) {
103
+ super(params);
104
+ }
105
+
106
+ static create(): Order {
107
+ return new Order({ id: EntityId.create(), version: 0 });
108
+ }
109
+ }
110
+
111
+ const order = Order.create();
112
+ order.getId().getValue(); // UUID string
113
+ order.getId().getType(); // 'uuid'
114
+ order.getId().toString(); // same UUID string
115
+ ```
116
+
117
+ ### Comparing two IDs
118
+
119
+ ```typescript
120
+ import { EntityId } from '@vytches/ddd-value-objects';
121
+
122
+ const a = EntityId.fromUUID('550e8400-e29b-41d4-a716-446655440000');
123
+ const b = EntityId.fromUUID('550e8400-e29b-41d4-a716-446655440000');
124
+
125
+ a.equals(b); // true
126
+ a.getValue() === b.getValue(); // true — same raw string
127
+ ```
128
+
129
+ ## Anti-Patterns
130
+
131
+ **Making value objects mutable.** `BaseValueObject.value` is `readonly` — never
132
+ expose a setter. All transformations must return a new instance (e.g., `add()`
133
+ returns `Money.create(...)`).
134
+
135
+ **Not implementing `validate()`.** The abstract method must be implemented.
136
+ Returning `true` unconditionally defeats the purpose. Call `validate` inside the
137
+ static factory and throw or return `Result.fail` there — do not throw inside the
138
+ constructor.
139
+
140
+ **Using primitive types instead of value objects for domain concepts.** Passing
141
+ `string` for email or `number` for money loses invariant enforcement at compile
142
+ time. Wrap every domain-meaningful primitive in a `BaseValueObject` subclass.
143
+
144
+ **Using `EntityIdFactory` in new code.** `EntityIdFactory` is deprecated. Use
145
+ `EntityId.create()`, `EntityId.fromUUID()`, etc. directly. The factory class
146
+ exists only for backward compatibility.
147
+
148
+ **Calling `new EntityId(value, type)` directly in application code.** The
149
+ constructor does not validate the value in the base class. Always use the static
150
+ factory methods (`create`, `fromUUID`, etc.) which throw `InvalidParameterError`
151
+ or `MissingValueError` on bad input.
152
+
153
+ ## Hidden Features
154
+
155
+ `EntityId.fromInteger` stores the integer as a string internally (returns
156
+ `EntityId<string>` not `EntityId<number>`) — this is intentional for consistent
157
+ serialization across all ID types.
158
+
159
+ `EntityId.fromBigInt` accepts both a native `bigint` and a `string`
160
+ representation — useful when reading from databases that return bigints as
161
+ strings.
162
+
163
+ `BaseValueObject.toJSON()` returns a JSON-serialized string of the value, not
164
+ the value itself — if you assign a value object to a JSON payload, call
165
+ `getValue()` instead of `toJSON()` to avoid double-encoding.
166
+
167
+ The `validate()` method on `BaseValueObject` receives `unknown` (not `T`) —
168
+ implement defensive type checks even though TypeScript narrows the parameter in
169
+ practice.
170
+
171
+ ## Result-Returning Factories (since 0.24.0)
172
+
173
+ Prefer `tryFromX()` over `fromX()` — returns `Result<EntityId, Error>` instead
174
+ of throwing, enabling functional error handling.
175
+
176
+ ```typescript
177
+ import { EntityId } from '@vytches/ddd-value-objects';
178
+
179
+ // Returns Result — never throws
180
+ const result = EntityId.tryFromUUID(userInput);
181
+
182
+ if (result.isFailure) {
183
+ console.log('Invalid ID:', result.error.message);
184
+ return;
185
+ }
186
+
187
+ const id = result.value; // EntityId<string>
188
+
189
+ // Chain with Result.map/flatMap
190
+ const idString = EntityId.tryFromUUID(userInput).map(id => id.value);
191
+ ```
192
+
193
+ Available: `tryFromUUID`, `tryFromInteger`, `tryFromBigInt`, `tryFromText`. The
194
+ `fromX()` throwing variants still work for backward compatibility.
195
+
196
+ ## Branded ID Types (since 0.24.0)
197
+
198
+ Prevent accidentally passing an OrderId where a CustomerId is expected. Zero
199
+ runtime overhead — branding exists only in the type system.
200
+
201
+ ```typescript
202
+ import {
203
+ type BrandedId,
204
+ newBrandedId,
205
+ brandedIdFromUUID,
206
+ } from '@vytches/ddd-value-objects';
207
+
208
+ // Define domain-specific ID types
209
+ type OrderId = BrandedId<'Order'>;
210
+ type CustomerId = BrandedId<'Customer'>;
211
+
212
+ // Create branded IDs
213
+ const orderId: OrderId = newBrandedId<'Order'>();
214
+ const customerId: CustomerId = brandedIdFromUUID<'Customer'>('550e8400-...');
215
+
216
+ // Type-safe function — compile error if wrong ID type passed
217
+ function cancelOrder(id: OrderId): void {
218
+ /* ... */
219
+ }
220
+ cancelOrder(orderId); // OK
221
+ // cancelOrder(customerId); // Compile error!
222
+ ```
223
+
224
+ Available factories: `createBrandedId<Tag>(entityId)`, `newBrandedId<Tag>()`,
225
+ `brandedIdFromUUID<Tag>(uuid)`, `brandedIdFromText<Tag>(text)`.
226
+
227
+ ## Package Dependencies
228
+
229
+ **Depends on:** `@vytches/ddd-contracts`, `@vytches/ddd-domain-primitives`,
230
+ `@vytches/ddd-utils`.
231
+
232
+ **Depended on by:** `@vytches/ddd-aggregates` (re-exports `EntityId` from
233
+ contracts), `@vytches/ddd-enterprise`.