@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.
Files changed (3) hide show
  1. package/LLMGUIDE.md +7 -0
  2. package/README.md +77 -0
  3. 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.31.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.31.0",
38
- "@vytches/ddd-utils": "0.31.0",
39
- "@vytches/ddd-domain-primitives": "0.31.0"
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",