@vytches/ddd-value-objects 0.31.0 → 0.32.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/LLMGUIDE.md +7 -0
- package/README.md +77 -0
- package/package.json +4 -4
package/LLMGUIDE.md
CHANGED
|
@@ -318,6 +318,13 @@ cancelOrder(orderId); // OK
|
|
|
318
318
|
Available factories: `createBrandedId<Tag>(entityId)`, `newBrandedId<Tag>()`,
|
|
319
319
|
`brandedIdFromUUID<Tag>(uuid)`, `brandedIdFromText<Tag>(text)`.
|
|
320
320
|
|
|
321
|
+
## Related Guides
|
|
322
|
+
|
|
323
|
+
- [`docs/guides/specification-vs-policy.md`](../../docs/guides/specification-vs-policy.md)
|
|
324
|
+
— "where do I validate what" decision tree: when a rule belongs in a VO's
|
|
325
|
+
`validate()` constructor invariant vs `Specification` /
|
|
326
|
+
`BusinessRuleValidator` / `PolicyBuilder`.
|
|
327
|
+
|
|
321
328
|
## Package Dependencies
|
|
322
329
|
|
|
323
330
|
**Depends on:** `@vytches/ddd-contracts`, `@vytches/ddd-domain-primitives`,
|
package/README.md
CHANGED
|
@@ -121,6 +121,83 @@ shipOrder(orderId); // OK
|
|
|
121
121
|
shipOrder(customerId); // TypeScript compile error!
|
|
122
122
|
```
|
|
123
123
|
|
|
124
|
+
## Keeping a specific validation error with `getInvalidValueMessage()`
|
|
125
|
+
|
|
126
|
+
_Since VF-023 — the constructor validates, see
|
|
127
|
+
[`CHANGELOG.md`](./CHANGELOG.md)._
|
|
128
|
+
|
|
129
|
+
`BaseValueObject`'s constructor calls `validate()` and throws on failure, before
|
|
130
|
+
the subclass constructor body runs. `validate()` returns a boolean, so any value
|
|
131
|
+
object whose real validation produces a _rich_ error — which field, which rule,
|
|
132
|
+
which expected checksum — has to swallow that error to satisfy the signature.
|
|
133
|
+
What reaches the caller is then the generic default:
|
|
134
|
+
|
|
135
|
+
```
|
|
136
|
+
Error: Invalid value for Regon
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Override `getInvalidValueMessage()` to put the specific message back. It is
|
|
140
|
+
called only on the failing path, so re-running the detailed validation there
|
|
141
|
+
costs nothing in the happy case:
|
|
142
|
+
|
|
143
|
+
```typescript
|
|
144
|
+
export abstract class NationalIdentifier extends BaseValueObject<IdentifierProps> {
|
|
145
|
+
/** Throws a rich, typed error. Called by validate() and, on failure, here. */
|
|
146
|
+
protected abstract assertValid(value: string): void;
|
|
147
|
+
|
|
148
|
+
validate(props: IdentifierProps): boolean {
|
|
149
|
+
try {
|
|
150
|
+
this.assertValid(props.value);
|
|
151
|
+
return props.locale === this.expectedLocale;
|
|
152
|
+
} catch {
|
|
153
|
+
return false; // the detail is lost here — recovered below
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
protected override getInvalidValueMessage(props: IdentifierProps): string {
|
|
158
|
+
try {
|
|
159
|
+
this.assertValid(props.value);
|
|
160
|
+
} catch (error) {
|
|
161
|
+
// "REGON has invalid checksum: expected 5, got 6"
|
|
162
|
+
return error instanceof Error ? error.message : String(error);
|
|
163
|
+
}
|
|
164
|
+
return `${this.typeName} requires locale '${this.expectedLocale}', got '${props.locale}'`;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Two constraints apply, both from the undefined-during-`super()` trap: the
|
|
170
|
+
override may read only its `value` parameter and state the base constructor has
|
|
171
|
+
already set (`this.value` is assigned before the throw), never a field
|
|
172
|
+
initialized in the subclass constructor — that field is still `undefined` here.
|
|
173
|
+
|
|
174
|
+
A `Result`-returning factory keeps the typed error by re-wrapping what the
|
|
175
|
+
constructor threw:
|
|
176
|
+
|
|
177
|
+
```typescript
|
|
178
|
+
static create(value: string): Result<Regon, IdentifierValidationError> {
|
|
179
|
+
try {
|
|
180
|
+
return Result.ok(new Regon({ value, locale: 'pl-PL' }));
|
|
181
|
+
} catch (error) {
|
|
182
|
+
if (error instanceof IdentifierValidationError) return Result.fail(error);
|
|
183
|
+
// Generic Error from the base constructor — message already specific,
|
|
184
|
+
// thanks to getInvalidValueMessage() above.
|
|
185
|
+
return Result.fail(
|
|
186
|
+
new IdentifierValidationError(
|
|
187
|
+
error instanceof Error ? error.message : 'Unknown error',
|
|
188
|
+
'REGON'
|
|
189
|
+
)
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
**Migration note.** Before VF-023 the common shape was `new X(props)` followed
|
|
196
|
+
by `x.assertValid(...)` in the factory — the constructor did not validate, so
|
|
197
|
+
the factory's own call produced the error. That second call is now unreachable:
|
|
198
|
+
the base class throws first. A factory relying on it silently changes its error
|
|
199
|
+
type from the typed one to a plain `Error`.
|
|
200
|
+
|
|
124
201
|
## Partial-identity equality with `getIdentityComponents()`
|
|
125
202
|
|
|
126
203
|
_Since VF-036 — additive minor, see [`CHANGELOG.md`](./CHANGELOG.md)._
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vytches/ddd-value-objects",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.32.0",
|
|
4
4
|
"description": "Enhanced value objects and EntityId implementations",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -34,9 +34,9 @@
|
|
|
34
34
|
"directory": "packages/value-objects"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@vytches/ddd-contracts": "0.
|
|
38
|
-
"@vytches/ddd-
|
|
39
|
-
"@vytches/ddd-
|
|
37
|
+
"@vytches/ddd-contracts": "0.32.0",
|
|
38
|
+
"@vytches/ddd-domain-primitives": "0.32.0",
|
|
39
|
+
"@vytches/ddd-utils": "0.32.0"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"typescript": "^5.0.0",
|