@vytches/ddd-value-objects 0.30.0 → 0.31.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 +101 -7
- package/README.md +220 -14
- package/dist/base-value-object.d.ts +200 -1
- package/dist/id.value-object.d.ts +1 -50
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +242 -120
- package/package.json +7 -6
package/LLMGUIDE.md
CHANGED
|
@@ -55,13 +55,107 @@ const textId = EntityId.fromText('order-slug');
|
|
|
55
55
|
|
|
56
56
|
### `BaseValueObject<T>` method reference
|
|
57
57
|
|
|
58
|
-
| Method
|
|
59
|
-
|
|
|
60
|
-
| `getValue(): T`
|
|
61
|
-
| `equals(other): boolean`
|
|
62
|
-
| `toString(): string`
|
|
63
|
-
| `toJSON():
|
|
64
|
-
| `abstract validate(value): boolean`
|
|
58
|
+
| Method | Description |
|
|
59
|
+
| ---------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
60
|
+
| `getValue(): T` | Returns the raw wrapped value |
|
|
61
|
+
| `equals(other): boolean` | `===` for primitives, `LibUtils.deepEqual` for objects — unless `getIdentityComponents()` is overridden on both sides, in which case identity components decide equality instead (VF-036) |
|
|
62
|
+
| `toString(): string` | `String(value)` |
|
|
63
|
+
| `toJSON(): unknown` | Returns the raw value (not a JSON string) |
|
|
64
|
+
| `abstract validate(value): boolean` | Must be implemented; called in subclass `create()` |
|
|
65
|
+
| `getIdentityComponents(): readonly unknown[] \| undefined` | Protected opt-in hook (VF-036). Default returns `undefined` (raw comparison, unchanged). Override to compare by a subset/projection of fields instead of the full raw value; if either side returns `undefined`, `equals()` falls back to the raw comparison above |
|
|
66
|
+
|
|
67
|
+
## Partial-identity equality: `getIdentityComponents()` (VF-036)
|
|
68
|
+
|
|
69
|
+
**When to use it:** override this protected hook when only a subset of a value
|
|
70
|
+
object's `value` should participate in equality (e.g. exclude a display flag,
|
|
71
|
+
audit timestamp, or cache key from identity). Leave it unimplemented (default
|
|
72
|
+
returns `undefined`) when the full raw `value` should decide equality — that is
|
|
73
|
+
the common case.
|
|
74
|
+
|
|
75
|
+
```ts compile-check
|
|
76
|
+
import { BaseValueObject } from '@vytches/ddd-value-objects';
|
|
77
|
+
|
|
78
|
+
interface MoneyProps {
|
|
79
|
+
amount: number;
|
|
80
|
+
currency: string;
|
|
81
|
+
displayFormat: 'symbol' | 'code';
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
class Money extends BaseValueObject<MoneyProps> {
|
|
85
|
+
validate(value: unknown): boolean {
|
|
86
|
+
const v = value as MoneyProps;
|
|
87
|
+
return v.amount >= 0 && v.currency.length === 3;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// string-literal discriminator ('Money') scopes equality to this type —
|
|
91
|
+
// see "Type-scoped equality" below; displayFormat is excluded on purpose.
|
|
92
|
+
protected override getIdentityComponents(): readonly unknown[] {
|
|
93
|
+
return ['Money', this.value.amount, this.value.currency];
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
**Dispatch rule:** `equals()` calls the hook on both sides. Both defined →
|
|
99
|
+
compare components (same length, then element-wise, dispatching to nested
|
|
100
|
+
`.equals()` for value objects, `LibUtils.deepEqual` otherwise). Either side
|
|
101
|
+
`undefined` → unchanged raw comparison (`===` / `LibUtils.deepEqual`).
|
|
102
|
+
|
|
103
|
+
**Non-transitivity in mixed populations (collection hazard).** The fallback is
|
|
104
|
+
symmetric but not transitive: with A (no override), B (override, same raw
|
|
105
|
+
`value` as A), C (override, same components as B), `A.equals(B)` and
|
|
106
|
+
`B.equals(C)` can both be `true` while `A.equals(C)` is `false`. Code that
|
|
107
|
+
assumes an equivalence relation — `list.some(x => x.equals(y))`, de-duplication
|
|
108
|
+
by `.equals()` — breaks silently when raw-comparison and component-override
|
|
109
|
+
instances of the same base type are mixed. Migrate a whole hierarchy together
|
|
110
|
+
(see MIGRATION.md), never partially.
|
|
111
|
+
|
|
112
|
+
**`[]` footgun.** `[]` is defined-and-empty, not "opt out" — two `[]`- returning
|
|
113
|
+
instances are always equal to each other. Return `undefined` to opt out, never
|
|
114
|
+
`[]`.
|
|
115
|
+
|
|
116
|
+
**Fixed arity.** A class must always return the same number of components in the
|
|
117
|
+
same order. Comparison begins with a length check, so a conditional push
|
|
118
|
+
(`if (this.scope) parts.push(this.scope)`) makes two instances of one class
|
|
119
|
+
unequal because of an unset optional field rather than a real data difference.
|
|
120
|
+
Use a stable placeholder — `[this.tenant, this.scope ?? null, this.key]`. A
|
|
121
|
+
subclass that adds a component changes the arity of every comparison against its
|
|
122
|
+
parent; decide that deliberately.
|
|
123
|
+
|
|
124
|
+
**Undefined-because-uninitialized is a different trap than it sounds.** Only a
|
|
125
|
+
literal `undefined` returned _by the hook itself_ triggers the raw fallback; an
|
|
126
|
+
`undefined` _component value_ inside a defined array does not. The real trap is
|
|
127
|
+
a _conditional_ override that sometimes returns `undefined` — that silently and
|
|
128
|
+
intermittently downgrades the same class to raw comparison. Always return an
|
|
129
|
+
array once a class opts in.
|
|
130
|
+
|
|
131
|
+
**Throw propagation.** A throwing override propagates out of `equals()` uncaught
|
|
132
|
+
— no try/catch, by design (`equals()` is a hot path; the library logger is
|
|
133
|
+
diagnostics-only). `equals()` is no longer a total function for a subclass whose
|
|
134
|
+
override can throw.
|
|
135
|
+
|
|
136
|
+
**Frozen/readonly state only.** The constructor deep-freezes `value`, not any
|
|
137
|
+
subclass field assigned after `super(value)`. Components must derive only from
|
|
138
|
+
`value` or from subclass fields the subclass itself keeps immutable — deriving
|
|
139
|
+
from mutable state breaks equality stability over the instance's lifetime.
|
|
140
|
+
|
|
141
|
+
**Type-scoped equality idiom.** No type/`instanceof` check is performed —
|
|
142
|
+
cross-subclass equality with matching components stays `true`, same as raw
|
|
143
|
+
comparison today. To scope equality to one type, add a string literal as the
|
|
144
|
+
first component (`['Money', ...]`), never `this.constructor.name`
|
|
145
|
+
(minification-unsafe) or the class object itself (unsafe across duplicate
|
|
146
|
+
package copies).
|
|
147
|
+
|
|
148
|
+
**`toString()`/`toJSON()` stay value-based** regardless of this hook — both
|
|
149
|
+
always serialize the raw `value`. An override that narrows equality can desync
|
|
150
|
+
serialization from equality; keeping that coherent is the consumer's
|
|
151
|
+
responsibility.
|
|
152
|
+
|
|
153
|
+
> **Permanent note:** `getEqualityComponents()` — a similarly-named hook from an
|
|
154
|
+
> early (2025) documentation draft — was **never implemented** in any released
|
|
155
|
+
> version and will not be added as an alias/shim/fallback.
|
|
156
|
+
> `getIdentityComponents()` is the real hook, under a deliberately new name. Any
|
|
157
|
+
> existing `getEqualityComponents()` override has always been dead code; see
|
|
158
|
+
> root `MIGRATION.md` to migrate it.
|
|
65
159
|
|
|
66
160
|
## Patterns
|
|
67
161
|
|
package/README.md
CHANGED
|
@@ -17,20 +17,20 @@ pnpm add @vytches/ddd-value-objects
|
|
|
17
17
|
|
|
18
18
|
## What's included
|
|
19
19
|
|
|
20
|
-
| Export | Kind | Description
|
|
21
|
-
| ------------------------------ | --------- |
|
|
22
|
-
| `BaseValueObject<T>` | class | Abstract base for value objects; deep-freezes internal value; implements `equals()` via structural comparison
|
|
23
|
-
| `ValueObjectValidator<T>` | interface | `validate(value: T): boolean` — implement to add invariant checking
|
|
24
|
-
| `EntityId<T>` | class | Enhanced entity identifier extending the contracts `EntityId`; adds UUID/integer/text validation, `create()`, `fromString()`, `fromNumber()`
|
|
25
|
-
| `EntityIdFactory` | class | **Deprecated** — will be removed in v1.0.0; use `EntityId.create()` directly
|
|
26
|
-
| `BrandedId<Tag>` | type | Compile-time branded `EntityId<string>` — prevents mixing IDs across aggregate types
|
|
27
|
-
| `createBrandedId<Tag>(id)` | function | Casts an `EntityId` to `BrandedId<Tag>`
|
|
28
|
-
| `newBrandedId<Tag>()` | function | Creates a new UUID-based `BrandedId<Tag>`
|
|
29
|
-
| `brandedIdFromUUID<Tag>(uuid)` | function | Creates a `BrandedId<Tag>` from an existing UUID string
|
|
30
|
-
| `brandedIdFromText<Tag>(text)` | function | Creates a `BrandedId<Tag>` from a text string
|
|
31
|
-
| `IEntityId<T>` | interface | Re-exported from `@vytches/ddd-contracts`
|
|
32
|
-
| `IEntityIdFactory` | interface | Re-exported from `@vytches/ddd-contracts`
|
|
33
|
-
| `IdType` | type | Re-exported from `@vytches/ddd-contracts` — `'uuid' \| 'integer' \| 'text'`
|
|
20
|
+
| Export | Kind | Description |
|
|
21
|
+
| ------------------------------ | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
22
|
+
| `BaseValueObject<T>` | class | Abstract base for value objects; deep-freezes internal value; implements `equals()` via structural comparison, with an opt-in [`getIdentityComponents()`](#partial-identity-equality-with-getidentitycomponents) hook for partial-identity comparison |
|
|
23
|
+
| `ValueObjectValidator<T>` | interface | `validate(value: T): boolean` — implement to add invariant checking |
|
|
24
|
+
| `EntityId<T>` | class | Enhanced entity identifier extending the contracts `EntityId`; adds UUID/integer/text validation, `create()`, `fromString()`, `fromNumber()` |
|
|
25
|
+
| `EntityIdFactory` | class | **Deprecated** — will be removed in v1.0.0; use `EntityId.create()` directly |
|
|
26
|
+
| `BrandedId<Tag>` | type | Compile-time branded `EntityId<string>` — prevents mixing IDs across aggregate types |
|
|
27
|
+
| `createBrandedId<Tag>(id)` | function | Casts an `EntityId` to `BrandedId<Tag>` |
|
|
28
|
+
| `newBrandedId<Tag>()` | function | Creates a new UUID-based `BrandedId<Tag>` |
|
|
29
|
+
| `brandedIdFromUUID<Tag>(uuid)` | function | Creates a `BrandedId<Tag>` from an existing UUID string |
|
|
30
|
+
| `brandedIdFromText<Tag>(text)` | function | Creates a `BrandedId<Tag>` from a text string |
|
|
31
|
+
| `IEntityId<T>` | interface | Re-exported from `@vytches/ddd-contracts` |
|
|
32
|
+
| `IEntityIdFactory` | interface | Re-exported from `@vytches/ddd-contracts` |
|
|
33
|
+
| `IdType` | type | Re-exported from `@vytches/ddd-contracts` — `'uuid' \| 'integer' \| 'text'` |
|
|
34
34
|
|
|
35
35
|
## Note on bundled domain examples
|
|
36
36
|
|
|
@@ -121,6 +121,212 @@ shipOrder(orderId); // OK
|
|
|
121
121
|
shipOrder(customerId); // TypeScript compile error!
|
|
122
122
|
```
|
|
123
123
|
|
|
124
|
+
## Partial-identity equality with `getIdentityComponents()`
|
|
125
|
+
|
|
126
|
+
_Since VF-036 — additive minor, see [`CHANGELOG.md`](./CHANGELOG.md)._
|
|
127
|
+
|
|
128
|
+
By default, `equals()` compares the **entire** stored value: `===` for
|
|
129
|
+
primitives, `LibUtils.deepEqual` for objects. Override the protected
|
|
130
|
+
`getIdentityComponents()` hook when only a subset of a value object's state
|
|
131
|
+
should participate in equality — for example, a `Money` value object that
|
|
132
|
+
carries a display-formatting flag that must not affect equality, or a value
|
|
133
|
+
object that should compare a derived/normalized projection of its state rather
|
|
134
|
+
than the raw stored shape.
|
|
135
|
+
|
|
136
|
+
```ts compile-check
|
|
137
|
+
import { BaseValueObject } from '@vytches/ddd-value-objects';
|
|
138
|
+
|
|
139
|
+
interface MoneyProps {
|
|
140
|
+
amount: number;
|
|
141
|
+
currency: string;
|
|
142
|
+
displayFormat: 'symbol' | 'code';
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
class Money extends BaseValueObject<MoneyProps> {
|
|
146
|
+
constructor(props: MoneyProps) {
|
|
147
|
+
super(props);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
validate(value: MoneyProps): boolean {
|
|
151
|
+
return (
|
|
152
|
+
typeof value === 'object' &&
|
|
153
|
+
value !== null &&
|
|
154
|
+
value.amount >= 0 &&
|
|
155
|
+
value.currency.length === 3
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// 'Money' is a type-scoped discriminator (see below); displayFormat is
|
|
160
|
+
// intentionally excluded from identity.
|
|
161
|
+
protected override getIdentityComponents(): readonly unknown[] {
|
|
162
|
+
return ['Money', this.value.amount, this.value.currency];
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const a = new Money({ amount: 10, currency: 'USD', displayFormat: 'symbol' });
|
|
167
|
+
const b = new Money({ amount: 10, currency: 'USD', displayFormat: 'code' });
|
|
168
|
+
|
|
169
|
+
a.equals(b); // true — displayFormat is not an identity component
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### When to reach for this instead of full-value equality
|
|
173
|
+
|
|
174
|
+
Use `getIdentityComponents()` when a value object's raw stored value carries
|
|
175
|
+
fields that are _incidental_ to identity — audit metadata, cache keys,
|
|
176
|
+
presentation flags — while the _value_ the domain actually cares about is
|
|
177
|
+
narrower. If every field of `value` matters for equality, leave the hook
|
|
178
|
+
unimplemented; the default `undefined` return keeps the unmodified raw
|
|
179
|
+
comparison.
|
|
180
|
+
|
|
181
|
+
### The asymmetric fallback — and why it breaks transitivity in mixed populations
|
|
182
|
+
|
|
183
|
+
`equals()` calls `getIdentityComponents()` on **both** sides being compared.
|
|
184
|
+
Component comparison only runs if **both** sides return a defined array; if
|
|
185
|
+
**either** side returns `undefined`, `equals()` falls back to the unchanged raw
|
|
186
|
+
comparison. This fallback is symmetric — `a.equals(b)` and `b.equals(a)` always
|
|
187
|
+
agree, since both directions see the same two results — but it is deliberately
|
|
188
|
+
**not transitive** once a raw-comparison instance and a component-override
|
|
189
|
+
instance coexist in the same population:
|
|
190
|
+
|
|
191
|
+
- `A` — no override, raw comparison.
|
|
192
|
+
- `B` — component override, whose raw `value` happens to equal `A`'s.
|
|
193
|
+
- `C` — component override, whose components match `B`'s.
|
|
194
|
+
|
|
195
|
+
`A.equals(B)` can be `true` (raw fallback matches), `B.equals(C)` can be `true`
|
|
196
|
+
(components match), yet `A.equals(C)` can be `false` (raw fallback, different
|
|
197
|
+
`value`). This is an accepted, test-pinned limitation — it is not "fixed" with
|
|
198
|
+
`instanceof`/type gating.
|
|
199
|
+
|
|
200
|
+
**Collection-level consequence:** code such as `list.some(x => x.equals(y))` or
|
|
201
|
+
de-duplication by `.equals()` implicitly assumes equality is an equivalence
|
|
202
|
+
relation — reflexive, symmetric, and **transitive**. Mixing component-identity
|
|
203
|
+
subclasses with raw-comparison subclasses of the same base type inside one
|
|
204
|
+
collection breaks that assumption silently. Migrate a whole class hierarchy to
|
|
205
|
+
`getIdentityComponents()` together, or not at all — see the root
|
|
206
|
+
[`MIGRATION.md`](../../MIGRATION.md) for the atomic-codemod guidance.
|
|
207
|
+
|
|
208
|
+
### The empty-array footgun
|
|
209
|
+
|
|
210
|
+
Returning `[]` is **defined-and-empty**, not "opt out." Two value objects that
|
|
211
|
+
both return `[]` are equal to each other regardless of what their `value` holds
|
|
212
|
+
(same length — zero — so the element-wise comparison is vacuously true for
|
|
213
|
+
both). To opt out of component comparison, return `undefined` (the base
|
|
214
|
+
default); never return `[]` to mean "not applicable."
|
|
215
|
+
|
|
216
|
+
### The fixed-arity rule
|
|
217
|
+
|
|
218
|
+
A class must **always return the same number of components**, in the same order.
|
|
219
|
+
Comparison starts with a length check, so a conditional push —
|
|
220
|
+
`if (this.scope) parts.push(this.scope)` — makes two instances of the same class
|
|
221
|
+
unequal purely because one had an optional field set. That reads as a data
|
|
222
|
+
difference when it is actually an arity difference. Push a stable placeholder
|
|
223
|
+
instead:
|
|
224
|
+
|
|
225
|
+
```ts compile-check
|
|
226
|
+
import { BaseValueObject } from '@vytches/ddd-value-objects';
|
|
227
|
+
|
|
228
|
+
interface GrantProps {
|
|
229
|
+
tenant: string;
|
|
230
|
+
scope?: string;
|
|
231
|
+
key: string;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
class GrantRef extends BaseValueObject<GrantProps> {
|
|
235
|
+
validate(value: GrantProps): boolean {
|
|
236
|
+
return typeof value === 'object' && value !== null;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
protected override getIdentityComponents(): readonly unknown[] {
|
|
240
|
+
// Always three slots, whatever is populated. Never push conditionally.
|
|
241
|
+
return [this.value.tenant, this.value.scope ?? null, this.value.key];
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
The same applies across a hierarchy: if a subclass adds a component, it has
|
|
247
|
+
changed the arity of every comparison against its parent — a design decision to
|
|
248
|
+
make deliberately rather than discover.
|
|
249
|
+
|
|
250
|
+
### The "sometimes undefined" downgrade trap
|
|
251
|
+
|
|
252
|
+
Only a **literal `undefined`/omitted return from the hook itself** triggers the
|
|
253
|
+
raw-comparison fallback — a component _value_ inside a returned array being
|
|
254
|
+
`undefined` (e.g. an unset optional field) does not; the array is still
|
|
255
|
+
"defined" and comparison proceeds element-wise. The trap is the reverse case: a
|
|
256
|
+
_conditional_ override — "return components once some field is set, otherwise
|
|
257
|
+
return `undefined`" — will silently and intermittently downgrade the very same
|
|
258
|
+
class to raw comparison across different instances or different points in an
|
|
259
|
+
instance's lifecycle. Prefer an override that always returns an array once a
|
|
260
|
+
class opts in.
|
|
261
|
+
|
|
262
|
+
### Throw propagation — `equals()` is no longer total
|
|
263
|
+
|
|
264
|
+
A `getIdentityComponents()` override that throws propagates straight out of
|
|
265
|
+
`equals()`, uncaught. This is deliberate: `equals()` is a hot path, and this
|
|
266
|
+
library's internal logger is diagnostics-only and not meant to intercept
|
|
267
|
+
consumer logic (see the logging-removal note in the root `MIGRATION.md`), so
|
|
268
|
+
silently swallowing the throw into a wrong-but-quiet `false`/`true` would hide a
|
|
269
|
+
real bug in the override. Once a subclass overrides this hook with logic that
|
|
270
|
+
can throw, `equals()` is no longer a total function for that subclass.
|
|
271
|
+
|
|
272
|
+
### Components must come from frozen or readonly state
|
|
273
|
+
|
|
274
|
+
The base constructor deep-freezes the `value` passed to `super()` — but it does
|
|
275
|
+
**not** freeze any additional fields a subclass declares and assigns after
|
|
276
|
+
`super(value)`; those remain ordinary mutable class fields unless the subclass
|
|
277
|
+
itself keeps them readonly/frozen. Derive components only from state that cannot
|
|
278
|
+
change after construction (the frozen `value`, or subclass fields the subclass
|
|
279
|
+
itself keeps immutable). Deriving a component from mutable state breaks the
|
|
280
|
+
invariant that a value object's equality is stable for its lifetime.
|
|
281
|
+
|
|
282
|
+
### Type-scoped equality: the sanctioned idiom
|
|
283
|
+
|
|
284
|
+
`getIdentityComponents()` performs no type or `instanceof` check — two different
|
|
285
|
+
subclasses returning matching components are still considered equal, exactly as
|
|
286
|
+
cross-subclass raw comparison behaves today. If equality should be scoped to a
|
|
287
|
+
specific type, add a **string-literal discriminator** as the first component (as
|
|
288
|
+
in the `Money` example above):
|
|
289
|
+
|
|
290
|
+
```ts compile-check
|
|
291
|
+
import { BaseValueObject } from '@vytches/ddd-value-objects';
|
|
292
|
+
|
|
293
|
+
class OrderNumber extends BaseValueObject<string> {
|
|
294
|
+
validate(value: unknown): boolean {
|
|
295
|
+
return typeof value === 'string' && value.length > 0;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
protected override getIdentityComponents(): readonly unknown[] {
|
|
299
|
+
return ['OrderNumber', this.value];
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
Do **not** use `this.constructor.name` (unsafe under minification, which can
|
|
305
|
+
rename classes inconsistently) and do **not** use the class object itself
|
|
306
|
+
(unsafe across duplicate package copies — the same hazard `instanceof` has).
|
|
307
|
+
|
|
308
|
+
### `toString()`/`toJSON()` stay value-based
|
|
309
|
+
|
|
310
|
+
Overriding `getIdentityComponents()` does not change `toString()` or `toJSON()`
|
|
311
|
+
— both continue to serialize the raw `value`, unfiltered. If your override
|
|
312
|
+
narrows equality to a subset of fields, serialization may now surface
|
|
313
|
+
information that equality ignores (or the reverse). Keeping any resulting
|
|
314
|
+
equals/serialization desync coherent is the consumer's responsibility.
|
|
315
|
+
|
|
316
|
+
> **A permanent note: `getEqualityComponents` was never real.** An early (2025)
|
|
317
|
+
> documentation draft described a similarly-named `getEqualityComponents()`
|
|
318
|
+
> hook. It was **never implemented** in any released version of this library —
|
|
319
|
+
> every `equals()` call has always used the raw `value`-based comparison
|
|
320
|
+
> described above. The name was removed from docs in a later accuracy pass, but
|
|
321
|
+
> some consumers had already written overrides against it, which were (and
|
|
322
|
+
> always had been) dead code. `getIdentityComponents()` is the real, supported
|
|
323
|
+
> hook, under a deliberately different name. `getEqualityComponents` will
|
|
324
|
+
> **not** be added as an alias, shim, or runtime-detected fallback — doing so
|
|
325
|
+
> would silently activate every dormant consumer override on upgrade, which is
|
|
326
|
+
> exactly the behavioral break this design avoids. If you are holding a dead
|
|
327
|
+
> `getEqualityComponents` override, see the root
|
|
328
|
+
> [`MIGRATION.md`](../../MIGRATION.md).
|
|
329
|
+
|
|
124
330
|
## Package boundaries
|
|
125
331
|
|
|
126
332
|
`@vytches/ddd-value-objects` depends on:
|
|
@@ -1,12 +1,195 @@
|
|
|
1
1
|
export interface ValueObjectValidator<T> {
|
|
2
2
|
validate(value: T): boolean;
|
|
3
3
|
}
|
|
4
|
+
/**
|
|
5
|
+
* VF-036 (D3): module-private brand used to recognize "this is a
|
|
6
|
+
* `BaseValueObject`" without `instanceof`. `instanceof` breaks silently
|
|
7
|
+
* across duplicate package copies (e.g. a failed `pnpm dedupe`, or two
|
|
8
|
+
* versions of this library resolved by a bundler/monorepo tool) because
|
|
9
|
+
* each copy has its own distinct class reference. `Symbol.for` reads from
|
|
10
|
+
* the *global* symbol registry, so every copy of this module — regardless
|
|
11
|
+
* of which physical `node_modules` install produced it — resolves the same
|
|
12
|
+
* symbol and agrees on the brand. The symbol is intentionally not exported:
|
|
13
|
+
* consumers never need to reference it directly, only the boolean brand
|
|
14
|
+
* check performed internally by `componentEquals`.
|
|
15
|
+
*/
|
|
16
|
+
declare const VALUE_OBJECT_BRAND: unique symbol;
|
|
4
17
|
export declare abstract class BaseValueObject<T> implements ValueObjectValidator<T> {
|
|
5
18
|
protected readonly value: T;
|
|
19
|
+
/**
|
|
20
|
+
* VF-036 (D3): marks every instance with the module-private brand symbol
|
|
21
|
+
* so `componentEquals` can recognize nested value objects without
|
|
22
|
+
* `instanceof`. Declared as an instance property (not on the prototype)
|
|
23
|
+
* so `in` works uniformly regardless of how a subclass is constructed.
|
|
24
|
+
*/
|
|
25
|
+
protected readonly [VALUE_OBJECT_BRAND] = true;
|
|
26
|
+
/**
|
|
27
|
+
* VF-023 (D-1, AC1, BREAKING): validates `value` via the polymorphic
|
|
28
|
+
* `validate()` hook and throws synchronously on failure. This replaces
|
|
29
|
+
* the previous convention where every subclass repeated
|
|
30
|
+
* `if (!this.validate(value)) throw ...` in its own constructor after
|
|
31
|
+
* `super(value)` — that boilerplate is now centralized here and enforced
|
|
32
|
+
* for ALL subclasses, including any that previously forgot it.
|
|
33
|
+
*
|
|
34
|
+
* MIGRATION NOTE (undefined-during-super() trap): `validate()` is called
|
|
35
|
+
* via prototype dispatch from *this* base constructor, i.e. before the
|
|
36
|
+
* subclass constructor body (and therefore any subclass field
|
|
37
|
+
* initializers declared after `super(value)`) has run. A `validate()`
|
|
38
|
+
* override that reads `this.someOtherField` set up elsewhere in the
|
|
39
|
+
* subclass constructor will see `undefined` for that field. Overrides
|
|
40
|
+
* must only depend on the `value` parameter passed in, not on
|
|
41
|
+
* subclass instance state. Existing subclasses that already call
|
|
42
|
+
* `this.validate(value)` redundantly after `super(value)` are
|
|
43
|
+
* unaffected — the base class throws first, before that redundant call
|
|
44
|
+
* is ever reached.
|
|
45
|
+
*/
|
|
6
46
|
constructor(value: T);
|
|
47
|
+
/**
|
|
48
|
+
* VF-036 (D1/D4): opt-in hook letting a subclass declare which fields
|
|
49
|
+
* make up its *identity*, so `equals()` compares those fields instead of
|
|
50
|
+
* the raw `value` this class stores. Ships under the name
|
|
51
|
+
* `getIdentityComponents` — an earlier, never-implemented name for a
|
|
52
|
+
* similar idea (`getEqualityComponents`) circulated in docs and MUST NOT
|
|
53
|
+
* be revived as an alias, shim, or runtime-detected fallback; it never
|
|
54
|
+
* existed in source and never will.
|
|
55
|
+
*
|
|
56
|
+
* ## When component identity beats full-value equality
|
|
57
|
+
* The default raw comparison (`this.value === valueObject.value`, then
|
|
58
|
+
* `LibUtils.deepEqual` for objects) treats the *entire* stored value as
|
|
59
|
+
* significant. Override this hook when only a subset of fields should
|
|
60
|
+
* participate — e.g. a `Money` VO whose `value` also carries a
|
|
61
|
+
* display-formatting flag that must not affect equality, or a VO that
|
|
62
|
+
* wants to compare by a derived/normalized projection of its state
|
|
63
|
+
* rather than the raw stored shape.
|
|
64
|
+
*
|
|
65
|
+
* ## The asymmetric fallback, and why it is not transitive
|
|
66
|
+
* The dispatch rule in `equals()` is: if BOTH sides return a defined
|
|
67
|
+
* array, compare components; if EITHER side returns `undefined`, run the
|
|
68
|
+
* completely unchanged raw comparison. This rule is symmetric (`a.equals(b)`
|
|
69
|
+
* and `b.equals(a)` agree, since both directions see the same two
|
|
70
|
+
* `getIdentityComponents()` results) but deliberately **not transitive**:
|
|
71
|
+
* given A (raw comparison, no override), B (component override, happens
|
|
72
|
+
* to have the same raw `value` as A), and C (component override, same
|
|
73
|
+
* components as B), `A.equals(B)` may be `true` (raw fallback matches),
|
|
74
|
+
* `B.equals(C)` may be `true` (components match), yet `A.equals(C)` can
|
|
75
|
+
* be `false` (raw fallback, different `value`). This is an accepted,
|
|
76
|
+
* documented, test-pinned limitation — do not "fix" it by adding
|
|
77
|
+
* type/instanceof gating; see D6. It matters most for collection code
|
|
78
|
+
* such as `list.some(x => x.equals(y))` or de-duplication by `.equals`:
|
|
79
|
+
* such code implicitly assumes an equivalence relation, and mixing
|
|
80
|
+
* component-identity subclasses with raw-comparison subclasses in the
|
|
81
|
+
* same collection breaks that assumption.
|
|
82
|
+
*
|
|
83
|
+
* ## The empty-array footgun
|
|
84
|
+
* Returning `[]` is **defined-and-empty**, not "opt out". An empty array
|
|
85
|
+
* means "this VO has no identity-relevant fields", so it is equal to
|
|
86
|
+
* every other VO whose `getIdentityComponents()` also returns `[]`
|
|
87
|
+
* (same length: zero, vacuously equal element-wise) — regardless of what
|
|
88
|
+
* their respective `value`s hold. To opt out, return `undefined` (the
|
|
89
|
+
* base default), not `[]`.
|
|
90
|
+
*
|
|
91
|
+
* ## The undefined-because-field-not-initialized trap
|
|
92
|
+
* `undefined` silently downgrades to the raw comparison. If an override
|
|
93
|
+
* reads a subclass field that has not been initialized yet (see the
|
|
94
|
+
* undefined-during-`super()` trap documented on the constructor) and
|
|
95
|
+
* that read produces `undefined` as a *component value* (not as the
|
|
96
|
+
* hook's own return value), the surrounding array is still "defined" —
|
|
97
|
+
* only a *literal* `undefined`/omitted return from the override itself
|
|
98
|
+
* triggers the fallback. Conversely, a conditional override that
|
|
99
|
+
* sometimes returns `undefined` (e.g. "only compare by components once
|
|
100
|
+
* some field is set") will silently and intermittently fall back to raw
|
|
101
|
+
* comparison for the same class — prefer always returning an array once
|
|
102
|
+
* a class opts in.
|
|
103
|
+
*
|
|
104
|
+
* ## Throw propagation
|
|
105
|
+
* A throwing override propagates out of `equals()` uncaught (D5).
|
|
106
|
+
* `equals()` is a hot path and this library's logger is
|
|
107
|
+
* diagnostics-only, so there is no try/catch here — catching would
|
|
108
|
+
* silently convert a loud consumer bug (e.g. a component accessor that
|
|
109
|
+
* dereferences something absent) into a wrong-but-quiet `false`/`true`
|
|
110
|
+
* answer. `equals()` stops being a total function for classes that
|
|
111
|
+
* override this hook with logic that can throw.
|
|
112
|
+
*
|
|
113
|
+
* ## Fixed arity
|
|
114
|
+
* A class must always return the same number of components on every
|
|
115
|
+
* call (do not vary the array length based on instance state). Two
|
|
116
|
+
* defined arrays of different lengths are treated as unequal without
|
|
117
|
+
* inspecting elements, so a class whose arity drifts will get
|
|
118
|
+
* inconsistent, order-of-comparison-dependent results.
|
|
119
|
+
*
|
|
120
|
+
* ## Components must come from frozen/readonly state
|
|
121
|
+
* The constructor deep-freezes the `value` passed to `super()`, but it
|
|
122
|
+
* does **not** freeze any additional fields a subclass declares and
|
|
123
|
+
* assigns after `super(value)` — those are ordinary mutable class
|
|
124
|
+
* fields unless the subclass itself makes them readonly/frozen.
|
|
125
|
+
* Components must be derived only from state that cannot change after
|
|
126
|
+
* construction (the frozen `value`, or subclass fields the subclass
|
|
127
|
+
* itself keeps immutable); deriving a component from mutable state
|
|
128
|
+
* breaks the invariant that a VO's equality is stable for its lifetime.
|
|
129
|
+
*
|
|
130
|
+
* ## Type-scoped equality: the sanctioned idiom
|
|
131
|
+
* This hook performs no type or `instanceof` check (D6) — cross-subclass
|
|
132
|
+
* equality behaves exactly as it does today, and two different
|
|
133
|
+
* subclasses returning matching components are considered equal. For
|
|
134
|
+
* consumers who want type-scoped equality, the sanctioned opt-in is a
|
|
135
|
+
* string literal as the first component (e.g. `['Money', this.amount,
|
|
136
|
+
* this.currency]`), NOT `this.constructor.name` (unsafe under
|
|
137
|
+
* minification, which can rename classes inconsistently) and NOT the
|
|
138
|
+
* class object itself (unsafe across duplicate package copies, the same
|
|
139
|
+
* hazard `instanceof` has — see the brand-symbol note above).
|
|
140
|
+
*
|
|
141
|
+
* ## toString()/toJSON() stay value-based
|
|
142
|
+
* Overriding this hook does not change `toString()` or `toJSON()` — both
|
|
143
|
+
* continue to reflect the raw `value`. If an override narrows equality
|
|
144
|
+
* to a subset of fields, `toString()`/`toJSON()` may now serialize
|
|
145
|
+
* information that equality ignores (or vice versa); keeping any
|
|
146
|
+
* resulting equals/hash/serialization desync coherent is the
|
|
147
|
+
* consumer's responsibility.
|
|
148
|
+
*
|
|
149
|
+
* @returns `undefined` (the base default) to opt out and keep the raw
|
|
150
|
+
* `value`-based comparison; a readonly array of identity-relevant
|
|
151
|
+
* values to opt in to component-wise comparison.
|
|
152
|
+
*/
|
|
153
|
+
protected getIdentityComponents(): readonly unknown[] | undefined;
|
|
7
154
|
/**
|
|
8
155
|
* Compares this value object with another for equality.
|
|
9
|
-
*
|
|
156
|
+
*
|
|
157
|
+
* VF-036 (D1/D2): first consults `getIdentityComponents()` on both
|
|
158
|
+
* sides. If BOTH return a defined array, equality is decided by that
|
|
159
|
+
* array alone: same length, then element-wise via `componentEquals`
|
|
160
|
+
* (which itself dispatches to nested `.equals()` for branded value
|
|
161
|
+
* objects, `LibUtils.deepEqual` otherwise). If EITHER side returns
|
|
162
|
+
* `undefined`, this method falls through to the exact, unmodified raw
|
|
163
|
+
* comparison that existed before VF-036 — see the block below and the
|
|
164
|
+
* `getIdentityComponents` doc comment for the full rationale, including
|
|
165
|
+
* why this fallback is symmetric but not transitive.
|
|
166
|
+
*
|
|
167
|
+
* The hook is consulted for primitive `T` as well as object `T`; nothing
|
|
168
|
+
* about this dispatch depends on `typeof this.value`.
|
|
169
|
+
*
|
|
170
|
+
* HARD CONSTRAINT: there is intentionally no unconditional
|
|
171
|
+
* `this === valueObject` shortcut at the top of this method — the only
|
|
172
|
+
* reflexivity shortcut this class takes lives inside the raw-comparison
|
|
173
|
+
* branch below (`this.value === valueObject.value`, which briefly
|
|
174
|
+
* predates any `getIdentityComponents` check on that branch only because
|
|
175
|
+
* neither side opted in). When neither side returns components, the
|
|
176
|
+
* executed code path is bit-for-bit identical to the pre-VF-036
|
|
177
|
+
* behavior, which is what keeps this a non-breaking, additive minor
|
|
178
|
+
* release rather than a breaking major.
|
|
179
|
+
*
|
|
180
|
+
* Uses deep structural comparison for object values, === for primitives,
|
|
181
|
+
* in the fallback branch.
|
|
182
|
+
*
|
|
183
|
+
* VF-023 (D-3, AC2/AC3, BREAKING): deep comparison now uses
|
|
184
|
+
* `LibUtils.deepEqual` instead of `JSON.stringify` equality. This fixes
|
|
185
|
+
* false negatives/positives inherent to JSON-based comparison — key
|
|
186
|
+
* order sensitivity is gone (already true before, since JSON.stringify
|
|
187
|
+
* of plain objects is insertion-order, not truly key-order-independent
|
|
188
|
+
* in all engines), values like `undefined`, `Date`, `Map`, `Set`, and
|
|
189
|
+
* `NaN` inside nested structures now compare by actual value semantics
|
|
190
|
+
* instead of silently dropping (`undefined`) or losing type
|
|
191
|
+
* (`Date`/`Map`/`Set` stringify to structurally different, sometimes
|
|
192
|
+
* colliding, JSON shapes).
|
|
10
193
|
* @param valueObject - The value object to compare with
|
|
11
194
|
* @returns True if they are equal, false otherwise
|
|
12
195
|
*/
|
|
@@ -32,4 +215,20 @@ export declare abstract class BaseValueObject<T> implements ValueObjectValidator
|
|
|
32
215
|
* @returns True if valid, false otherwise
|
|
33
216
|
*/
|
|
34
217
|
abstract validate(value: unknown): boolean;
|
|
218
|
+
/**
|
|
219
|
+
* VF-023 (D-1 regression fix): produces the message used when the
|
|
220
|
+
* constructor's synchronous `validate()` call fails. Because the base
|
|
221
|
+
* constructor now throws BEFORE the subclass constructor body runs (see
|
|
222
|
+
* the class-level doc comment), a subclass can no longer rely on its own
|
|
223
|
+
* post-`super(value)` `throw new Error('...')` — that code is
|
|
224
|
+
* unreachable, since the base class has already thrown by the time
|
|
225
|
+
* control would return to the subclass. Override this hook instead to
|
|
226
|
+
* provide a validation-error message specific to the subclass. Only
|
|
227
|
+
* `value` (the parameter, not subclass instance state — see the
|
|
228
|
+
* undefined-during-super() trap note above) is safe to use here.
|
|
229
|
+
* @param value The value that failed validation
|
|
230
|
+
* @returns The message to use for the thrown `Error`
|
|
231
|
+
*/
|
|
232
|
+
protected getInvalidValueMessage(value: T): string;
|
|
35
233
|
}
|
|
234
|
+
export {};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { EntityId as BaseEntityId,
|
|
1
|
+
import { EntityId as BaseEntityId, IdType } from '@vytches/ddd-contracts';
|
|
2
2
|
import { Result } from '@vytches/ddd-utils';
|
|
3
3
|
export declare class EntityId<T = string> extends BaseEntityId<T> {
|
|
4
4
|
constructor(value: T, type: IdType);
|
|
@@ -55,52 +55,3 @@ export declare class EntityId<T = string> extends BaseEntityId<T> {
|
|
|
55
55
|
*/
|
|
56
56
|
static tryFromText(value: string): Result<EntityId<string>, Error>;
|
|
57
57
|
}
|
|
58
|
-
/**
|
|
59
|
-
* @deprecated Use EntityId static methods directly instead. This class will be removed in the next major version.
|
|
60
|
-
* @example
|
|
61
|
-
* ```typescript
|
|
62
|
-
* // Before (deprecated):
|
|
63
|
-
* const id = EntityIdFactory.createWithRandomUUID();
|
|
64
|
-
*
|
|
65
|
-
* // After (recommended):
|
|
66
|
-
* const id = EntityId.create();
|
|
67
|
-
* ```
|
|
68
|
-
*/
|
|
69
|
-
export declare class EntityIdFactory implements IEntityIdFactory {
|
|
70
|
-
/**
|
|
71
|
-
* @deprecated Use EntityId.create() instead. Will be removed in v1.0.0.
|
|
72
|
-
*/
|
|
73
|
-
static createWithRandomUUID(): EntityId<string>;
|
|
74
|
-
/**
|
|
75
|
-
* @deprecated Use EntityId.fromUUID() instead. Will be removed in v1.0.0.
|
|
76
|
-
* @throws {MissingValueError} if value is empty
|
|
77
|
-
* @throws {InvalidParameterError} if value is empty
|
|
78
|
-
*/
|
|
79
|
-
static fromUUID(value: string): EntityId<string>;
|
|
80
|
-
/**
|
|
81
|
-
* @deprecated Use EntityId.fromInteger() instead. Will be removed in v1.0.0.
|
|
82
|
-
* @throws {InvalidParameterError} if value is empty
|
|
83
|
-
*/
|
|
84
|
-
static fromInteger(value: number): EntityId<string>;
|
|
85
|
-
/**
|
|
86
|
-
* @deprecated Use EntityId.fromBigInt() instead. Will be removed in v1.0.0.
|
|
87
|
-
* @throws {InvalidParameterError} if value is empty
|
|
88
|
-
*/
|
|
89
|
-
static fromBigInt(value: string | bigint): EntityId<string>;
|
|
90
|
-
/**
|
|
91
|
-
* @deprecated Use EntityId.fromText() instead. Will be removed in v1.0.0.
|
|
92
|
-
* @throws {MissingValueError} if value is empty
|
|
93
|
-
* @throws {InvalidParameterError} if value is empty
|
|
94
|
-
*/
|
|
95
|
-
static fromText(value: string): EntityId<string>;
|
|
96
|
-
/** @deprecated Use EntityId.create() instead */
|
|
97
|
-
createWithRandomUUID(): EntityId<string>;
|
|
98
|
-
/** @deprecated Use EntityId.fromUUID() instead */
|
|
99
|
-
fromUUID(value: string): EntityId<string>;
|
|
100
|
-
/** @deprecated Use EntityId.fromInteger() instead */
|
|
101
|
-
fromInteger(value: number): EntityId<string>;
|
|
102
|
-
/** @deprecated Use EntityId.fromBigInt() instead */
|
|
103
|
-
fromBigInt(value: string | bigint): EntityId<string>;
|
|
104
|
-
/** @deprecated Use EntityId.fromText() instead */
|
|
105
|
-
fromText(value: string): EntityId<string>;
|
|
106
|
-
}
|
package/dist/index.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("@vytches/ddd-utils"),d=require("@vytches/ddd-contracts"),r=require("@vytches/ddd-domain-primitives");var u;const o=Symbol.for("@vytches/ddd.valueObject");function f(n,t){return typeof n=="object"&&n!==null&&o in n&&typeof t=="object"&&t!==null&&o in t?n.equals(t):e.LibUtils.deepEqual(n,t,new WeakMap)}u=o;class c{constructor(t){if(this[u]=!0,this.value=t!==null&&typeof t=="object"?e.LibUtils.deepFreeze(t):t,!this.validate(this.value))throw new Error(this.getInvalidValueMessage(this.value))}getIdentityComponents(){}equals(t){if(!t)return!1;const a=this.getIdentityComponents(),l=t.getIdentityComponents();if(a!==void 0&&l!==void 0){if(a.length!==l.length)return!1;for(let s=0;s<a.length;s++)if(!f(a[s],l[s]))return!1;return!0}return this.value===t.value?!0:typeof this.value=="object"&&this.value!==null?e.LibUtils.deepEqual(this.value,t.value):!1}toString(){return String(this.value)}toJSON(){return this.value}getValue(){return this.value}getInvalidValueMessage(t){return`Invalid value for ${this.constructor.name}`}}class i extends d.EntityId{constructor(t,a){super(t,a)}validate(t){switch(this.getType()){case"uuid":return e.LibUtils.isValidUUID(t);case"integer":return e.LibUtils.isValidInteger(t);case"bigint":return e.LibUtils.isValidBigInt(t);case"text":return e.LibUtils.isValidTextId(t);default:throw new r.InvalidParameterError(`Unsupported IdType: ${this.getType()}`)}}static create(){return new i(e.LibUtils.getUUID(),"uuid")}static createWithRandomUUID(){return i.create()}static fromUUID(t){if(!e.LibUtils.hasValue(t))throw r.MissingValueError.withValue("entity identifier");if(!e.LibUtils.isValidUUID(t))throw r.InvalidParameterError.withParameter("entity identifier");return new i(t,"uuid")}static fromInteger(t){if(!e.LibUtils.isValidInteger(t))throw r.InvalidParameterError.withParameter("entity identifier must be a non-negative integer");return new i(t.toString(),"integer")}static fromBigInt(t){const a=e.LibUtils.normalizeIdToString(t);if(!e.LibUtils.isValidBigInt(a))throw r.InvalidParameterError.withParameter("entity identifier must be a valid bigint");return new i(a,"bigint")}static fromText(t){if(!e.LibUtils.hasValue(t))throw r.MissingValueError.withValue("entity identifier");if(!e.LibUtils.isValidTextId(t))throw r.InvalidParameterError.withParameter("entity identifier contains invalid characters");return new i(t,"text")}static tryFromUUID(t){return e.LibUtils.hasValue(t)?e.LibUtils.isValidUUID(t)?e.Result.ok(new i(t,"uuid")):e.Result.fail(r.InvalidParameterError.withParameter("entity identifier")):e.Result.fail(r.MissingValueError.withValue("entity identifier"))}static tryFromInteger(t){return e.LibUtils.isValidInteger(t)?e.Result.ok(new i(t.toString(),"integer")):e.Result.fail(r.InvalidParameterError.withParameter("entity identifier must be a non-negative integer"))}static tryFromBigInt(t){const a=e.LibUtils.normalizeIdToString(t);return e.LibUtils.isValidBigInt(a)?e.Result.ok(new i(a,"bigint")):e.Result.fail(r.InvalidParameterError.withParameter("entity identifier must be a valid bigint"))}static tryFromText(t){return e.LibUtils.hasValue(t)?e.LibUtils.isValidTextId(t)?e.Result.ok(new i(t,"text")):e.Result.fail(r.InvalidParameterError.withParameter("entity identifier contains invalid characters")):e.Result.fail(r.MissingValueError.withValue("entity identifier"))}}function g(n){return n}function h(){return i.create()}function m(n){return i.fromUUID(n)}function U(n){return i.fromText(n)}exports.BaseValueObject=c;exports.EntityId=i;exports.brandedIdFromText=U;exports.brandedIdFromUUID=m;exports.createBrandedId=g;exports.newBrandedId=h;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { BaseValueObject, type ValueObjectValidator } from './base-value-object';
|
|
2
|
-
export { EntityId
|
|
2
|
+
export { EntityId } from './id.value-object';
|
|
3
3
|
export type { BrandedId } from './branded-id';
|
|
4
4
|
export { createBrandedId, newBrandedId, brandedIdFromUUID, brandedIdFromText } from './branded-id';
|
|
5
5
|
export type { IEntityId, IEntityIdFactory, IdType } from '@vytches/ddd-contracts';
|
package/dist/index.js
CHANGED
|
@@ -1,18 +1,202 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
|
|
1
|
+
import { LibUtils as e, Result as a } from "@vytches/ddd-utils";
|
|
2
|
+
import { EntityId as h } from "@vytches/ddd-contracts";
|
|
3
|
+
import { InvalidParameterError as s, MissingValueError as u } from "@vytches/ddd-domain-primitives";
|
|
4
|
+
var f;
|
|
5
|
+
const d = Symbol.for("@vytches/ddd.valueObject");
|
|
6
|
+
function c(r, t) {
|
|
7
|
+
return typeof r == "object" && r !== null && d in r && typeof t == "object" && t !== null && d in t ? r.equals(
|
|
8
|
+
t
|
|
9
|
+
) : e.deepEqual(r, t, /* @__PURE__ */ new WeakMap());
|
|
10
|
+
}
|
|
11
|
+
f = d;
|
|
12
|
+
class I {
|
|
13
|
+
/**
|
|
14
|
+
* VF-023 (D-1, AC1, BREAKING): validates `value` via the polymorphic
|
|
15
|
+
* `validate()` hook and throws synchronously on failure. This replaces
|
|
16
|
+
* the previous convention where every subclass repeated
|
|
17
|
+
* `if (!this.validate(value)) throw ...` in its own constructor after
|
|
18
|
+
* `super(value)` — that boilerplate is now centralized here and enforced
|
|
19
|
+
* for ALL subclasses, including any that previously forgot it.
|
|
20
|
+
*
|
|
21
|
+
* MIGRATION NOTE (undefined-during-super() trap): `validate()` is called
|
|
22
|
+
* via prototype dispatch from *this* base constructor, i.e. before the
|
|
23
|
+
* subclass constructor body (and therefore any subclass field
|
|
24
|
+
* initializers declared after `super(value)`) has run. A `validate()`
|
|
25
|
+
* override that reads `this.someOtherField` set up elsewhere in the
|
|
26
|
+
* subclass constructor will see `undefined` for that field. Overrides
|
|
27
|
+
* must only depend on the `value` parameter passed in, not on
|
|
28
|
+
* subclass instance state. Existing subclasses that already call
|
|
29
|
+
* `this.validate(value)` redundantly after `super(value)` are
|
|
30
|
+
* unaffected — the base class throws first, before that redundant call
|
|
31
|
+
* is ever reached.
|
|
32
|
+
*/
|
|
5
33
|
constructor(t) {
|
|
6
|
-
t !== null && typeof t == "object" ?
|
|
34
|
+
if (this[f] = !0, this.value = t !== null && typeof t == "object" ? e.deepFreeze(t) : t, !this.validate(this.value))
|
|
35
|
+
throw new Error(this.getInvalidValueMessage(this.value));
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* VF-036 (D1/D4): opt-in hook letting a subclass declare which fields
|
|
39
|
+
* make up its *identity*, so `equals()` compares those fields instead of
|
|
40
|
+
* the raw `value` this class stores. Ships under the name
|
|
41
|
+
* `getIdentityComponents` — an earlier, never-implemented name for a
|
|
42
|
+
* similar idea (`getEqualityComponents`) circulated in docs and MUST NOT
|
|
43
|
+
* be revived as an alias, shim, or runtime-detected fallback; it never
|
|
44
|
+
* existed in source and never will.
|
|
45
|
+
*
|
|
46
|
+
* ## When component identity beats full-value equality
|
|
47
|
+
* The default raw comparison (`this.value === valueObject.value`, then
|
|
48
|
+
* `LibUtils.deepEqual` for objects) treats the *entire* stored value as
|
|
49
|
+
* significant. Override this hook when only a subset of fields should
|
|
50
|
+
* participate — e.g. a `Money` VO whose `value` also carries a
|
|
51
|
+
* display-formatting flag that must not affect equality, or a VO that
|
|
52
|
+
* wants to compare by a derived/normalized projection of its state
|
|
53
|
+
* rather than the raw stored shape.
|
|
54
|
+
*
|
|
55
|
+
* ## The asymmetric fallback, and why it is not transitive
|
|
56
|
+
* The dispatch rule in `equals()` is: if BOTH sides return a defined
|
|
57
|
+
* array, compare components; if EITHER side returns `undefined`, run the
|
|
58
|
+
* completely unchanged raw comparison. This rule is symmetric (`a.equals(b)`
|
|
59
|
+
* and `b.equals(a)` agree, since both directions see the same two
|
|
60
|
+
* `getIdentityComponents()` results) but deliberately **not transitive**:
|
|
61
|
+
* given A (raw comparison, no override), B (component override, happens
|
|
62
|
+
* to have the same raw `value` as A), and C (component override, same
|
|
63
|
+
* components as B), `A.equals(B)` may be `true` (raw fallback matches),
|
|
64
|
+
* `B.equals(C)` may be `true` (components match), yet `A.equals(C)` can
|
|
65
|
+
* be `false` (raw fallback, different `value`). This is an accepted,
|
|
66
|
+
* documented, test-pinned limitation — do not "fix" it by adding
|
|
67
|
+
* type/instanceof gating; see D6. It matters most for collection code
|
|
68
|
+
* such as `list.some(x => x.equals(y))` or de-duplication by `.equals`:
|
|
69
|
+
* such code implicitly assumes an equivalence relation, and mixing
|
|
70
|
+
* component-identity subclasses with raw-comparison subclasses in the
|
|
71
|
+
* same collection breaks that assumption.
|
|
72
|
+
*
|
|
73
|
+
* ## The empty-array footgun
|
|
74
|
+
* Returning `[]` is **defined-and-empty**, not "opt out". An empty array
|
|
75
|
+
* means "this VO has no identity-relevant fields", so it is equal to
|
|
76
|
+
* every other VO whose `getIdentityComponents()` also returns `[]`
|
|
77
|
+
* (same length: zero, vacuously equal element-wise) — regardless of what
|
|
78
|
+
* their respective `value`s hold. To opt out, return `undefined` (the
|
|
79
|
+
* base default), not `[]`.
|
|
80
|
+
*
|
|
81
|
+
* ## The undefined-because-field-not-initialized trap
|
|
82
|
+
* `undefined` silently downgrades to the raw comparison. If an override
|
|
83
|
+
* reads a subclass field that has not been initialized yet (see the
|
|
84
|
+
* undefined-during-`super()` trap documented on the constructor) and
|
|
85
|
+
* that read produces `undefined` as a *component value* (not as the
|
|
86
|
+
* hook's own return value), the surrounding array is still "defined" —
|
|
87
|
+
* only a *literal* `undefined`/omitted return from the override itself
|
|
88
|
+
* triggers the fallback. Conversely, a conditional override that
|
|
89
|
+
* sometimes returns `undefined` (e.g. "only compare by components once
|
|
90
|
+
* some field is set") will silently and intermittently fall back to raw
|
|
91
|
+
* comparison for the same class — prefer always returning an array once
|
|
92
|
+
* a class opts in.
|
|
93
|
+
*
|
|
94
|
+
* ## Throw propagation
|
|
95
|
+
* A throwing override propagates out of `equals()` uncaught (D5).
|
|
96
|
+
* `equals()` is a hot path and this library's logger is
|
|
97
|
+
* diagnostics-only, so there is no try/catch here — catching would
|
|
98
|
+
* silently convert a loud consumer bug (e.g. a component accessor that
|
|
99
|
+
* dereferences something absent) into a wrong-but-quiet `false`/`true`
|
|
100
|
+
* answer. `equals()` stops being a total function for classes that
|
|
101
|
+
* override this hook with logic that can throw.
|
|
102
|
+
*
|
|
103
|
+
* ## Fixed arity
|
|
104
|
+
* A class must always return the same number of components on every
|
|
105
|
+
* call (do not vary the array length based on instance state). Two
|
|
106
|
+
* defined arrays of different lengths are treated as unequal without
|
|
107
|
+
* inspecting elements, so a class whose arity drifts will get
|
|
108
|
+
* inconsistent, order-of-comparison-dependent results.
|
|
109
|
+
*
|
|
110
|
+
* ## Components must come from frozen/readonly state
|
|
111
|
+
* The constructor deep-freezes the `value` passed to `super()`, but it
|
|
112
|
+
* does **not** freeze any additional fields a subclass declares and
|
|
113
|
+
* assigns after `super(value)` — those are ordinary mutable class
|
|
114
|
+
* fields unless the subclass itself makes them readonly/frozen.
|
|
115
|
+
* Components must be derived only from state that cannot change after
|
|
116
|
+
* construction (the frozen `value`, or subclass fields the subclass
|
|
117
|
+
* itself keeps immutable); deriving a component from mutable state
|
|
118
|
+
* breaks the invariant that a VO's equality is stable for its lifetime.
|
|
119
|
+
*
|
|
120
|
+
* ## Type-scoped equality: the sanctioned idiom
|
|
121
|
+
* This hook performs no type or `instanceof` check (D6) — cross-subclass
|
|
122
|
+
* equality behaves exactly as it does today, and two different
|
|
123
|
+
* subclasses returning matching components are considered equal. For
|
|
124
|
+
* consumers who want type-scoped equality, the sanctioned opt-in is a
|
|
125
|
+
* string literal as the first component (e.g. `['Money', this.amount,
|
|
126
|
+
* this.currency]`), NOT `this.constructor.name` (unsafe under
|
|
127
|
+
* minification, which can rename classes inconsistently) and NOT the
|
|
128
|
+
* class object itself (unsafe across duplicate package copies, the same
|
|
129
|
+
* hazard `instanceof` has — see the brand-symbol note above).
|
|
130
|
+
*
|
|
131
|
+
* ## toString()/toJSON() stay value-based
|
|
132
|
+
* Overriding this hook does not change `toString()` or `toJSON()` — both
|
|
133
|
+
* continue to reflect the raw `value`. If an override narrows equality
|
|
134
|
+
* to a subset of fields, `toString()`/`toJSON()` may now serialize
|
|
135
|
+
* information that equality ignores (or vice versa); keeping any
|
|
136
|
+
* resulting equals/hash/serialization desync coherent is the
|
|
137
|
+
* consumer's responsibility.
|
|
138
|
+
*
|
|
139
|
+
* @returns `undefined` (the base default) to opt out and keep the raw
|
|
140
|
+
* `value`-based comparison; a readonly array of identity-relevant
|
|
141
|
+
* values to opt in to component-wise comparison.
|
|
142
|
+
*/
|
|
143
|
+
getIdentityComponents() {
|
|
7
144
|
}
|
|
8
145
|
/**
|
|
9
146
|
* Compares this value object with another for equality.
|
|
10
|
-
*
|
|
147
|
+
*
|
|
148
|
+
* VF-036 (D1/D2): first consults `getIdentityComponents()` on both
|
|
149
|
+
* sides. If BOTH return a defined array, equality is decided by that
|
|
150
|
+
* array alone: same length, then element-wise via `componentEquals`
|
|
151
|
+
* (which itself dispatches to nested `.equals()` for branded value
|
|
152
|
+
* objects, `LibUtils.deepEqual` otherwise). If EITHER side returns
|
|
153
|
+
* `undefined`, this method falls through to the exact, unmodified raw
|
|
154
|
+
* comparison that existed before VF-036 — see the block below and the
|
|
155
|
+
* `getIdentityComponents` doc comment for the full rationale, including
|
|
156
|
+
* why this fallback is symmetric but not transitive.
|
|
157
|
+
*
|
|
158
|
+
* The hook is consulted for primitive `T` as well as object `T`; nothing
|
|
159
|
+
* about this dispatch depends on `typeof this.value`.
|
|
160
|
+
*
|
|
161
|
+
* HARD CONSTRAINT: there is intentionally no unconditional
|
|
162
|
+
* `this === valueObject` shortcut at the top of this method — the only
|
|
163
|
+
* reflexivity shortcut this class takes lives inside the raw-comparison
|
|
164
|
+
* branch below (`this.value === valueObject.value`, which briefly
|
|
165
|
+
* predates any `getIdentityComponents` check on that branch only because
|
|
166
|
+
* neither side opted in). When neither side returns components, the
|
|
167
|
+
* executed code path is bit-for-bit identical to the pre-VF-036
|
|
168
|
+
* behavior, which is what keeps this a non-breaking, additive minor
|
|
169
|
+
* release rather than a breaking major.
|
|
170
|
+
*
|
|
171
|
+
* Uses deep structural comparison for object values, === for primitives,
|
|
172
|
+
* in the fallback branch.
|
|
173
|
+
*
|
|
174
|
+
* VF-023 (D-3, AC2/AC3, BREAKING): deep comparison now uses
|
|
175
|
+
* `LibUtils.deepEqual` instead of `JSON.stringify` equality. This fixes
|
|
176
|
+
* false negatives/positives inherent to JSON-based comparison — key
|
|
177
|
+
* order sensitivity is gone (already true before, since JSON.stringify
|
|
178
|
+
* of plain objects is insertion-order, not truly key-order-independent
|
|
179
|
+
* in all engines), values like `undefined`, `Date`, `Map`, `Set`, and
|
|
180
|
+
* `NaN` inside nested structures now compare by actual value semantics
|
|
181
|
+
* instead of silently dropping (`undefined`) or losing type
|
|
182
|
+
* (`Date`/`Map`/`Set` stringify to structurally different, sometimes
|
|
183
|
+
* colliding, JSON shapes).
|
|
11
184
|
* @param valueObject - The value object to compare with
|
|
12
185
|
* @returns True if they are equal, false otherwise
|
|
13
186
|
*/
|
|
14
187
|
equals(t) {
|
|
15
|
-
|
|
188
|
+
if (!t)
|
|
189
|
+
return !1;
|
|
190
|
+
const n = this.getIdentityComponents(), l = t.getIdentityComponents();
|
|
191
|
+
if (n !== void 0 && l !== void 0) {
|
|
192
|
+
if (n.length !== l.length)
|
|
193
|
+
return !1;
|
|
194
|
+
for (let o = 0; o < n.length; o++)
|
|
195
|
+
if (!c(n[o], l[o]))
|
|
196
|
+
return !1;
|
|
197
|
+
return !0;
|
|
198
|
+
}
|
|
199
|
+
return this.value === t.value ? !0 : typeof this.value == "object" && this.value !== null ? e.deepEqual(this.value, t.value) : !1;
|
|
16
200
|
}
|
|
17
201
|
/**
|
|
18
202
|
* Returns a string representation of the value object
|
|
@@ -35,16 +219,27 @@ class l {
|
|
|
35
219
|
getValue() {
|
|
36
220
|
return this.value;
|
|
37
221
|
}
|
|
222
|
+
/**
|
|
223
|
+
* VF-023 (D-1 regression fix): produces the message used when the
|
|
224
|
+
* constructor's synchronous `validate()` call fails. Because the base
|
|
225
|
+
* constructor now throws BEFORE the subclass constructor body runs (see
|
|
226
|
+
* the class-level doc comment), a subclass can no longer rely on its own
|
|
227
|
+
* post-`super(value)` `throw new Error('...')` — that code is
|
|
228
|
+
* unreachable, since the base class has already thrown by the time
|
|
229
|
+
* control would return to the subclass. Override this hook instead to
|
|
230
|
+
* provide a validation-error message specific to the subclass. Only
|
|
231
|
+
* `value` (the parameter, not subclass instance state — see the
|
|
232
|
+
* undefined-during-super() trap note above) is safe to use here.
|
|
233
|
+
* @param value The value that failed validation
|
|
234
|
+
* @returns The message to use for the thrown `Error`
|
|
235
|
+
*/
|
|
236
|
+
getInvalidValueMessage(t) {
|
|
237
|
+
return `Invalid value for ${this.constructor.name}`;
|
|
238
|
+
}
|
|
38
239
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
`[@vytches/ddd-value-objects] EntityIdFactory.${n}() is deprecated and will be removed in v1.0.0. Use ${t} instead. See https://github.com/vytches/ddd/blob/main/CHANGELOG.md for migration details.`
|
|
43
|
-
));
|
|
44
|
-
}
|
|
45
|
-
class i extends m {
|
|
46
|
-
constructor(t, a) {
|
|
47
|
-
super(t, a);
|
|
240
|
+
class i extends h {
|
|
241
|
+
constructor(t, n) {
|
|
242
|
+
super(t, n);
|
|
48
243
|
}
|
|
49
244
|
validate(t) {
|
|
50
245
|
switch (this.getType()) {
|
|
@@ -57,7 +252,7 @@ class i extends m {
|
|
|
57
252
|
case "text":
|
|
58
253
|
return e.isValidTextId(t);
|
|
59
254
|
default:
|
|
60
|
-
throw new
|
|
255
|
+
throw new s(`Unsupported IdType: ${this.getType()}`);
|
|
61
256
|
}
|
|
62
257
|
}
|
|
63
258
|
/**
|
|
@@ -85,27 +280,27 @@ class i extends m {
|
|
|
85
280
|
*/
|
|
86
281
|
static fromUUID(t) {
|
|
87
282
|
if (!e.hasValue(t))
|
|
88
|
-
throw
|
|
283
|
+
throw u.withValue("entity identifier");
|
|
89
284
|
if (!e.isValidUUID(t))
|
|
90
|
-
throw
|
|
285
|
+
throw s.withParameter("entity identifier");
|
|
91
286
|
return new i(t, "uuid");
|
|
92
287
|
}
|
|
93
288
|
static fromInteger(t) {
|
|
94
289
|
if (!e.isValidInteger(t))
|
|
95
|
-
throw
|
|
290
|
+
throw s.withParameter("entity identifier must be a non-negative integer");
|
|
96
291
|
return new i(t.toString(), "integer");
|
|
97
292
|
}
|
|
98
293
|
static fromBigInt(t) {
|
|
99
|
-
const
|
|
100
|
-
if (!e.isValidBigInt(
|
|
101
|
-
throw
|
|
102
|
-
return new i(
|
|
294
|
+
const n = e.normalizeIdToString(t);
|
|
295
|
+
if (!e.isValidBigInt(n))
|
|
296
|
+
throw s.withParameter("entity identifier must be a valid bigint");
|
|
297
|
+
return new i(n, "bigint");
|
|
103
298
|
}
|
|
104
299
|
static fromText(t) {
|
|
105
300
|
if (!e.hasValue(t))
|
|
106
|
-
throw
|
|
301
|
+
throw u.withValue("entity identifier");
|
|
107
302
|
if (!e.isValidTextId(t))
|
|
108
|
-
throw
|
|
303
|
+
throw s.withParameter("entity identifier contains invalid characters");
|
|
109
304
|
return new i(t, "text");
|
|
110
305
|
}
|
|
111
306
|
// --- Result-returning factory methods (non-throwing) ---
|
|
@@ -116,7 +311,7 @@ class i extends m {
|
|
|
116
311
|
* @since 0.24.0
|
|
117
312
|
*/
|
|
118
313
|
static tryFromUUID(t) {
|
|
119
|
-
return e.hasValue(t) ? e.isValidUUID(t) ?
|
|
314
|
+
return e.hasValue(t) ? e.isValidUUID(t) ? a.ok(new i(t, "uuid")) : a.fail(s.withParameter("entity identifier")) : a.fail(u.withValue("entity identifier"));
|
|
120
315
|
}
|
|
121
316
|
/**
|
|
122
317
|
* Create EntityId from integer, returning Result instead of throwing.
|
|
@@ -125,8 +320,8 @@ class i extends m {
|
|
|
125
320
|
* @since 0.24.0
|
|
126
321
|
*/
|
|
127
322
|
static tryFromInteger(t) {
|
|
128
|
-
return e.isValidInteger(t) ?
|
|
129
|
-
|
|
323
|
+
return e.isValidInteger(t) ? a.ok(new i(t.toString(), "integer")) : a.fail(
|
|
324
|
+
s.withParameter("entity identifier must be a non-negative integer")
|
|
130
325
|
);
|
|
131
326
|
}
|
|
132
327
|
/**
|
|
@@ -136,9 +331,9 @@ class i extends m {
|
|
|
136
331
|
* @since 0.24.0
|
|
137
332
|
*/
|
|
138
333
|
static tryFromBigInt(t) {
|
|
139
|
-
const
|
|
140
|
-
return e.isValidBigInt(
|
|
141
|
-
|
|
334
|
+
const n = e.normalizeIdToString(t);
|
|
335
|
+
return e.isValidBigInt(n) ? a.ok(new i(n, "bigint")) : a.fail(
|
|
336
|
+
s.withParameter("entity identifier must be a valid bigint")
|
|
142
337
|
);
|
|
143
338
|
}
|
|
144
339
|
/**
|
|
@@ -148,101 +343,28 @@ class i extends m {
|
|
|
148
343
|
* @since 0.24.0
|
|
149
344
|
*/
|
|
150
345
|
static tryFromText(t) {
|
|
151
|
-
return e.hasValue(t) ? e.isValidTextId(t) ?
|
|
152
|
-
|
|
153
|
-
) :
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
class d {
|
|
157
|
-
/**
|
|
158
|
-
* @deprecated Use EntityId.create() instead. Will be removed in v1.0.0.
|
|
159
|
-
*/
|
|
160
|
-
static createWithRandomUUID() {
|
|
161
|
-
return f("createWithRandomUUID", "EntityId.create()"), i.create();
|
|
162
|
-
}
|
|
163
|
-
/**
|
|
164
|
-
* @deprecated Use EntityId.fromUUID() instead. Will be removed in v1.0.0.
|
|
165
|
-
* @throws {MissingValueError} if value is empty
|
|
166
|
-
* @throws {InvalidParameterError} if value is empty
|
|
167
|
-
*/
|
|
168
|
-
static fromUUID(t) {
|
|
169
|
-
if (f("fromUUID", "EntityId.fromUUID()"), !e.hasValue(t))
|
|
170
|
-
throw o.withValue("entity identifier");
|
|
171
|
-
if (!e.isValidUUID(t))
|
|
172
|
-
throw r.withParameter("entity identifier");
|
|
173
|
-
return new i(t, "uuid");
|
|
174
|
-
}
|
|
175
|
-
/**
|
|
176
|
-
* @deprecated Use EntityId.fromInteger() instead. Will be removed in v1.0.0.
|
|
177
|
-
* @throws {InvalidParameterError} if value is empty
|
|
178
|
-
*/
|
|
179
|
-
static fromInteger(t) {
|
|
180
|
-
if (f("fromInteger", "EntityId.fromInteger()"), !e.isValidInteger(t))
|
|
181
|
-
throw r.withParameter("entity identifier must be a non-negative integer");
|
|
182
|
-
return new i(t.toString(), "integer");
|
|
183
|
-
}
|
|
184
|
-
/**
|
|
185
|
-
* @deprecated Use EntityId.fromBigInt() instead. Will be removed in v1.0.0.
|
|
186
|
-
* @throws {InvalidParameterError} if value is empty
|
|
187
|
-
*/
|
|
188
|
-
static fromBigInt(t) {
|
|
189
|
-
f("fromBigInt", "EntityId.fromBigInt()");
|
|
190
|
-
const a = e.normalizeIdToString(t);
|
|
191
|
-
if (!e.isValidBigInt(a))
|
|
192
|
-
throw r.withParameter("entity identifier must be a valid bigint");
|
|
193
|
-
return new i(a, "bigint");
|
|
194
|
-
}
|
|
195
|
-
/**
|
|
196
|
-
* @deprecated Use EntityId.fromText() instead. Will be removed in v1.0.0.
|
|
197
|
-
* @throws {MissingValueError} if value is empty
|
|
198
|
-
* @throws {InvalidParameterError} if value is empty
|
|
199
|
-
*/
|
|
200
|
-
static fromText(t) {
|
|
201
|
-
if (f("fromText", "EntityId.fromText()"), !e.hasValue(t))
|
|
202
|
-
throw o.withValue("entity identifier");
|
|
203
|
-
if (!e.isValidTextId(t))
|
|
204
|
-
throw r.withParameter("entity identifier contains invalid characters");
|
|
205
|
-
return new i(t, "text");
|
|
206
|
-
}
|
|
207
|
-
/** @deprecated Use EntityId.create() instead */
|
|
208
|
-
createWithRandomUUID() {
|
|
209
|
-
return i.create();
|
|
210
|
-
}
|
|
211
|
-
/** @deprecated Use EntityId.fromUUID() instead */
|
|
212
|
-
fromUUID(t) {
|
|
213
|
-
return d.fromUUID(t);
|
|
214
|
-
}
|
|
215
|
-
/** @deprecated Use EntityId.fromInteger() instead */
|
|
216
|
-
fromInteger(t) {
|
|
217
|
-
return d.fromInteger(t);
|
|
218
|
-
}
|
|
219
|
-
/** @deprecated Use EntityId.fromBigInt() instead */
|
|
220
|
-
fromBigInt(t) {
|
|
221
|
-
return d.fromBigInt(t);
|
|
222
|
-
}
|
|
223
|
-
/** @deprecated Use EntityId.fromText() instead */
|
|
224
|
-
fromText(t) {
|
|
225
|
-
return d.fromText(t);
|
|
346
|
+
return e.hasValue(t) ? e.isValidTextId(t) ? a.ok(new i(t, "text")) : a.fail(
|
|
347
|
+
s.withParameter("entity identifier contains invalid characters")
|
|
348
|
+
) : a.fail(u.withValue("entity identifier"));
|
|
226
349
|
}
|
|
227
350
|
}
|
|
228
|
-
function
|
|
229
|
-
return
|
|
351
|
+
function V(r) {
|
|
352
|
+
return r;
|
|
230
353
|
}
|
|
231
|
-
function
|
|
354
|
+
function p() {
|
|
232
355
|
return i.create();
|
|
233
356
|
}
|
|
234
|
-
function
|
|
235
|
-
return i.fromUUID(
|
|
357
|
+
function y(r) {
|
|
358
|
+
return i.fromUUID(r);
|
|
236
359
|
}
|
|
237
|
-
function
|
|
238
|
-
return i.fromText(
|
|
360
|
+
function U(r) {
|
|
361
|
+
return i.fromText(r);
|
|
239
362
|
}
|
|
240
363
|
export {
|
|
241
|
-
|
|
364
|
+
I as BaseValueObject,
|
|
242
365
|
i as EntityId,
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
I as newBrandedId
|
|
366
|
+
U as brandedIdFromText,
|
|
367
|
+
y as brandedIdFromUUID,
|
|
368
|
+
V as createBrandedId,
|
|
369
|
+
p as newBrandedId
|
|
248
370
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vytches/ddd-value-objects",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.31.0",
|
|
4
4
|
"description": "Enhanced value objects and EntityId implementations",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -17,7 +17,8 @@
|
|
|
17
17
|
"files": [
|
|
18
18
|
"dist",
|
|
19
19
|
"README.md",
|
|
20
|
-
"LLMGUIDE.md"
|
|
20
|
+
"LLMGUIDE.md",
|
|
21
|
+
"LICENSE"
|
|
21
22
|
],
|
|
22
23
|
"keywords": [
|
|
23
24
|
"ddd",
|
|
@@ -33,15 +34,15 @@
|
|
|
33
34
|
"directory": "packages/value-objects"
|
|
34
35
|
},
|
|
35
36
|
"dependencies": {
|
|
36
|
-
"@vytches/ddd-contracts": "0.
|
|
37
|
-
"@vytches/ddd-
|
|
38
|
-
"@vytches/ddd-
|
|
37
|
+
"@vytches/ddd-contracts": "0.31.0",
|
|
38
|
+
"@vytches/ddd-utils": "0.31.0",
|
|
39
|
+
"@vytches/ddd-domain-primitives": "0.31.0"
|
|
39
40
|
},
|
|
40
41
|
"devDependencies": {
|
|
41
42
|
"typescript": "^5.0.0",
|
|
42
43
|
"vite": "^7.3.2",
|
|
43
44
|
"vite-plugin-dts": "^4.0.0",
|
|
44
|
-
"vitest": "^4.
|
|
45
|
+
"vitest": "^4.1.0"
|
|
45
46
|
},
|
|
46
47
|
"nx": {
|
|
47
48
|
"tags": [
|