@shirudo/ddd-kit 3.0.0-rc.12 → 3.0.0-rc.14

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.
@@ -0,0 +1,1275 @@
1
+ import { err, ok } from "@shirudo/result";
2
+
3
+ //#region src/internal/structural/is-built-in.ts
4
+ /**
5
+ * Set of `Object.prototype.toString.call(x)` tags that the library treats
6
+ * as built-in atomic types. Members of this set are compared/cloned by
7
+ * reference (or with type-specific logic) rather than walked structurally.
8
+ *
9
+ * Detection is tag-based, since `Object.prototype.toString` gives the same
10
+ * answer across realms (an iframe's `Date` has the same tag as the main
11
+ * window's `Date`), and then brand-verified via internal-slot probes,
12
+ * because `Symbol.toStringTag` lets any plain object claim a built-in tag.
13
+ * The previous strategy also checked `globalThis[name] === constructor`
14
+ * and a `proto !== Object.prototype` heuristic; both broke for cross-realm
15
+ * objects and the latter additionally misclassified ordinary user classes
16
+ * as built-ins.
17
+ */
18
+ const BUILT_IN_TAGS = /* @__PURE__ */ new Set([
19
+ "[object Date]",
20
+ "[object RegExp]",
21
+ "[object Map]",
22
+ "[object Set]",
23
+ "[object WeakMap]",
24
+ "[object WeakSet]",
25
+ "[object Promise]",
26
+ "[object Error]",
27
+ "[object Boolean]",
28
+ "[object Number]",
29
+ "[object String]",
30
+ "[object BigInt]",
31
+ "[object ArrayBuffer]",
32
+ "[object SharedArrayBuffer]",
33
+ "[object DataView]"
34
+ ]);
35
+ function intrinsicGetter(proto, prop) {
36
+ const get = Object.getOwnPropertyDescriptor(proto, prop)?.get;
37
+ if (!get) throw new Error(`missing intrinsic getter for ${prop}`);
38
+ return get;
39
+ }
40
+ const dateGetTime = Date.prototype.getTime;
41
+ const mapSizeGet = intrinsicGetter(Map.prototype, "size");
42
+ const setSizeGet = intrinsicGetter(Set.prototype, "size");
43
+ const weakMapHas = WeakMap.prototype.has;
44
+ const weakSetHas = WeakSet.prototype.has;
45
+ const dataViewByteLengthGet = intrinsicGetter(DataView.prototype, "byteLength");
46
+ const arrayBufferByteLengthGet = intrinsicGetter(ArrayBuffer.prototype, "byteLength");
47
+ const sharedArrayBufferByteLengthGet = typeof SharedArrayBuffer === "undefined" ? void 0 : intrinsicGetter(SharedArrayBuffer.prototype, "byteLength");
48
+ const regExpSourceGet = intrinsicGetter(RegExp.prototype, "source");
49
+ const booleanValueOf = Boolean.prototype.valueOf;
50
+ const numberValueOf = Number.prototype.valueOf;
51
+ const stringValueOf = String.prototype.valueOf;
52
+ const bigIntValueOf = BigInt.prototype.valueOf;
53
+ const functionToString = Function.prototype.toString;
54
+ const PROBE_KEY = {};
55
+ const intrinsicConstructorSources = new Map([
56
+ "Object",
57
+ "Array",
58
+ "Date",
59
+ "RegExp",
60
+ "Map",
61
+ "Set",
62
+ "WeakMap",
63
+ "WeakSet",
64
+ "Promise",
65
+ "Error",
66
+ "EvalError",
67
+ "RangeError",
68
+ "ReferenceError",
69
+ "SyntaxError",
70
+ "TypeError",
71
+ "URIError",
72
+ "AggregateError",
73
+ "Boolean",
74
+ "Number",
75
+ "String",
76
+ "BigInt",
77
+ "ArrayBuffer",
78
+ "SharedArrayBuffer",
79
+ "DataView",
80
+ "Int8Array",
81
+ "Uint8Array",
82
+ "Uint8ClampedArray",
83
+ "Int16Array",
84
+ "Uint16Array",
85
+ "Int32Array",
86
+ "Uint32Array",
87
+ "Float32Array",
88
+ "Float64Array",
89
+ "BigInt64Array",
90
+ "BigUint64Array"
91
+ ].flatMap((name) => {
92
+ const intrinsic = Object.getOwnPropertyDescriptor(globalThis, name)?.value;
93
+ return typeof intrinsic === "function" ? [[name, /* @__PURE__ */ new Set([functionToString.call(intrinsic), `function ${name}() { [native code] }`])]] : [];
94
+ }));
95
+ const intrinsicConstructorSourceSet = new Set([...intrinsicConstructorSources.values()].flatMap((sources) => [...sources]));
96
+ const ERROR_INTRINSIC_NAMES = [
97
+ "Error",
98
+ "EvalError",
99
+ "RangeError",
100
+ "ReferenceError",
101
+ "SyntaxError",
102
+ "TypeError",
103
+ "URIError",
104
+ "AggregateError"
105
+ ];
106
+ function isIntrinsicConstructorPrototype(prototype, expectedName) {
107
+ const constructorDescriptor = Object.getOwnPropertyDescriptor(prototype, "constructor");
108
+ const candidateConstructor = constructorDescriptor?.value;
109
+ if (constructorDescriptor === void 0 || !("value" in constructorDescriptor) || typeof candidateConstructor !== "function") return false;
110
+ let candidateSource;
111
+ try {
112
+ candidateSource = functionToString.call(candidateConstructor);
113
+ } catch {
114
+ return false;
115
+ }
116
+ const expectedSources = expectedName === void 0 ? void 0 : intrinsicConstructorSources.get(expectedName);
117
+ if (expectedSources !== void 0 && !expectedSources.has(candidateSource) || expectedSources === void 0 && !intrinsicConstructorSourceSet.has(candidateSource)) return false;
118
+ const nameDescriptor = Object.getOwnPropertyDescriptor(candidateConstructor, "name");
119
+ const candidateName = nameDescriptor !== void 0 && "value" in nameDescriptor && typeof nameDescriptor.value === "string" ? nameDescriptor.value : void 0;
120
+ const intrinsicName = expectedName ?? candidateName;
121
+ const intrinsicSources = intrinsicName === void 0 ? void 0 : intrinsicConstructorSources.get(intrinsicName);
122
+ return candidateName === intrinsicName && intrinsicSources !== void 0 && intrinsicSources.has(candidateSource) && Object.getOwnPropertyDescriptor(candidateConstructor, "prototype")?.value === prototype;
123
+ }
124
+ /**
125
+ * Accepts an intrinsic prototype, optionally behind transparent
126
+ * `Symbol.toStringTag` override layers. A user-defined subclass has its own
127
+ * non-native constructor and is therefore rejected before reaching the
128
+ * intrinsic prototype.
129
+ */
130
+ function hasIntrinsicPrototypeChain(value, expectedName) {
131
+ const visited = /* @__PURE__ */ new WeakSet();
132
+ let prototype = Object.getPrototypeOf(value);
133
+ while (prototype !== null && !visited.has(prototype)) {
134
+ visited.add(prototype);
135
+ if (Object.hasOwn(prototype, "constructor")) return isIntrinsicConstructorPrototype(prototype, expectedName);
136
+ const ownKeys = Reflect.ownKeys(prototype);
137
+ if (ownKeys.length !== 1 || ownKeys[0] !== Symbol.toStringTag) return false;
138
+ prototype = Object.getPrototypeOf(prototype);
139
+ }
140
+ return false;
141
+ }
142
+ /**
143
+ * Tags that `deepEqual` compares BY REFERENCE (its unhandled-built-in
144
+ * fallback) and that `deepOmit` must therefore ALIAS rather than clone:
145
+ * a clone would break `deepEqualExcept(x, x)` reflexivity. Single source
146
+ * of truth so the two modules cannot drift: if `deepEqual` ever learns a
147
+ * by-value comparison for one of these, remove it here and add a clone
148
+ * case in `deepOmit`'s `cloneBuiltIn` in the same change.
149
+ */
150
+ const REFERENCE_COMPARED_TAGS = /* @__PURE__ */ new Set([
151
+ "[object Error]",
152
+ "[object ArrayBuffer]",
153
+ "[object SharedArrayBuffer]",
154
+ "[object Promise]",
155
+ "[object WeakMap]",
156
+ "[object WeakSet]"
157
+ ]);
158
+ /**
159
+ * Intrinsic tags of OPAQUE exotics: objects whose internal state no
160
+ * structural walk can observe (boxed Symbols, generator objects,
161
+ * WeakRefs, FinalizationRegistry handles). `deepEqual` compares them by
162
+ * identity and `deepOmit` passes them through by reference; treating
163
+ * them as (empty) plain objects would make ALL such exotics equal to
164
+ * each other. Deliberately a curated INTRINSIC list, not "every unknown
165
+ * tag": a user class exposing its own `Symbol.toStringTag` (e.g.
166
+ * "Money") keeps structural comparison, and a plain object spoofing one
167
+ * of these intrinsic tags gets the identity semantics of the thing it
168
+ * claims to be.
169
+ */
170
+ const OPAQUE_EXOTIC_TAGS = /* @__PURE__ */ new Set([
171
+ "[object Symbol]",
172
+ "[object Generator]",
173
+ "[object AsyncGenerator]",
174
+ "[object WeakRef]",
175
+ "[object FinalizationRegistry]"
176
+ ]);
177
+ /** True when `tag` names an opaque intrinsic; see {@link OPAQUE_EXOTIC_TAGS}. */
178
+ function isOpaqueExoticTag(tag) {
179
+ return OPAQUE_EXOTIC_TAGS.has(tag);
180
+ }
181
+ function findPropertyDescriptor(value, key) {
182
+ const visited = /* @__PURE__ */ new WeakSet();
183
+ let current = value;
184
+ while (current !== null && !visited.has(current)) {
185
+ visited.add(current);
186
+ const descriptor = Object.getOwnPropertyDescriptor(current, key);
187
+ if (descriptor !== void 0) return descriptor;
188
+ current = Object.getPrototypeOf(current);
189
+ }
190
+ }
191
+ function builtInTagWithoutInvokingAccessors(value) {
192
+ if (ArrayBuffer.isView(value)) return hasBrand(value, "[object DataView]") ? "[object DataView]" : "[object TypedArray]";
193
+ const descriptor = findPropertyDescriptor(value, Symbol.toStringTag);
194
+ if (descriptor !== void 0 && !("value" in descriptor)) return builtInTagFromBrand(value);
195
+ const tag = Object.prototype.toString.call(value);
196
+ if (BUILT_IN_TAGS.has(tag) && hasBrand(value, tag)) return tag;
197
+ return descriptor === void 0 ? void 0 : builtInTagFromBrand(value);
198
+ }
199
+ /** The type name in a tag: `"Map"` for `"[object Map]"`. */
200
+ function builtInTypeName(tag) {
201
+ return tag.slice(8, -1);
202
+ }
203
+ function builtInTagFromBrand(value) {
204
+ if (hasBrand(value, "[object Date]")) return "[object Date]";
205
+ if (hasBrand(value, "[object RegExp]")) return "[object RegExp]";
206
+ if (hasBrand(value, "[object Map]")) return "[object Map]";
207
+ if (hasBrand(value, "[object Set]")) return "[object Set]";
208
+ if (hasBrand(value, "[object WeakMap]")) return "[object WeakMap]";
209
+ if (hasBrand(value, "[object WeakSet]")) return "[object WeakSet]";
210
+ if (hasBrand(value, "[object DataView]")) return "[object DataView]";
211
+ if (hasBrand(value, "[object ArrayBuffer]")) return "[object ArrayBuffer]";
212
+ if (hasBrand(value, "[object SharedArrayBuffer]")) return "[object SharedArrayBuffer]";
213
+ if (hasBrand(value, "[object Boolean]")) return "[object Boolean]";
214
+ if (hasBrand(value, "[object Number]")) return "[object Number]";
215
+ if (hasBrand(value, "[object String]")) return "[object String]";
216
+ if (hasBrand(value, "[object BigInt]")) return "[object BigInt]";
217
+ if (hasNativePrototype(value, "Promise")) return "[object Promise]";
218
+ if (hasNativePrototype(value, "Error")) return "[object Error]";
219
+ }
220
+ function hasNativePrototype(value, expectedName) {
221
+ const visited = /* @__PURE__ */ new WeakSet();
222
+ let prototype = Object.getPrototypeOf(value);
223
+ while (prototype !== null && !visited.has(prototype)) {
224
+ visited.add(prototype);
225
+ if (isIntrinsicConstructorPrototype(prototype, expectedName)) return true;
226
+ prototype = Object.getPrototypeOf(prototype);
227
+ }
228
+ return false;
229
+ }
230
+ /**
231
+ * Verifies that `obj` genuinely is the type its tag claims, via an
232
+ * internal-slot probe. Promise and Error have no side-effect-free standard
233
+ * probe, so their visible tags remain conservative; masked instances are
234
+ * identified separately through their native prototype chain.
235
+ */
236
+ function hasBrand(obj, tag) {
237
+ try {
238
+ switch (tag) {
239
+ case "[object Date]":
240
+ dateGetTime.call(obj);
241
+ return true;
242
+ case "[object RegExp]":
243
+ regExpSourceGet.call(obj);
244
+ return true;
245
+ case "[object Map]":
246
+ mapSizeGet.call(obj);
247
+ return true;
248
+ case "[object Set]":
249
+ setSizeGet.call(obj);
250
+ return true;
251
+ case "[object WeakMap]":
252
+ weakMapHas.call(obj, PROBE_KEY);
253
+ return true;
254
+ case "[object WeakSet]":
255
+ weakSetHas.call(obj, PROBE_KEY);
256
+ return true;
257
+ case "[object DataView]":
258
+ dataViewByteLengthGet.call(obj);
259
+ return true;
260
+ case "[object ArrayBuffer]":
261
+ arrayBufferByteLengthGet.call(obj);
262
+ return true;
263
+ case "[object SharedArrayBuffer]":
264
+ if (!sharedArrayBufferByteLengthGet) return false;
265
+ sharedArrayBufferByteLengthGet.call(obj);
266
+ return true;
267
+ case "[object Boolean]":
268
+ booleanValueOf.call(obj);
269
+ return true;
270
+ case "[object Number]":
271
+ numberValueOf.call(obj);
272
+ return true;
273
+ case "[object String]":
274
+ stringValueOf.call(obj);
275
+ return true;
276
+ case "[object BigInt]":
277
+ bigIntValueOf.call(obj);
278
+ return true;
279
+ case "[object Promise]": return hasNativePrototype(obj, "Promise");
280
+ case "[object Error]": return ERROR_INTRINSIC_NAMES.some((name) => hasNativePrototype(obj, name));
281
+ default: return false;
282
+ }
283
+ } catch {
284
+ return false;
285
+ }
286
+ }
287
+ /**
288
+ * Returns `true` when `obj` is a built-in JavaScript type that should be
289
+ * treated atomically (compared/cloned as a unit, not walked structurally).
290
+ * Cross-realm safe, and brand-verified: a plain object spoofing a built-in
291
+ * tag via `Symbol.toStringTag` returns `false` and is walked structurally
292
+ * like any other plain object instead of crashing type-specific code.
293
+ *
294
+ * @param obj - The object to classify
295
+ * @param tag - The result of `Object.prototype.toString.call(obj)`, passed
296
+ * in so callers that already computed it don't pay twice
297
+ */
298
+ function isBuiltInObject(obj, tag) {
299
+ if (ArrayBuffer.isView(obj)) return true;
300
+ if (tag.endsWith("Array]")) return false;
301
+ return BUILT_IN_TAGS.has(tag) && hasBrand(obj, tag);
302
+ }
303
+ /**
304
+ * Brand-verified `WeakMap` check that holds across realms. `instanceof
305
+ * WeakMap` binds to the constructor of one realm, so a WeakMap from a `vm`
306
+ * context or an iframe reads as a foreign value. The internal-slot probe
307
+ * answers the same in every realm, and a plain object cannot spoof it
308
+ * through `Symbol.toStringTag`.
309
+ */
310
+ function isWeakMap(value) {
311
+ return typeof value === "object" && value !== null && hasBrand(value, "[object WeakMap]");
312
+ }
313
+
314
+ //#endregion
315
+ //#region src/internal/structural/deep-equal.ts
316
+ const objProto = Object.prototype;
317
+ const objToString = objProto.toString;
318
+ const objHasOwn = objProto.hasOwnProperty;
319
+ /**
320
+ * SameValueZero: `===` plus NaN-equals-NaN (and `+0 === -0`, unlike
321
+ * `Object.is`). The numeric semantics `deepEqual` documents for primitives,
322
+ * applied consistently inside TypedArrays, Dates and Number wrappers.
323
+ */
324
+ function sameValueZero(a, b) {
325
+ return a === b || Number.isNaN(a) && Number.isNaN(b);
326
+ }
327
+ /**
328
+ * Performs a deep equality check between two values.
329
+ *
330
+ * This function compares values recursively, handling:
331
+ * - Primitives (with special handling for NaN)
332
+ * - Arrays (nested arrays supported)
333
+ * - Objects (plain objects and class instances)
334
+ * - TypedArrays (Uint8Array, Int32Array, etc.)
335
+ * - DataView
336
+ * - Maps and Sets
337
+ * - Dates and RegExp
338
+ * - Wrapper objects (Boolean, Number, String)
339
+ * - Circular references (detected and handled)
340
+ *
341
+ * @param a - The first value to compare
342
+ * @param b - The second value to compare
343
+ * @returns `true` if the values are deeply equal, `false` otherwise
344
+ *
345
+ * @example
346
+ * ```ts
347
+ * deepEqual([1, 2, 3], [1, 2, 3]); // true
348
+ * deepEqual({ a: 1, b: [2, 3] }, { a: 1, b: [2, 3] }); // true
349
+ * deepEqual(NaN, NaN); // true
350
+ * deepEqual([1, 2], [1, 2, 3]); // false
351
+ * ```
352
+ */
353
+ function deepEqual(a, b) {
354
+ return deepEqualInner(a, b, /* @__PURE__ */ new WeakMap());
355
+ }
356
+ /**
357
+ * Internal recursive function for deep equality comparison.
358
+ *
359
+ * @internal
360
+ */
361
+ function deepEqualInner(a, b, visited) {
362
+ if (a === b) return true;
363
+ const typeA = typeof a;
364
+ const typeB = typeof b;
365
+ if (typeA !== "object" || a === null || typeB !== "object" || b === null) {
366
+ if (typeA === "number" && typeB === "number") return Number.isNaN(a) && Number.isNaN(b);
367
+ return false;
368
+ }
369
+ const objA = a;
370
+ const objB = b;
371
+ let cachedBs = visited.get(objA);
372
+ if (cachedBs?.has(objB)) return true;
373
+ if (!cachedBs) {
374
+ cachedBs = /* @__PURE__ */ new WeakSet();
375
+ visited.set(objA, cachedBs);
376
+ }
377
+ cachedBs.add(objB);
378
+ if (ArrayBuffer.isView(objA) || ArrayBuffer.isView(objB)) {
379
+ if (!ArrayBuffer.isView(objA) || !ArrayBuffer.isView(objB)) return false;
380
+ const tagA = objToString.call(objA);
381
+ if (tagA !== objToString.call(objB)) return false;
382
+ if (tagA === "[object DataView]") {
383
+ const viewA = objA;
384
+ const viewB = objB;
385
+ if (viewA.byteLength !== viewB.byteLength) return false;
386
+ const len = viewA.byteLength;
387
+ for (let i = 0; i < len; i++) if (viewA.getUint8(i) !== viewB.getUint8(i)) return false;
388
+ return true;
389
+ }
390
+ const arrA = objA;
391
+ const arrB = objB;
392
+ const len = arrA.length;
393
+ if (len !== arrB.length) return false;
394
+ for (let i = 0; i < len; i++) if (!sameValueZero(arrA[i], arrB[i])) return false;
395
+ return true;
396
+ }
397
+ if (Array.isArray(objA) || Array.isArray(objB)) {
398
+ if (!Array.isArray(objA) || !Array.isArray(objB)) return false;
399
+ if (objA.length !== objB.length) return false;
400
+ const keysA = Reflect.ownKeys(objA).filter((key) => key !== "length");
401
+ const keysB = Reflect.ownKeys(objB).filter((key) => key !== "length");
402
+ if (keysA.length !== keysB.length) return false;
403
+ const arrA = objA;
404
+ const arrB = objB;
405
+ for (const key of keysA) {
406
+ if (!objHasOwn.call(objB, key)) return false;
407
+ if (!deepEqualInner(arrA[key], arrB[key], visited)) return false;
408
+ }
409
+ return true;
410
+ }
411
+ const tagA = objToString.call(objA);
412
+ const tagB = objToString.call(objB);
413
+ if (tagA !== tagB) return false;
414
+ const builtInA = isBuiltInObject(objA, tagA);
415
+ if (builtInA !== isBuiltInObject(objB, tagB)) return false;
416
+ if (!builtInA) {
417
+ if (isOpaqueExoticTag(tagA)) return objA === objB;
418
+ return comparePlainObjects(objA, objB, visited);
419
+ }
420
+ switch (tagA) {
421
+ case "[object Map]": {
422
+ const mapA = objA;
423
+ const mapB = objB;
424
+ if (mapA.size !== mapB.size) return false;
425
+ for (const [key, valA] of mapA) {
426
+ if (!mapB.has(key)) return false;
427
+ if (!deepEqualInner(valA, mapB.get(key), visited)) return false;
428
+ }
429
+ return true;
430
+ }
431
+ case "[object Set]": {
432
+ const setA = objA;
433
+ const setB = objB;
434
+ if (setA.size !== setB.size) return false;
435
+ for (const value of setA) if (!setB.has(value)) return false;
436
+ return true;
437
+ }
438
+ case "[object Date]": return sameValueZero(objA.getTime(), objB.getTime());
439
+ case "[object RegExp]": {
440
+ const regA = objA;
441
+ const regB = objB;
442
+ return regA.source === regB.source && regA.flags === regB.flags;
443
+ }
444
+ case "[object Boolean]":
445
+ case "[object Number]":
446
+ case "[object String]":
447
+ case "[object BigInt]": return sameValueZero(objA.valueOf(), objB.valueOf());
448
+ default: return objA === objB;
449
+ }
450
+ }
451
+ /**
452
+ * Plain / custom objects: compare own enumerable string keys + own symbol
453
+ * keys and their values. Used both as the final fallback and for objects
454
+ * whose built-in-looking tag failed brand verification.
455
+ */
456
+ function comparePlainObjects(objA, objB, visited) {
457
+ const recA = objA;
458
+ const recB = objB;
459
+ const stringKeysA = Object.getOwnPropertyNames(objA);
460
+ const stringKeysB = Object.getOwnPropertyNames(objB);
461
+ if (stringKeysA.length !== stringKeysB.length) return false;
462
+ const symbolKeysA = Object.getOwnPropertySymbols(objA);
463
+ const symbolKeysB = Object.getOwnPropertySymbols(objB);
464
+ if (symbolKeysA.length !== symbolKeysB.length) return false;
465
+ const symbolKeysBSet = new Set(symbolKeysB);
466
+ for (const key of stringKeysA) if (!objHasOwn.call(objB, key)) return false;
467
+ for (const key of symbolKeysA) if (!symbolKeysBSet.has(key)) return false;
468
+ for (const key of stringKeysA) if (!deepEqualInner(recA[key], recB[key], visited)) return false;
469
+ for (const key of symbolKeysA) if (!deepEqualInner(recA[key], recB[key], visited)) return false;
470
+ return true;
471
+ }
472
+
473
+ //#endregion
474
+ //#region src/internal/structural/deep-omit.ts
475
+ /**
476
+ * Creates a deep copy of `value` with certain keys removed according to the
477
+ * provided rules.
478
+ *
479
+ * Walks the object tree and skips keys that match `ignoreKeys` /
480
+ * `ignoreKeyPredicate`. Built-in atomic types that `deepEqual` compares by
481
+ * value (Date, RegExp, Map, Set, TypedArrays, DataView) are cloned by type
482
+ * rather than walked, since their internal structure has no key filtering to
483
+ * apply. Types that `deepEqual` compares by reference (Error, ArrayBuffer,
484
+ * SharedArrayBuffer, Promise, WeakMap, WeakSet) are passed through by
485
+ * reference, so `deepEqualExcept(x, x)` stays reflexive. Cycles are
486
+ * preserved: a cycle `a → a` clones to `a' → a'`. Arrays retain sparse
487
+ * holes and all non-ignored own properties, including symbol keys.
488
+ *
489
+ * **Shared references.** Without `ignoreKeyPredicate`, an object reached
490
+ * via several paths dedupes to a single clone. With a predicate, each
491
+ * path gets its own clone, because the predicate may decide differently per
492
+ * path, so memoising the first path's result would be wrong. This is
493
+ * inherently exponential for diamond-shaped sharing (a node reachable
494
+ * via 2^n paths is cloned 2^n times); the walk aborts with a descriptive
495
+ * error after {@link PATH_SENSITIVE_VISIT_BUDGET} node visits instead of
496
+ * hanging the process.
497
+ *
498
+ * **Prototype-pollution safety.** `__proto__` and `constructor` keys
499
+ * encountered as *own* properties of the input (typical of `JSON.parse`
500
+ * output) are copied as inert data properties via `Object.defineProperty`
501
+ * so the clone graph cannot bleed into `Object.prototype`.
502
+ *
503
+ * **Class instances.** When the input is a class instance, the clone is
504
+ * built via `Object.create(proto)` so the prototype is preserved, but the
505
+ * constructor is NOT re-invoked, so class invariants enforced by the
506
+ * constructor are not re-checked. `deepOmit` is therefore best used for
507
+ * comparison/serialisation (`valueObjectEqualsExcept`, `deepEqualExcept`), not as
508
+ * a general-purpose clone for behaviour-carrying objects.
509
+ *
510
+ * @param value - The value to create a deep copy from
511
+ * @param options - Options specifying which keys to ignore
512
+ * @returns A deep copy of `value` with specified keys removed
513
+ */
514
+ function deepOmit(value, options) {
515
+ const visited = /* @__PURE__ */ new WeakMap();
516
+ return omitInternal(value, options, options.ignoreKeys ? new Set(options.ignoreKeys) : void 0, [], visited, options.ignoreKeyPredicate ? { visits: 0 } : void 0);
517
+ }
518
+ /**
519
+ * Maximum object-node visits for a single path-sensitive `deepOmit` walk.
520
+ * Per-path cloning expands exponentially on diamond-shaped sharing; past
521
+ * this bound the walk throws instead of hanging the process. One million
522
+ * visits covers any realistically tree-shaped input.
523
+ */
524
+ const PATH_SENSITIVE_VISIT_BUDGET = 1e6;
525
+ function omitInternal(value, options, ignoreKeys, path, visited, budget) {
526
+ if (value === null) return value;
527
+ if (typeof value !== "object") return value;
528
+ const obj = value;
529
+ if (visited.has(obj)) return visited.get(obj);
530
+ if (budget && ++budget.visits > PATH_SENSITIVE_VISIT_BUDGET) throw new Error(`deepOmit: exceeded ${PATH_SENSITIVE_VISIT_BUDGET} node visits. With ignoreKeyPredicate, objects reached via shared references are cloned once per path (the predicate may decide differently per path), which expands exponentially on diamond-shaped sharing. Restructure the input to a tree, or use ignoreKeys for path-independent filtering.`);
531
+ if (Array.isArray(obj)) {
532
+ const arr = obj;
533
+ const clone = new Array(arr.length);
534
+ const lengthDescriptor = Object.getOwnPropertyDescriptor(arr, "length");
535
+ visited.set(obj, clone);
536
+ for (const key of Reflect.ownKeys(arr)) {
537
+ if (key === "length") continue;
538
+ const segment = arrayPathSegment(key);
539
+ if (typeof segment !== "number" && shouldIgnoreKey(key, path, ignoreKeys, options)) continue;
540
+ const descriptor = Object.getOwnPropertyDescriptor(arr, key);
541
+ if (descriptor === void 0) continue;
542
+ path.push(segment);
543
+ if ("value" in descriptor) descriptor.value = omitInternal(descriptor.value, options, ignoreKeys, path, visited, budget);
544
+ Object.defineProperty(clone, key, descriptor);
545
+ path.pop();
546
+ }
547
+ if (lengthDescriptor !== void 0) Object.defineProperty(clone, "length", lengthDescriptor);
548
+ if (budget) visited.delete(obj);
549
+ return clone;
550
+ }
551
+ const tag = Object.prototype.toString.call(obj);
552
+ if (isBuiltInObject(obj, tag)) {
553
+ const builtInClone = cloneBuiltIn(obj, tag);
554
+ visited.set(obj, builtInClone);
555
+ return builtInClone;
556
+ }
557
+ if (isOpaqueExoticTag(tag)) {
558
+ visited.set(obj, obj);
559
+ return obj;
560
+ }
561
+ const clone = Object.create(Object.getPrototypeOf(obj));
562
+ visited.set(obj, clone);
563
+ const stringKeys = Object.getOwnPropertyNames(obj);
564
+ const symbolKeys = Object.getOwnPropertySymbols(obj);
565
+ for (const key of [...stringKeys, ...symbolKeys]) {
566
+ if (shouldIgnoreKey(key, path, ignoreKeys, options)) continue;
567
+ path.push(key);
568
+ assignOwn(clone, key, omitInternal(obj[key], options, ignoreKeys, path, visited, budget), Object.getOwnPropertyDescriptor(obj, key)?.enumerable ?? true);
569
+ path.pop();
570
+ }
571
+ if (budget) visited.delete(obj);
572
+ return clone;
573
+ }
574
+ function arrayPathSegment(key) {
575
+ if (typeof key === "symbol") return key;
576
+ const index = Number(key);
577
+ return Number.isInteger(index) && index >= 0 && index < 4294967295 && String(index) === key ? index : key;
578
+ }
579
+ /**
580
+ * Assigns `value` as an OWN data property on `target` without going through
581
+ * any inherited setter; critically, it never invokes the `__proto__` setter
582
+ * even when `key === "__proto__"`. Required to defeat prototype-pollution
583
+ * payloads that ship `__proto__` as a parsed-JSON own key.
584
+ */
585
+ function assignOwn(target, key, value, enumerable = true) {
586
+ Object.defineProperty(target, key, {
587
+ value,
588
+ writable: true,
589
+ enumerable,
590
+ configurable: true
591
+ });
592
+ }
593
+ /**
594
+ * Clones a built-in atomic type by case. Falls back to `structuredClone`
595
+ * for anything not explicitly enumerated (e.g. DataView, TypedArrays,
596
+ * Boolean/Number/String wrappers, all of which `deepEqual` compares by
597
+ * value). Types that `deepEqual` compares BY REFERENCE (the shared
598
+ * {@link REFERENCE_COMPARED_TAGS} set) are passed through by reference
599
+ * instead; cloning them would make `deepEqualExcept(x, x)` false.
600
+ * Promise/WeakMap/WeakSet additionally cannot be cloned at all
601
+ * (`structuredClone` rejects them).
602
+ */
603
+ function cloneBuiltIn(obj, tag) {
604
+ if (REFERENCE_COMPARED_TAGS.has(tag)) return obj;
605
+ switch (tag) {
606
+ case "[object Date]": return new Date(obj.getTime());
607
+ case "[object RegExp]": {
608
+ const re = obj;
609
+ const copy = new RegExp(re.source, re.flags);
610
+ copy.lastIndex = re.lastIndex;
611
+ return copy;
612
+ }
613
+ case "[object Map]": return new Map(obj);
614
+ case "[object Set]": return new Set(obj);
615
+ default: return structuredClone(obj);
616
+ }
617
+ }
618
+ function shouldIgnoreKey(key, path, ignoreKeys, options) {
619
+ if (ignoreKeys?.has(key)) return true;
620
+ if (options.ignoreKeyPredicate?.(key, path.slice())) return true;
621
+ return false;
622
+ }
623
+
624
+ //#endregion
625
+ //#region src/internal/structural/deep-equal-except.ts
626
+ /**
627
+ * Performs a deep equality comparison between two values after omitting specified keys.
628
+ *
629
+ * This function first removes the specified keys from both values using `deepOmit`,
630
+ * then performs a deep equality check using `deepEqual`.
631
+ *
632
+ * @param a - The first value to compare
633
+ * @param b - The second value to compare
634
+ * @param options - Options specifying which keys to omit before comparison
635
+ * @returns `true` if the values are deeply equal after omitting specified keys, `false` otherwise
636
+ *
637
+ * @example
638
+ * ```ts
639
+ * const obj1 = { id: 1, name: "Alice", updatedAt: "2024-01-01" };
640
+ * const obj2 = { id: 2, name: "Alice", updatedAt: "2024-01-02" };
641
+ *
642
+ * deepEqualExcept(obj1, obj2, { ignoreKeys: ["id", "updatedAt"] }); // true
643
+ * ```
644
+ */
645
+ function deepEqualExcept(a, b, options) {
646
+ const prunedA = deepOmit(a, options);
647
+ const prunedB = deepOmit(b, options);
648
+ return deepEqual(prunedA, prunedB);
649
+ }
650
+
651
+ //#endregion
652
+ //#region src/internal/cooperative-brand.ts
653
+ /**
654
+ * A cooperative brand marks an object that a kit constructor produced, so
655
+ * a second loaded copy of the kit (duplicate npm dependency, dual CJS/ESM
656
+ * load, plugin bundle) recognizes it. A module-private WeakSet cannot do
657
+ * that: it is bound to one loaded copy. The brand is a `Symbol.for` key
658
+ * with the value `true`. The property is non-enumerable, so it never
659
+ * leaks into spreads, JSON, or equality, and non-writable and
660
+ * non-configurable, so nothing edits it after the brand is applied.
661
+ *
662
+ * The probe reads the brand as an OWN property with exactly these
663
+ * attributes and requires the carrier to be frozen. An object that
664
+ * inherits a branded object through its prototype can carry mutable own
665
+ * overrides; a branded object that is still open can change after the
666
+ * brand. Neither is what the constructor produced. Every site that brands
667
+ * therefore freezes the carrier right after {@link applyCooperativeBrand}.
668
+ *
669
+ * The brand is forgeable BY DESIGN. It catches accidental hand-rolled
670
+ * literals; it is not a security boundary against code in the same
671
+ * process that fakes it on purpose.
672
+ *
673
+ * Internal utility (not exported from the package barrels).
674
+ */
675
+ function applyCooperativeBrand(target, brand) {
676
+ Object.defineProperty(target, brand, {
677
+ value: true,
678
+ enumerable: false,
679
+ writable: false,
680
+ configurable: false
681
+ });
682
+ }
683
+ /**
684
+ * Whether `value` carries `brand` as an own, non-enumerable, non-writable,
685
+ * non-configurable `true` and is frozen. A Proxy trap that throws reads
686
+ * as unbranded.
687
+ */
688
+ function hasCooperativeBrand(value, brand) {
689
+ if (value === null || typeof value !== "object" && typeof value !== "function") return false;
690
+ try {
691
+ const marker = Reflect.getOwnPropertyDescriptor(value, brand);
692
+ return marker?.value === true && marker.enumerable === false && marker.writable === false && marker.configurable === false && Object.isFrozen(value);
693
+ } catch {
694
+ return false;
695
+ }
696
+ }
697
+
698
+ //#endregion
699
+ //#region src/internal/structural/shadowed-mutators.ts
700
+ /**
701
+ * `Object.freeze` does not protect internal slots: a frozen Date still
702
+ * accepts `setTime`, a frozen Map still accepts `set`. A deep freeze
703
+ * therefore shadows these mutators with own throwing functions. The
704
+ * shadows are non-enumerable, so they are invisible to `Object.keys` and
705
+ * spread, and `structuredClone` drops them. A walk that checks a copy
706
+ * accepts them as machinery, not data.
707
+ */
708
+ const mutatorsByTag = /* @__PURE__ */ new Map([
709
+ ["[object Date]", [
710
+ "setTime",
711
+ "setMilliseconds",
712
+ "setUTCMilliseconds",
713
+ "setSeconds",
714
+ "setUTCSeconds",
715
+ "setMinutes",
716
+ "setUTCMinutes",
717
+ "setHours",
718
+ "setUTCHours",
719
+ "setDate",
720
+ "setUTCDate",
721
+ "setMonth",
722
+ "setUTCMonth",
723
+ "setFullYear",
724
+ "setUTCFullYear",
725
+ "setYear"
726
+ ]],
727
+ ["[object Map]", [
728
+ "set",
729
+ "delete",
730
+ "clear"
731
+ ]],
732
+ ["[object Set]", [
733
+ "add",
734
+ "delete",
735
+ "clear"
736
+ ]]
737
+ ]);
738
+ const mutatorShadowBrand = Symbol.for("@shirudo/ddd-kit/mutator-shadow/v1");
739
+ const shadows = /* @__PURE__ */ new Map();
740
+ function shadowOf(typeName, method) {
741
+ const key = `${typeName}.${method}`;
742
+ let shadow = shadows.get(key);
743
+ if (shadow === void 0) {
744
+ shadow = function throwFrozenMutation() {
745
+ throw new TypeError(`Cannot call ${method}() on a ${typeName} inside a deeply frozen value`);
746
+ };
747
+ applyCooperativeBrand(shadow, mutatorShadowBrand);
748
+ Object.freeze(shadow);
749
+ shadows.set(key, shadow);
750
+ }
751
+ return shadow;
752
+ }
753
+ const shadowDescriptor = {
754
+ value: void 0,
755
+ writable: false,
756
+ enumerable: false,
757
+ configurable: false
758
+ };
759
+ /**
760
+ * Installs the mutator shadows on a Date, a Map, or a Set whose tag is
761
+ * `tag`. Returns whether it installed them: a built-in of another kind has
762
+ * no mutator shadows, and a non-extensible one cannot take them.
763
+ */
764
+ function installMutatorShadows(object, tag) {
765
+ const mutators = mutatorsByTag.get(tag);
766
+ if (mutators === void 0 || !Object.isExtensible(object)) return false;
767
+ const typeName = builtInTypeName(tag);
768
+ for (const method of mutators) {
769
+ shadowDescriptor.value = shadowOf(typeName, method);
770
+ Object.defineProperty(object, method, shadowDescriptor);
771
+ }
772
+ return true;
773
+ }
774
+ /**
775
+ * Whether the own property `key` of a built-in whose tag is `tag` is a
776
+ * mutator shadow: a locked, non-enumerable function with the cooperative
777
+ * brand of a shadow, under a mutator key of that built-in. The brand
778
+ * catches accidents, not a forgery.
779
+ */
780
+ function isMutatorShadow(tag, key, descriptor) {
781
+ const shadow = descriptor.value;
782
+ return (mutatorsByTag.get(tag)?.includes(key) ?? false) && typeof shadow === "function" && descriptor.enumerable === false && descriptor.writable === false && descriptor.configurable === false && hasCooperativeBrand(shadow, mutatorShadowBrand);
783
+ }
784
+
785
+ //#endregion
786
+ //#region src/domain/value-object/value-object.ts
787
+ const mapEntries = Map.prototype.entries;
788
+ const setValues = Set.prototype.values;
789
+ const VALUE_OBJECT_CLASS = Symbol.for("@shirudo/ddd-kit/value-object-class/v1");
790
+ function recordValueObjectClass(instance, valueObjectClass) {
791
+ Object.defineProperty(instance, VALUE_OBJECT_CLASS, {
792
+ value: valueObjectClass,
793
+ enumerable: false,
794
+ writable: false,
795
+ configurable: false
796
+ });
797
+ }
798
+ function isValueObjectInstance(value) {
799
+ if (value === null || typeof value !== "object") return false;
800
+ const record = Reflect.getOwnPropertyDescriptor(value, VALUE_OBJECT_CLASS);
801
+ if (record === void 0 || typeof record.value !== "function" || record.enumerable !== false || record.writable !== false || record.configurable !== false) return false;
802
+ const props = Reflect.getOwnPropertyDescriptor(value, "props");
803
+ return props !== void 0 && isSealedProps(props.value);
804
+ }
805
+ function isSealedProps(props) {
806
+ if (typeof props !== "object" || props === null) return false;
807
+ return Object.isFrozen(props) || builtInTagWithoutInvokingAccessors(props) === "[object RegExp]";
808
+ }
809
+ function looksLikeValueObject(instance) {
810
+ if (Object.hasOwn(instance, VALUE_OBJECT_CLASS)) return true;
811
+ const props = Reflect.getOwnPropertyDescriptor(instance, "props");
812
+ return props !== void 0 && isSealedProps(props.value);
813
+ }
814
+ function openValueObjectKeys(instance) {
815
+ return Reflect.ownKeys(instance).filter((key) => key !== "props" && key !== VALUE_OBJECT_CLASS);
816
+ }
817
+ function rejectValueObjectAsInput(input, entry) {
818
+ if (isValueObjectInstance(input)) throw new TypeError(`${entry} does not accept a value object as its input: nest the value object under a key, or pass its props`);
819
+ }
820
+ /**
821
+ * Deep freezes an object and all its nested properties recursively, then
822
+ * returns it. Iterates both string-keyed and symbol-keyed own properties
823
+ * so the freeze symmetry matches `deepEqual` (which also considers symbol
824
+ * keys). Handles circular references by tracking visited objects.
825
+ *
826
+ * Note: `deepFreeze` mutates its argument in place; it sets `[[Frozen]]`
827
+ * on the object you pass in. Callers that need to avoid touching the
828
+ * input (e.g. `valueObject()`) should deep-clone first.
829
+ *
830
+ * Date/Map/Set keep internal-slot mutability under `Object.freeze`
831
+ * (`setTime`, `set`, `add`, … still work on frozen instances), so their
832
+ * mutator methods are shadowed with throwing own properties and Map/Set
833
+ * contents are frozen recursively. The shadows are non-enumerable:
834
+ * invisible to `Object.keys`, spread, `deepEqual`, and `structuredClone`.
835
+ *
836
+ * The shadowing is deny-by-enumeration: only the mutators known at
837
+ * release time are blocked. If the runtime grows a NEW mutator (e.g. the
838
+ * stage-3 `Map.prototype.getOrInsert` upsert proposal), it is not blocked
839
+ * until the list is updated. Treat the mutator blocking as a guard rail,
840
+ * not a security boundary.
841
+ *
842
+ * Two kinds of built-in pass through unfrozen. ArrayBuffer views
843
+ * (TypedArrays, DataView): the spec forbids freezing a view with elements,
844
+ * and a freeze cannot protect the underlying buffer, so their contents stay
845
+ * mutable. RegExp: pattern and flags live in immutable internal slots, and
846
+ * the only own data property, `lastIndex`, is scan state that every global
847
+ * or sticky match writes. A frozen RegExp protects nothing and throws on
848
+ * the first such match, so the RegExp keeps matching instead. A view or a
849
+ * RegExp passes through whole: an expando property on it stays open, and
850
+ * the walk does not enter its subtree.
851
+ */
852
+ const DEEP_FROZEN = /* @__PURE__ */ new WeakSet();
853
+ const KIT_SHADOWED = /* @__PURE__ */ new WeakSet();
854
+ function deepFreeze(obj) {
855
+ const walk = {
856
+ cyclic: false,
857
+ sealed: [],
858
+ done: /* @__PURE__ */ new Map(),
859
+ inProgress: /* @__PURE__ */ new Set()
860
+ };
861
+ freezeDeep(obj, walk);
862
+ if (!walk.cyclic) for (const object of walk.sealed) DEEP_FROZEN.add(object);
863
+ return obj;
864
+ }
865
+ /** Freezes `obj` and its subtree; returns whether the subtree is sealed. */
866
+ function freezeDeep(obj, walk) {
867
+ if (obj === null || typeof obj !== "object") return true;
868
+ if (DEEP_FROZEN.has(obj)) return true;
869
+ if (ArrayBuffer.isView(obj)) return true;
870
+ const finished = walk.done.get(obj);
871
+ if (finished !== void 0) return finished;
872
+ if (walk.inProgress.has(obj)) {
873
+ walk.cyclic = true;
874
+ return true;
875
+ }
876
+ const builtInTag = builtInTagWithoutInvokingAccessors(obj);
877
+ if (builtInTag === "[object RegExp]") return true;
878
+ walk.inProgress.add(obj);
879
+ let sealed = true;
880
+ if (builtInTag === "[object Date]" || builtInTag === "[object Map]" || builtInTag === "[object Set]") {
881
+ if (builtInTag === "[object Map]") for (const [key, value] of obj) {
882
+ if (!freezeDeep(key, walk)) sealed = false;
883
+ if (!freezeDeep(value, walk)) sealed = false;
884
+ }
885
+ else if (builtInTag === "[object Set]") {
886
+ for (const member of obj) if (!freezeDeep(member, walk)) sealed = false;
887
+ }
888
+ if (installMutatorShadows(obj, builtInTag) || KIT_SHADOWED.has(obj)) KIT_SHADOWED.add(obj);
889
+ else sealed = false;
890
+ }
891
+ const keys = Reflect.ownKeys(obj);
892
+ for (const key of keys) {
893
+ const value = obj[key];
894
+ if (value !== null && typeof value === "object") {
895
+ if (!freezeDeep(value, walk)) sealed = false;
896
+ }
897
+ }
898
+ Object.freeze(obj);
899
+ walk.inProgress.delete(obj);
900
+ walk.done.set(obj, sealed);
901
+ if (sealed) walk.sealed.push(obj);
902
+ return sealed;
903
+ }
904
+ /**
905
+ * Deep clone used by `valueObject()` and the `ValueObject` constructor.
906
+ *
907
+ * Plain objects, arrays, and Map values are walked manually so
908
+ * that symbol-keyed properties survive (which `structuredClone` silently
909
+ * drops; they would otherwise be invisible to `valueObjectEquals`, whose
910
+ * `deepEqual` DOES consider symbol keys) and shared references / cycles
911
+ * keep their identity across Map boundaries. Function values throw,
912
+ * preserving `valueObject()`'s documented data-not-behaviour gate. Built-ins without
913
+ * immutable value semantics throw a descriptive `TypeError`. A kit
914
+ * `ValueObject` instance is admitted by reference (see
915
+ * `VALUE_OBJECT_CLASS`). Every other custom class instance and every
916
+ * subclass of a built-in is rejected because cloning it without invoking
917
+ * its constructor can silently lose private or non-enumerable state. Map
918
+ * keys and Set members must be primitive because their equality is
919
+ * identity-based and object identity cannot survive defensive cloning.
920
+ * Accessor properties are rejected without invoking them. Admitted atomic
921
+ * built-ins (Date, RegExp and primitive wrappers) delegate to
922
+ * `structuredClone`, brand-verified so a `Symbol.toStringTag` spoofer is
923
+ * walked as the plain object it is. `__proto__` own keys are copied as
924
+ * inert data properties.
925
+ */
926
+ function cloneForVo(value, visited) {
927
+ if (typeof value === "function") throw new TypeError("valueObject() does not accept function values: Value Objects are data, not behaviour");
928
+ if (value === null || typeof value !== "object") return value;
929
+ const obj = value;
930
+ if (ArrayBuffer.isView(obj)) throwUnsupportedValueSemantics(builtInTagWithoutInvokingAccessors(obj) ?? "[object ArrayBuffer view]");
931
+ if (visited.has(obj)) return visited.get(obj);
932
+ if (Array.isArray(obj)) {
933
+ if (!hasIntrinsicPrototypeChain(obj, "Array")) throwUnsupportedClassInstance(obj);
934
+ const clone = new Array(obj.length);
935
+ visited.set(obj, clone);
936
+ for (const key of Reflect.ownKeys(obj)) {
937
+ if (key === "length") continue;
938
+ const descriptor = Object.getOwnPropertyDescriptor(obj, key);
939
+ if (descriptor === void 0) continue;
940
+ if (!("value" in descriptor)) throwUnsupportedAccessorProperty();
941
+ if (typeof key === "string" && !descriptor.enumerable) continue;
942
+ descriptor.value = cloneForVo(descriptor.value, visited);
943
+ Object.defineProperty(clone, key, descriptor);
944
+ }
945
+ return clone;
946
+ }
947
+ const tag = builtInTagWithoutInvokingAccessors(obj);
948
+ if (tag !== void 0) {
949
+ if (!hasIntrinsicPrototypeChain(obj)) throwUnsupportedClassInstance(obj);
950
+ if (tag === "[object Map]") {
951
+ const clone = /* @__PURE__ */ new Map();
952
+ visited.set(obj, clone);
953
+ for (const [key, entry] of mapEntries.call(obj)) {
954
+ if (!isPrimitiveValue(key)) throw new TypeError("valueObject() Map keys must be primitive values to preserve value equality");
955
+ clone.set(key, cloneForVo(entry, visited));
956
+ }
957
+ return clone;
958
+ }
959
+ if (tag === "[object Set]") {
960
+ const clone = /* @__PURE__ */ new Set();
961
+ visited.set(obj, clone);
962
+ for (const member of setValues.call(obj)) {
963
+ if (!isPrimitiveValue(member)) throw new TypeError("valueObject() Set members must be primitive values to preserve value equality");
964
+ clone.add(member);
965
+ }
966
+ return clone;
967
+ }
968
+ if (tag === "[object Promise]" || tag === "[object WeakMap]" || tag === "[object WeakSet]") throw new TypeError(`valueObject() cannot clone a ${tag.slice(8, -1)}: Value Objects are plain data`);
969
+ if (tag === "[object Error]" || tag === "[object ArrayBuffer]" || tag === "[object SharedArrayBuffer]") throwUnsupportedValueSemantics(tag);
970
+ if (tag === "[object RegExp]") {
971
+ const regExp = obj;
972
+ if (regExp.global || regExp.sticky) throw new TypeError("valueObject() cannot accept a global or sticky RegExp: its lastIndex is mutable scan state, not an immutable value");
973
+ }
974
+ const builtInClone = structuredClone(obj);
975
+ visited.set(obj, builtInClone);
976
+ return builtInClone;
977
+ }
978
+ const prototype = Object.getPrototypeOf(obj);
979
+ if (prototype !== null && (!isIntrinsicConstructorPrototype(prototype, "Object") || Object.getPrototypeOf(prototype) !== null)) {
980
+ if (isValueObjectInstance(obj)) {
981
+ const openKeys = openValueObjectKeys(obj);
982
+ if (openKeys.length > 0) throwOpenValueObjectFields(openKeys);
983
+ return obj;
984
+ }
985
+ throwUnsupportedClassInstance(obj);
986
+ }
987
+ const clone = Object.create(prototype === null ? null : Object.prototype);
988
+ visited.set(obj, clone);
989
+ for (const key of Reflect.ownKeys(obj)) {
990
+ const descriptor = Object.getOwnPropertyDescriptor(obj, key);
991
+ if (descriptor === void 0) continue;
992
+ if (!("value" in descriptor)) throwUnsupportedAccessorProperty();
993
+ if (typeof key === "string" && !descriptor.enumerable) continue;
994
+ Object.defineProperty(clone, key, {
995
+ value: cloneForVo(descriptor.value, visited),
996
+ writable: true,
997
+ enumerable: descriptor.enumerable,
998
+ configurable: true
999
+ });
1000
+ }
1001
+ return clone;
1002
+ }
1003
+ function throwUnsupportedClassInstance(instance) {
1004
+ const valueObjectHint = looksLikeValueObject(instance) ? ". A value object is recognized only when a copy of this kit version built it and its props are frozen" : "";
1005
+ throw new TypeError(`valueObject() cannot clone custom class instances: Value Objects are plain data${valueObjectHint}`);
1006
+ }
1007
+ function throwOpenValueObjectFields(keys) {
1008
+ throw new TypeError(`valueObject() cannot nest a value object with own fields outside props (${keys.map(String).join(", ")}): keep the state of a value object in props`);
1009
+ }
1010
+ function throwUnsupportedAccessorProperty() {
1011
+ throw new TypeError("valueObject() cannot clone accessor properties: Value Objects are plain data");
1012
+ }
1013
+ function throwUnsupportedValueSemantics(tag) {
1014
+ const name = tag.startsWith("[object ") ? tag.slice(8, -1) : tag;
1015
+ throw new TypeError(`valueObject() cannot accept ${name} values: Value Objects require immutable value semantics`);
1016
+ }
1017
+ function isPrimitiveValue(value) {
1018
+ return value === null || typeof value !== "object" && typeof value !== "function";
1019
+ }
1020
+ /**
1021
+ * Creates a deeply immutable value object from the given data.
1022
+ *
1023
+ * The input is first deep-cloned, then the clone is frozen, so calling
1024
+ * `valueObject(input)` never freezes the caller's own object graph as a
1025
+ * side-effect. Mutating the input afterwards does not bleed into the value object.
1026
+ * Symbol-keyed properties are preserved (matching `valueObjectEquals`). A kit
1027
+ * `ValueObject` instance nested in the input is kept by reference and
1028
+ * frozen in place; it must keep all of its state in `props`. A value
1029
+ * object as the input itself is rejected.
1030
+ * Function values and every other custom class instance are rejected
1031
+ * (Value Objects are plain data, not behaviour-bearing object graphs). Inputs must be trusted and
1032
+ * Proxy-free: ECMAScript provides no portable way to identify a transparent
1033
+ * Proxy without potentially executing its traps, so `valueObject()` is not a sandbox
1034
+ * for hostile in-process objects. Built-ins that cannot provide immutable,
1035
+ * value-based semantics are rejected instead of weakening the value object contract.
1036
+ *
1037
+ * @example
1038
+ * ```typescript
1039
+ * const nested = { lat: 52.5, lng: 13.4 };
1040
+ * const address = valueObject({ street: "Main St", coordinates: nested });
1041
+ * address.coordinates.lat = 99; // ❌ Cannot assign to read-only property
1042
+ * nested.lat = 0; // ✅ caller's input still mutable
1043
+ * ```
1044
+ */
1045
+ function valueObject(t) {
1046
+ rejectValueObjectAsInput(t, "valueObject()");
1047
+ return deepFreeze(cloneForVo(t, /* @__PURE__ */ new WeakMap()));
1048
+ }
1049
+ /**
1050
+ * Compares two value objects for equality based on their values.
1051
+ * Uses deep equality comparison that handles:
1052
+ * - Nested objects and arrays
1053
+ * - Primitives (including NaN)
1054
+ * - Dates, Maps, Sets, RegExp
1055
+ * - Symbol keys
1056
+ * - Circular references
1057
+ *
1058
+ * @param a - First value object
1059
+ * @param b - Second value object
1060
+ * @returns true if both objects have the same values, false otherwise
1061
+ *
1062
+ * @example
1063
+ * ```typescript
1064
+ * const money1 = valueObject({ amount: 100, currency: "USD" });
1065
+ * const money2 = valueObject({ amount: 100, currency: "USD" });
1066
+ * valueObjectEquals(money1, money2); // true
1067
+ *
1068
+ * const address1 = valueObject({
1069
+ * street: "Main St",
1070
+ * coordinates: { lat: 52.5, lng: 13.4 }
1071
+ * });
1072
+ * const address2 = valueObject({
1073
+ * street: "Main St",
1074
+ * coordinates: { lat: 52.5, lng: 13.4 }
1075
+ * });
1076
+ * valueObjectEquals(address1, address2); // true
1077
+ * ```
1078
+ */
1079
+ function valueObjectEquals(a, b) {
1080
+ return deepEqual(a, b);
1081
+ }
1082
+ /**
1083
+ * Compares two value objects for equality while ignoring specified keys.
1084
+ * Useful for comparing value objects that contain metadata or optional fields
1085
+ * that should not affect equality comparison.
1086
+ *
1087
+ * The walk enters a nested `ValueObject` instance like any other object,
1088
+ * so inside it the path continues with `props`; `ignoreKeys: ["props"]`
1089
+ * empties every nested value object. The key under which the kit records
1090
+ * the class of the instance is never ignored.
1091
+ *
1092
+ * @param a - First value object
1093
+ * @param b - Second value object
1094
+ * @param options - Options specifying which keys to ignore during comparison
1095
+ * @returns true if both objects have the same values (after ignoring specified keys), false otherwise
1096
+ *
1097
+ * @example
1098
+ * ```typescript
1099
+ * // Value object with metadata
1100
+ * const address1 = valueObject({
1101
+ * street: "Main St",
1102
+ * city: "Berlin",
1103
+ * metadata: { createdAt: "2024-01-01", updatedAt: "2024-01-02" }
1104
+ * });
1105
+ *
1106
+ * const address2 = valueObject({
1107
+ * street: "Main St",
1108
+ * city: "Berlin",
1109
+ * metadata: { createdAt: "2024-01-01", updatedAt: "2024-01-03" }
1110
+ * });
1111
+ *
1112
+ * // Compare ignoring metadata timestamps
1113
+ * valueObjectEqualsExcept(address1, address2, {
1114
+ * ignoreKeys: ["updatedAt"],
1115
+ * ignoreKeyPredicate: (key, path) => path.includes("metadata")
1116
+ * }); // true
1117
+ *
1118
+ * // Compare ignoring all metadata
1119
+ * valueObjectEqualsExcept(address1, address2, {
1120
+ * ignoreKeyPredicate: (key, path) => path.includes("metadata")
1121
+ * }); // true
1122
+ * ```
1123
+ */
1124
+ function valueObjectEqualsExcept(a, b, options) {
1125
+ return deepEqualExcept(a, b, keepValueObjectClass(options));
1126
+ }
1127
+ function keepValueObjectClass(options) {
1128
+ const { ignoreKeys, ignoreKeyPredicate } = options;
1129
+ return {
1130
+ ...options,
1131
+ ignoreKeys: ignoreKeys?.filter((key) => key !== VALUE_OBJECT_CLASS),
1132
+ ignoreKeyPredicate: ignoreKeyPredicate && ((key, path) => key !== VALUE_OBJECT_CLASS && ignoreKeyPredicate(key, path))
1133
+ };
1134
+ }
1135
+ /**
1136
+ * Creates a value object with optional validation.
1137
+ * Returns a Result type instead of throwing an error.
1138
+ *
1139
+ * Note: the Result covers VALIDATION failures only. Non-data values and
1140
+ * built-ins without immutable value semantics still throw a
1141
+ * `TypeError` from `valueObject()`; they cannot occur in parsed JSON and signal
1142
+ * a programming error, not a validation failure.
1143
+ *
1144
+ * @param t - The data to convert into a value object
1145
+ * @param validate - Validation function that returns true if valid
1146
+ * @param errorMessage - Optional custom error message if validation fails
1147
+ * @returns Result containing the value object if valid, or an error message if validation fails
1148
+ *
1149
+ * @example
1150
+ * ```typescript
1151
+ * const result = valueObjectOrError(
1152
+ * { amount: 100, currency: "USD" },
1153
+ * (m) => m.amount >= 0 && m.currency.length === 3,
1154
+ * "Invalid money: amount must be non-negative and currency must be 3 characters"
1155
+ * );
1156
+ *
1157
+ * if (result.ok) {
1158
+ * console.log(result.value); // Use the value object
1159
+ * } else {
1160
+ * console.error(result.error); // Handle validation error
1161
+ * }
1162
+ * ```
1163
+ */
1164
+ function valueObjectOrError(t, validate, errorMessage) {
1165
+ if (!validate(t)) return err(errorMessage ?? `Validation failed for value object: ${describeValue(t)}`);
1166
+ return ok(valueObject(t));
1167
+ }
1168
+ /**
1169
+ * Best-effort rendering of a value for the default validation-failure
1170
+ * message. `JSON.stringify` throws for cyclic and BigInt-bearing values, and
1171
+ * the error path of a Result-returning function must never throw itself.
1172
+ */
1173
+ function describeValue(value) {
1174
+ try {
1175
+ const json = JSON.stringify(value);
1176
+ if (json !== void 0) return json;
1177
+ } catch {}
1178
+ return String(value);
1179
+ }
1180
+ /**
1181
+ * Abstract base class for creating Value Objects.
1182
+ * Value Objects are immutable and defined by their properties. A value
1183
+ * object can hold other value objects in its props. Every instance
1184
+ * records its class under an own symbol key, so `equals` compares a
1185
+ * nested value object by class and props and does not call its `equals`
1186
+ * method.
1187
+ *
1188
+ * @template T - The shape of the value object's properties
1189
+ */
1190
+ var ValueObject = class {
1191
+ props;
1192
+ /**
1193
+ * Creates a new ValueObject.
1194
+ * The plain-data properties are deep-cloned and then deeply
1195
+ * frozen, so the caller's own object graph is never frozen or mutated,
1196
+ * and later mutation of the input does not bleed into the value object.
1197
+ *
1198
+ * @param props - The properties of the value object
1199
+ * @example
1200
+ * ```ts
1201
+ * class Money extends ValueObject<{ amount: number; currency: string }> {
1202
+ * constructor(props: { amount: number; currency: string }) {
1203
+ * super(props);
1204
+ * }
1205
+ *
1206
+ * protected validate(props: { amount: number; currency: string }): void {
1207
+ * if (props.amount < 0) throw new Error("Amount cannot be negative");
1208
+ * }
1209
+ * }
1210
+ * ```
1211
+ */
1212
+ constructor(props) {
1213
+ rejectValueObjectAsInput(props, "new ValueObject()");
1214
+ this.validate(props);
1215
+ this.props = deepFreeze(cloneForVo(props, /* @__PURE__ */ new WeakMap()));
1216
+ recordValueObjectClass(this, this.constructor);
1217
+ }
1218
+ /**
1219
+ * Optional validation hook that can be overridden by subclasses.
1220
+ * Should throw an error if validation fails.
1221
+ *
1222
+ * @param props - The properties to validate
1223
+ * @throws Error if validation fails
1224
+ */
1225
+ validate(props) {}
1226
+ /**
1227
+ * Checks if this value object is equal to another.
1228
+ * Uses deep equality comparison on the properties and checks for constructor equality.
1229
+ *
1230
+ * @param other - The other value object to compare
1231
+ * @returns true if the properties are deeply equal and constructors match
1232
+ */
1233
+ equals(other) {
1234
+ if (other === null || other === void 0) return false;
1235
+ if (this.constructor !== other.constructor) return false;
1236
+ return deepEqual(this.props, other.props);
1237
+ }
1238
+ /**
1239
+ * Creates a clone of the value object with optional property overrides.
1240
+ *
1241
+ * @param props - Optional properties to override
1242
+ * @returns A new instance of the value object
1243
+ */
1244
+ clone(props) {
1245
+ const Constructor = this.constructor;
1246
+ const merged = {
1247
+ ...this.props,
1248
+ ...props || {}
1249
+ };
1250
+ for (const key of Reflect.ownKeys(this.props)) {
1251
+ const descriptor = Object.getOwnPropertyDescriptor(this.props, key);
1252
+ if (descriptor === void 0 || descriptor.enumerable) continue;
1253
+ if (props && Object.hasOwn(props, key)) continue;
1254
+ Object.defineProperty(merged, key, {
1255
+ value: this.props[key],
1256
+ writable: true,
1257
+ enumerable: false,
1258
+ configurable: true
1259
+ });
1260
+ }
1261
+ return new Constructor(merged);
1262
+ }
1263
+ /**
1264
+ * Serializes the value object to its raw properties for JSON operations.
1265
+ *
1266
+ * @returns The raw properties object
1267
+ */
1268
+ toJSON() {
1269
+ return this.props;
1270
+ }
1271
+ };
1272
+
1273
+ //#endregion
1274
+ export { isIntrinsicConstructorPrototype as _, valueObjectEqualsExcept as a, applyCooperativeBrand as c, deepEqual as d, builtInTagWithoutInvokingAccessors as f, isBuiltInObject as g, hasIntrinsicPrototypeChain as h, valueObjectEquals as i, hasCooperativeBrand as l, findPropertyDescriptor as m, deepFreeze as n, valueObjectOrError as o, builtInTypeName as p, valueObject as r, isMutatorShadow as s, ValueObject as t, deepOmit as u, isWeakMap as v };
1275
+ //# sourceMappingURL=value-object.js.map