@shirudo/ddd-kit 2.2.0 → 3.0.0-rc.4

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,639 @@
1
+ //#region src/utils/array/is-built-in.ts
2
+ /**
3
+ * Set of `Object.prototype.toString.call(x)` tags that the library treats
4
+ * as built-in atomic types. Members of this set are compared/cloned by
5
+ * reference (or with type-specific logic) rather than walked structurally.
6
+ *
7
+ * Detection is tag-based, since `Object.prototype.toString` gives the same
8
+ * answer across realms (an iframe's `Date` has the same tag as the main
9
+ * window's `Date`), and then brand-verified via internal-slot probes,
10
+ * because `Symbol.toStringTag` lets any plain object claim a built-in tag.
11
+ * The previous strategy also checked `globalThis[name] === constructor`
12
+ * and a `proto !== Object.prototype` heuristic; both broke for cross-realm
13
+ * objects and the latter additionally misclassified ordinary user classes
14
+ * as built-ins.
15
+ */
16
+ const BUILT_IN_TAGS = /* @__PURE__ */ new Set([
17
+ "[object Date]",
18
+ "[object RegExp]",
19
+ "[object Map]",
20
+ "[object Set]",
21
+ "[object WeakMap]",
22
+ "[object WeakSet]",
23
+ "[object Promise]",
24
+ "[object Error]",
25
+ "[object Boolean]",
26
+ "[object Number]",
27
+ "[object String]",
28
+ "[object BigInt]",
29
+ "[object ArrayBuffer]",
30
+ "[object SharedArrayBuffer]",
31
+ "[object DataView]"
32
+ ]);
33
+ function intrinsicGetter(proto, prop) {
34
+ const get = Object.getOwnPropertyDescriptor(proto, prop)?.get;
35
+ if (!get) throw new Error(`missing intrinsic getter for ${prop}`);
36
+ return get;
37
+ }
38
+ const dateGetTime = Date.prototype.getTime;
39
+ const mapSizeGet = intrinsicGetter(Map.prototype, "size");
40
+ const setSizeGet = intrinsicGetter(Set.prototype, "size");
41
+ const weakMapHas = WeakMap.prototype.has;
42
+ const weakSetHas = WeakSet.prototype.has;
43
+ const dataViewByteLengthGet = intrinsicGetter(DataView.prototype, "byteLength");
44
+ const arrayBufferByteLengthGet = intrinsicGetter(ArrayBuffer.prototype, "byteLength");
45
+ const sharedArrayBufferByteLengthGet = typeof SharedArrayBuffer === "undefined" ? void 0 : intrinsicGetter(SharedArrayBuffer.prototype, "byteLength");
46
+ const regExpSourceGet = intrinsicGetter(RegExp.prototype, "source");
47
+ const booleanValueOf = Boolean.prototype.valueOf;
48
+ const numberValueOf = Number.prototype.valueOf;
49
+ const stringValueOf = String.prototype.valueOf;
50
+ const bigIntValueOf = BigInt.prototype.valueOf;
51
+ const functionToString = Function.prototype.toString;
52
+ const PROBE_KEY = {};
53
+ const intrinsicConstructorSources = new Map([
54
+ "Object",
55
+ "Array",
56
+ "Date",
57
+ "RegExp",
58
+ "Map",
59
+ "Set",
60
+ "WeakMap",
61
+ "WeakSet",
62
+ "Promise",
63
+ "Error",
64
+ "EvalError",
65
+ "RangeError",
66
+ "ReferenceError",
67
+ "SyntaxError",
68
+ "TypeError",
69
+ "URIError",
70
+ "AggregateError",
71
+ "Boolean",
72
+ "Number",
73
+ "String",
74
+ "BigInt",
75
+ "ArrayBuffer",
76
+ "SharedArrayBuffer",
77
+ "DataView",
78
+ "Int8Array",
79
+ "Uint8Array",
80
+ "Uint8ClampedArray",
81
+ "Int16Array",
82
+ "Uint16Array",
83
+ "Int32Array",
84
+ "Uint32Array",
85
+ "Float32Array",
86
+ "Float64Array",
87
+ "BigInt64Array",
88
+ "BigUint64Array"
89
+ ].flatMap((name) => {
90
+ const intrinsic = Object.getOwnPropertyDescriptor(globalThis, name)?.value;
91
+ return typeof intrinsic === "function" ? [[name, functionToString.call(intrinsic)]] : [];
92
+ }));
93
+ const intrinsicConstructorSourceSet = new Set(intrinsicConstructorSources.values());
94
+ const ERROR_INTRINSIC_NAMES = [
95
+ "Error",
96
+ "EvalError",
97
+ "RangeError",
98
+ "ReferenceError",
99
+ "SyntaxError",
100
+ "TypeError",
101
+ "URIError",
102
+ "AggregateError"
103
+ ];
104
+ function isIntrinsicConstructorPrototype(prototype, expectedName) {
105
+ const constructorDescriptor = Object.getOwnPropertyDescriptor(prototype, "constructor");
106
+ const candidateConstructor = constructorDescriptor?.value;
107
+ if (constructorDescriptor === void 0 || !("value" in constructorDescriptor) || typeof candidateConstructor !== "function") return false;
108
+ let candidateSource;
109
+ try {
110
+ candidateSource = functionToString.call(candidateConstructor);
111
+ } catch {
112
+ return false;
113
+ }
114
+ const expectedSource = expectedName === void 0 ? void 0 : intrinsicConstructorSources.get(expectedName);
115
+ if (expectedSource !== void 0 && candidateSource !== expectedSource || expectedSource === void 0 && !intrinsicConstructorSourceSet.has(candidateSource)) return false;
116
+ const nameDescriptor = Object.getOwnPropertyDescriptor(candidateConstructor, "name");
117
+ const candidateName = nameDescriptor !== void 0 && "value" in nameDescriptor && typeof nameDescriptor.value === "string" ? nameDescriptor.value : void 0;
118
+ const intrinsicName = expectedName ?? candidateName;
119
+ const intrinsicSource = intrinsicName === void 0 ? void 0 : intrinsicConstructorSources.get(intrinsicName);
120
+ return candidateName === intrinsicName && intrinsicSource !== void 0 && candidateSource === intrinsicSource && Object.getOwnPropertyDescriptor(candidateConstructor, "prototype")?.value === prototype;
121
+ }
122
+ /**
123
+ * Accepts an intrinsic prototype, optionally behind transparent
124
+ * `Symbol.toStringTag` override layers. A user-defined subclass has its own
125
+ * non-native constructor and is therefore rejected before reaching the
126
+ * intrinsic prototype.
127
+ */
128
+ function hasIntrinsicPrototypeChain(value, expectedName) {
129
+ const visited = /* @__PURE__ */ new WeakSet();
130
+ let prototype = Object.getPrototypeOf(value);
131
+ while (prototype !== null && !visited.has(prototype)) {
132
+ visited.add(prototype);
133
+ if (Object.hasOwn(prototype, "constructor")) return isIntrinsicConstructorPrototype(prototype, expectedName);
134
+ const ownKeys = Reflect.ownKeys(prototype);
135
+ if (ownKeys.length !== 1 || ownKeys[0] !== Symbol.toStringTag) return false;
136
+ prototype = Object.getPrototypeOf(prototype);
137
+ }
138
+ return false;
139
+ }
140
+ /**
141
+ * Tags that `deepEqual` compares BY REFERENCE (its unhandled-built-in
142
+ * fallback) and that `deepOmit` must therefore ALIAS rather than clone:
143
+ * a clone would break `deepEqualExcept(x, x)` reflexivity. Single source
144
+ * of truth so the two modules cannot drift: if `deepEqual` ever learns a
145
+ * by-value comparison for one of these, remove it here and add a clone
146
+ * case in `deepOmit`'s `cloneBuiltIn` in the same change.
147
+ */
148
+ const REFERENCE_COMPARED_TAGS = /* @__PURE__ */ new Set([
149
+ "[object Error]",
150
+ "[object ArrayBuffer]",
151
+ "[object SharedArrayBuffer]",
152
+ "[object Promise]",
153
+ "[object WeakMap]",
154
+ "[object WeakSet]"
155
+ ]);
156
+ /**
157
+ * Intrinsic tags of OPAQUE exotics: objects whose internal state no
158
+ * structural walk can observe (boxed Symbols, generator objects,
159
+ * WeakRefs, FinalizationRegistry handles). `deepEqual` compares them by
160
+ * identity and `deepOmit` passes them through by reference; treating
161
+ * them as (empty) plain objects would make ALL such exotics equal to
162
+ * each other. Deliberately a curated INTRINSIC list, not "every unknown
163
+ * tag": a user class exposing its own `Symbol.toStringTag` (e.g.
164
+ * "Money") keeps structural comparison, and a plain object spoofing one
165
+ * of these intrinsic tags gets the identity semantics of the thing it
166
+ * claims to be.
167
+ */
168
+ const OPAQUE_EXOTIC_TAGS = /* @__PURE__ */ new Set([
169
+ "[object Symbol]",
170
+ "[object Generator]",
171
+ "[object AsyncGenerator]",
172
+ "[object WeakRef]",
173
+ "[object FinalizationRegistry]"
174
+ ]);
175
+ /** True when `tag` names an opaque intrinsic; see {@link OPAQUE_EXOTIC_TAGS}. */
176
+ function isOpaqueExoticTag(tag) {
177
+ return OPAQUE_EXOTIC_TAGS.has(tag);
178
+ }
179
+ function findPropertyDescriptor(value, key) {
180
+ const visited = /* @__PURE__ */ new WeakSet();
181
+ let current = value;
182
+ while (current !== null && !visited.has(current)) {
183
+ visited.add(current);
184
+ const descriptor = Object.getOwnPropertyDescriptor(current, key);
185
+ if (descriptor !== void 0) return descriptor;
186
+ current = Object.getPrototypeOf(current);
187
+ }
188
+ }
189
+ function builtInTagWithoutInvokingAccessors(value) {
190
+ if (ArrayBuffer.isView(value)) return hasBrand(value, "[object DataView]") ? "[object DataView]" : "[object TypedArray]";
191
+ const descriptor = findPropertyDescriptor(value, Symbol.toStringTag);
192
+ if (descriptor !== void 0 && !("value" in descriptor)) return builtInTagFromBrand(value);
193
+ const tag = Object.prototype.toString.call(value);
194
+ if (BUILT_IN_TAGS.has(tag) && hasBrand(value, tag)) return tag;
195
+ return descriptor === void 0 ? void 0 : builtInTagFromBrand(value);
196
+ }
197
+ function mutableBuiltInTagWithoutInvokingAccessors(value) {
198
+ const tag = builtInTagWithoutInvokingAccessors(value);
199
+ return tag === "[object Date]" || tag === "[object Map]" || tag === "[object Set]" ? tag : void 0;
200
+ }
201
+ function builtInTagFromBrand(value) {
202
+ if (hasBrand(value, "[object Date]")) return "[object Date]";
203
+ if (hasBrand(value, "[object RegExp]")) return "[object RegExp]";
204
+ if (hasBrand(value, "[object Map]")) return "[object Map]";
205
+ if (hasBrand(value, "[object Set]")) return "[object Set]";
206
+ if (hasBrand(value, "[object WeakMap]")) return "[object WeakMap]";
207
+ if (hasBrand(value, "[object WeakSet]")) return "[object WeakSet]";
208
+ if (hasBrand(value, "[object DataView]")) return "[object DataView]";
209
+ if (hasBrand(value, "[object ArrayBuffer]")) return "[object ArrayBuffer]";
210
+ if (hasBrand(value, "[object SharedArrayBuffer]")) return "[object SharedArrayBuffer]";
211
+ if (hasBrand(value, "[object Boolean]")) return "[object Boolean]";
212
+ if (hasBrand(value, "[object Number]")) return "[object Number]";
213
+ if (hasBrand(value, "[object String]")) return "[object String]";
214
+ if (hasBrand(value, "[object BigInt]")) return "[object BigInt]";
215
+ if (hasNativePrototype(value, "Promise")) return "[object Promise]";
216
+ if (hasNativePrototype(value, "Error")) return "[object Error]";
217
+ }
218
+ function hasNativePrototype(value, expectedName) {
219
+ const visited = /* @__PURE__ */ new WeakSet();
220
+ let prototype = Object.getPrototypeOf(value);
221
+ while (prototype !== null && !visited.has(prototype)) {
222
+ visited.add(prototype);
223
+ if (isIntrinsicConstructorPrototype(prototype, expectedName)) return true;
224
+ prototype = Object.getPrototypeOf(prototype);
225
+ }
226
+ return false;
227
+ }
228
+ /**
229
+ * Verifies that `obj` genuinely is the type its tag claims, via an
230
+ * internal-slot probe. Promise and Error have no side-effect-free standard
231
+ * probe, so their visible tags remain conservative; masked instances are
232
+ * identified separately through their native prototype chain.
233
+ */
234
+ function hasBrand(obj, tag) {
235
+ try {
236
+ switch (tag) {
237
+ case "[object Date]":
238
+ dateGetTime.call(obj);
239
+ return true;
240
+ case "[object RegExp]":
241
+ regExpSourceGet.call(obj);
242
+ return true;
243
+ case "[object Map]":
244
+ mapSizeGet.call(obj);
245
+ return true;
246
+ case "[object Set]":
247
+ setSizeGet.call(obj);
248
+ return true;
249
+ case "[object WeakMap]":
250
+ weakMapHas.call(obj, PROBE_KEY);
251
+ return true;
252
+ case "[object WeakSet]":
253
+ weakSetHas.call(obj, PROBE_KEY);
254
+ return true;
255
+ case "[object DataView]":
256
+ dataViewByteLengthGet.call(obj);
257
+ return true;
258
+ case "[object ArrayBuffer]":
259
+ arrayBufferByteLengthGet.call(obj);
260
+ return true;
261
+ case "[object SharedArrayBuffer]":
262
+ if (!sharedArrayBufferByteLengthGet) return false;
263
+ sharedArrayBufferByteLengthGet.call(obj);
264
+ return true;
265
+ case "[object Boolean]":
266
+ booleanValueOf.call(obj);
267
+ return true;
268
+ case "[object Number]":
269
+ numberValueOf.call(obj);
270
+ return true;
271
+ case "[object String]":
272
+ stringValueOf.call(obj);
273
+ return true;
274
+ case "[object BigInt]":
275
+ bigIntValueOf.call(obj);
276
+ return true;
277
+ case "[object Promise]": return hasNativePrototype(obj, "Promise");
278
+ case "[object Error]": return ERROR_INTRINSIC_NAMES.some((name) => hasNativePrototype(obj, name));
279
+ default: return false;
280
+ }
281
+ } catch {
282
+ return false;
283
+ }
284
+ }
285
+ /**
286
+ * Returns `true` when `obj` is a built-in JavaScript type that should be
287
+ * treated atomically (compared/cloned as a unit, not walked structurally).
288
+ * Cross-realm safe, and brand-verified: a plain object spoofing a built-in
289
+ * tag via `Symbol.toStringTag` returns `false` and is walked structurally
290
+ * like any other plain object instead of crashing type-specific code.
291
+ *
292
+ * @param obj - The object to classify
293
+ * @param tag - The result of `Object.prototype.toString.call(obj)`, passed
294
+ * in so callers that already computed it don't pay twice
295
+ */
296
+ function isBuiltInObject(obj, tag) {
297
+ if (ArrayBuffer.isView(obj)) return true;
298
+ if (tag.endsWith("Array]")) return false;
299
+ return BUILT_IN_TAGS.has(tag) && hasBrand(obj, tag);
300
+ }
301
+
302
+ //#endregion
303
+ //#region src/utils/array/deep-equal.ts
304
+ const objProto = Object.prototype;
305
+ const objToString = objProto.toString;
306
+ const objHasOwn = objProto.hasOwnProperty;
307
+ /**
308
+ * SameValueZero: `===` plus NaN-equals-NaN (and `+0 === -0`, unlike
309
+ * `Object.is`). The numeric semantics `deepEqual` documents for primitives,
310
+ * applied consistently inside TypedArrays, Dates and Number wrappers.
311
+ */
312
+ function sameValueZero(a, b) {
313
+ return a === b || Number.isNaN(a) && Number.isNaN(b);
314
+ }
315
+ /**
316
+ * Performs a deep equality check between two values.
317
+ *
318
+ * This function compares values recursively, handling:
319
+ * - Primitives (with special handling for NaN)
320
+ * - Arrays (nested arrays supported)
321
+ * - Objects (plain objects and class instances)
322
+ * - TypedArrays (Uint8Array, Int32Array, etc.)
323
+ * - DataView
324
+ * - Maps and Sets
325
+ * - Dates and RegExp
326
+ * - Wrapper objects (Boolean, Number, String)
327
+ * - Circular references (detected and handled)
328
+ *
329
+ * @param a - The first value to compare
330
+ * @param b - The second value to compare
331
+ * @returns `true` if the values are deeply equal, `false` otherwise
332
+ *
333
+ * @example
334
+ * ```ts
335
+ * deepEqual([1, 2, 3], [1, 2, 3]); // true
336
+ * deepEqual({ a: 1, b: [2, 3] }, { a: 1, b: [2, 3] }); // true
337
+ * deepEqual(NaN, NaN); // true
338
+ * deepEqual([1, 2], [1, 2, 3]); // false
339
+ * ```
340
+ */
341
+ function deepEqual(a, b) {
342
+ return deepEqualInner(a, b, /* @__PURE__ */ new WeakMap());
343
+ }
344
+ /**
345
+ * Internal recursive function for deep equality comparison.
346
+ *
347
+ * @internal
348
+ */
349
+ function deepEqualInner(a, b, visited) {
350
+ if (a === b) return true;
351
+ const typeA = typeof a;
352
+ const typeB = typeof b;
353
+ if (typeA !== "object" || a === null || typeB !== "object" || b === null) {
354
+ if (typeA === "number" && typeB === "number") return Number.isNaN(a) && Number.isNaN(b);
355
+ return false;
356
+ }
357
+ const objA = a;
358
+ const objB = b;
359
+ let cachedBs = visited.get(objA);
360
+ if (cachedBs?.has(objB)) return true;
361
+ if (!cachedBs) {
362
+ cachedBs = /* @__PURE__ */ new WeakSet();
363
+ visited.set(objA, cachedBs);
364
+ }
365
+ cachedBs.add(objB);
366
+ if (ArrayBuffer.isView(objA) || ArrayBuffer.isView(objB)) {
367
+ if (!ArrayBuffer.isView(objA) || !ArrayBuffer.isView(objB)) return false;
368
+ const tagA = objToString.call(objA);
369
+ if (tagA !== objToString.call(objB)) return false;
370
+ if (tagA === "[object DataView]") {
371
+ const viewA = objA;
372
+ const viewB = objB;
373
+ if (viewA.byteLength !== viewB.byteLength) return false;
374
+ const len = viewA.byteLength;
375
+ for (let i = 0; i < len; i++) if (viewA.getUint8(i) !== viewB.getUint8(i)) return false;
376
+ return true;
377
+ }
378
+ const arrA = objA;
379
+ const arrB = objB;
380
+ const len = arrA.length;
381
+ if (len !== arrB.length) return false;
382
+ for (let i = 0; i < len; i++) if (!sameValueZero(arrA[i], arrB[i])) return false;
383
+ return true;
384
+ }
385
+ if (Array.isArray(objA) || Array.isArray(objB)) {
386
+ if (!Array.isArray(objA) || !Array.isArray(objB)) return false;
387
+ if (objA.length !== objB.length) return false;
388
+ const keysA = Reflect.ownKeys(objA).filter((key) => key !== "length");
389
+ const keysB = Reflect.ownKeys(objB).filter((key) => key !== "length");
390
+ if (keysA.length !== keysB.length) return false;
391
+ const arrA = objA;
392
+ const arrB = objB;
393
+ for (const key of keysA) {
394
+ if (!objHasOwn.call(objB, key)) return false;
395
+ if (!deepEqualInner(arrA[key], arrB[key], visited)) return false;
396
+ }
397
+ return true;
398
+ }
399
+ const tagA = objToString.call(objA);
400
+ const tagB = objToString.call(objB);
401
+ if (tagA !== tagB) return false;
402
+ const builtInA = isBuiltInObject(objA, tagA);
403
+ if (builtInA !== isBuiltInObject(objB, tagB)) return false;
404
+ if (!builtInA) {
405
+ if (isOpaqueExoticTag(tagA)) return objA === objB;
406
+ return comparePlainObjects(objA, objB, visited);
407
+ }
408
+ switch (tagA) {
409
+ case "[object Map]": {
410
+ const mapA = objA;
411
+ const mapB = objB;
412
+ if (mapA.size !== mapB.size) return false;
413
+ for (const [key, valA] of mapA) {
414
+ if (!mapB.has(key)) return false;
415
+ if (!deepEqualInner(valA, mapB.get(key), visited)) return false;
416
+ }
417
+ return true;
418
+ }
419
+ case "[object Set]": {
420
+ const setA = objA;
421
+ const setB = objB;
422
+ if (setA.size !== setB.size) return false;
423
+ for (const value of setA) if (!setB.has(value)) return false;
424
+ return true;
425
+ }
426
+ case "[object Date]": return sameValueZero(objA.getTime(), objB.getTime());
427
+ case "[object RegExp]": {
428
+ const regA = objA;
429
+ const regB = objB;
430
+ return regA.source === regB.source && regA.flags === regB.flags;
431
+ }
432
+ case "[object Boolean]":
433
+ case "[object Number]":
434
+ case "[object String]":
435
+ case "[object BigInt]": return sameValueZero(objA.valueOf(), objB.valueOf());
436
+ default: return objA === objB;
437
+ }
438
+ }
439
+ /**
440
+ * Plain / custom objects: compare own enumerable string keys + own symbol
441
+ * keys and their values. Used both as the final fallback and for objects
442
+ * whose built-in-looking tag failed brand verification.
443
+ */
444
+ function comparePlainObjects(objA, objB, visited) {
445
+ const recA = objA;
446
+ const recB = objB;
447
+ const stringKeysA = Object.getOwnPropertyNames(objA);
448
+ const stringKeysB = Object.getOwnPropertyNames(objB);
449
+ if (stringKeysA.length !== stringKeysB.length) return false;
450
+ const symbolKeysA = Object.getOwnPropertySymbols(objA);
451
+ const symbolKeysB = Object.getOwnPropertySymbols(objB);
452
+ if (symbolKeysA.length !== symbolKeysB.length) return false;
453
+ const symbolKeysBSet = new Set(symbolKeysB);
454
+ for (const key of stringKeysA) if (!objHasOwn.call(objB, key)) return false;
455
+ for (const key of symbolKeysA) if (!symbolKeysBSet.has(key)) return false;
456
+ for (const key of stringKeysA) if (!deepEqualInner(recA[key], recB[key], visited)) return false;
457
+ for (const key of symbolKeysA) if (!deepEqualInner(recA[key], recB[key], visited)) return false;
458
+ return true;
459
+ }
460
+
461
+ //#endregion
462
+ //#region src/utils/array/deep-omit.ts
463
+ /**
464
+ * Creates a deep copy of `value` with certain keys removed according to the
465
+ * provided rules.
466
+ *
467
+ * Walks the object tree and skips keys that match `ignoreKeys` /
468
+ * `ignoreKeyPredicate`. Built-in atomic types that `deepEqual` compares by
469
+ * value (Date, RegExp, Map, Set, TypedArrays, DataView) are cloned by type
470
+ * rather than walked, since their internal structure has no key filtering to
471
+ * apply. Types that `deepEqual` compares by reference (Error, ArrayBuffer,
472
+ * SharedArrayBuffer, Promise, WeakMap, WeakSet) are passed through by
473
+ * reference, so `deepEqualExcept(x, x)` stays reflexive. Cycles are
474
+ * preserved: a cycle `a → a` clones to `a' → a'`. Arrays retain sparse
475
+ * holes and all non-ignored own properties, including symbol keys.
476
+ *
477
+ * **Shared references.** Without `ignoreKeyPredicate`, an object reached
478
+ * via several paths dedupes to a single clone. With a predicate, each
479
+ * path gets its own clone, because the predicate may decide differently per
480
+ * path, so memoising the first path's result would be wrong. This is
481
+ * inherently exponential for diamond-shaped sharing (a node reachable
482
+ * via 2^n paths is cloned 2^n times); the walk aborts with a descriptive
483
+ * error after {@link PATH_SENSITIVE_VISIT_BUDGET} node visits instead of
484
+ * hanging the process.
485
+ *
486
+ * **Prototype-pollution safety.** `__proto__` and `constructor` keys
487
+ * encountered as *own* properties of the input (typical of `JSON.parse`
488
+ * output) are copied as inert data properties via `Object.defineProperty`
489
+ * so the clone graph cannot bleed into `Object.prototype`.
490
+ *
491
+ * **Class instances.** When the input is a class instance, the clone is
492
+ * built via `Object.create(proto)` so the prototype is preserved, but the
493
+ * constructor is NOT re-invoked, so class invariants enforced by the
494
+ * constructor are not re-checked. `deepOmit` is therefore best used for
495
+ * comparison/serialisation (`voEqualsExcept`, `deepEqualExcept`), not as
496
+ * a general-purpose clone for behaviour-carrying objects.
497
+ *
498
+ * @param value - The value to create a deep copy from
499
+ * @param options - Options specifying which keys to ignore
500
+ * @returns A deep copy of `value` with specified keys removed
501
+ */
502
+ function deepOmit(value, options) {
503
+ const visited = /* @__PURE__ */ new WeakMap();
504
+ return omitInternal(value, options, options.ignoreKeys ? new Set(options.ignoreKeys) : void 0, [], visited, options.ignoreKeyPredicate ? { visits: 0 } : void 0);
505
+ }
506
+ /**
507
+ * Maximum object-node visits for a single path-sensitive `deepOmit` walk.
508
+ * Per-path cloning expands exponentially on diamond-shaped sharing; past
509
+ * this bound the walk throws instead of hanging the process. One million
510
+ * visits covers any realistically tree-shaped input.
511
+ */
512
+ const PATH_SENSITIVE_VISIT_BUDGET = 1e6;
513
+ function omitInternal(value, options, ignoreKeys, path, visited, budget) {
514
+ if (value === null) return value;
515
+ if (typeof value !== "object") return value;
516
+ const obj = value;
517
+ if (visited.has(obj)) return visited.get(obj);
518
+ 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.`);
519
+ if (Array.isArray(obj)) {
520
+ const arr = obj;
521
+ const clone = new Array(arr.length);
522
+ const lengthDescriptor = Object.getOwnPropertyDescriptor(arr, "length");
523
+ visited.set(obj, clone);
524
+ for (const key of Reflect.ownKeys(arr)) {
525
+ if (key === "length") continue;
526
+ const segment = arrayPathSegment(key);
527
+ if (typeof segment !== "number" && shouldIgnoreKey(key, path, ignoreKeys, options)) continue;
528
+ const descriptor = Object.getOwnPropertyDescriptor(arr, key);
529
+ if (descriptor === void 0) continue;
530
+ path.push(segment);
531
+ if ("value" in descriptor) descriptor.value = omitInternal(descriptor.value, options, ignoreKeys, path, visited, budget);
532
+ Object.defineProperty(clone, key, descriptor);
533
+ path.pop();
534
+ }
535
+ if (lengthDescriptor !== void 0) Object.defineProperty(clone, "length", lengthDescriptor);
536
+ if (budget) visited.delete(obj);
537
+ return clone;
538
+ }
539
+ const tag = Object.prototype.toString.call(obj);
540
+ if (isBuiltInObject(obj, tag)) {
541
+ const builtInClone = cloneBuiltIn(obj, tag);
542
+ visited.set(obj, builtInClone);
543
+ return builtInClone;
544
+ }
545
+ if (isOpaqueExoticTag(tag)) {
546
+ visited.set(obj, obj);
547
+ return obj;
548
+ }
549
+ const clone = Object.create(Object.getPrototypeOf(obj));
550
+ visited.set(obj, clone);
551
+ const stringKeys = Object.getOwnPropertyNames(obj);
552
+ const symbolKeys = Object.getOwnPropertySymbols(obj);
553
+ for (const key of [...stringKeys, ...symbolKeys]) {
554
+ if (shouldIgnoreKey(key, path, ignoreKeys, options)) continue;
555
+ path.push(key);
556
+ assignOwn(clone, key, omitInternal(obj[key], options, ignoreKeys, path, visited, budget), Object.getOwnPropertyDescriptor(obj, key)?.enumerable ?? true);
557
+ path.pop();
558
+ }
559
+ if (budget) visited.delete(obj);
560
+ return clone;
561
+ }
562
+ function arrayPathSegment(key) {
563
+ if (typeof key === "symbol") return key;
564
+ const index = Number(key);
565
+ return Number.isInteger(index) && index >= 0 && index < 4294967295 && String(index) === key ? index : key;
566
+ }
567
+ /**
568
+ * Assigns `value` as an OWN data property on `target` without going through
569
+ * any inherited setter; critically, it never invokes the `__proto__` setter
570
+ * even when `key === "__proto__"`. Required to defeat prototype-pollution
571
+ * payloads that ship `__proto__` as a parsed-JSON own key.
572
+ */
573
+ function assignOwn(target, key, value, enumerable = true) {
574
+ Object.defineProperty(target, key, {
575
+ value,
576
+ writable: true,
577
+ enumerable,
578
+ configurable: true
579
+ });
580
+ }
581
+ /**
582
+ * Clones a built-in atomic type by case. Falls back to `structuredClone`
583
+ * for anything not explicitly enumerated (e.g. DataView, TypedArrays,
584
+ * Boolean/Number/String wrappers, all of which `deepEqual` compares by
585
+ * value). Types that `deepEqual` compares BY REFERENCE (the shared
586
+ * {@link REFERENCE_COMPARED_TAGS} set) are passed through by reference
587
+ * instead; cloning them would make `deepEqualExcept(x, x)` false.
588
+ * Promise/WeakMap/WeakSet additionally cannot be cloned at all
589
+ * (`structuredClone` rejects them).
590
+ */
591
+ function cloneBuiltIn(obj, tag) {
592
+ if (REFERENCE_COMPARED_TAGS.has(tag)) return obj;
593
+ switch (tag) {
594
+ case "[object Date]": return new Date(obj.getTime());
595
+ case "[object RegExp]": {
596
+ const re = obj;
597
+ const copy = new RegExp(re.source, re.flags);
598
+ copy.lastIndex = re.lastIndex;
599
+ return copy;
600
+ }
601
+ case "[object Map]": return new Map(obj);
602
+ case "[object Set]": return new Set(obj);
603
+ default: return structuredClone(obj);
604
+ }
605
+ }
606
+ function shouldIgnoreKey(key, path, ignoreKeys, options) {
607
+ if (ignoreKeys?.has(key)) return true;
608
+ if (options.ignoreKeyPredicate?.(key, path.slice())) return true;
609
+ return false;
610
+ }
611
+
612
+ //#endregion
613
+ //#region src/utils/array/deep-equal-except.ts
614
+ /**
615
+ * Performs a deep equality comparison between two values after omitting specified keys.
616
+ *
617
+ * This function first removes the specified keys from both values using `deepOmit`,
618
+ * then performs a deep equality check using `deepEqual`.
619
+ *
620
+ * @param a - The first value to compare
621
+ * @param b - The second value to compare
622
+ * @param options - Options specifying which keys to omit before comparison
623
+ * @returns `true` if the values are deeply equal after omitting specified keys, `false` otherwise
624
+ *
625
+ * @example
626
+ * ```ts
627
+ * const obj1 = { id: 1, name: "Alice", updatedAt: "2024-01-01" };
628
+ * const obj2 = { id: 2, name: "Alice", updatedAt: "2024-01-02" };
629
+ *
630
+ * deepEqualExcept(obj1, obj2, { ignoreKeys: ["id", "updatedAt"] }); // true
631
+ * ```
632
+ */
633
+ function deepEqualExcept(a, b, options) {
634
+ return deepEqual(deepOmit(a, options), deepOmit(b, options));
635
+ }
636
+
637
+ //#endregion
638
+ export { findPropertyDescriptor as a, isIntrinsicConstructorPrototype as c, builtInTagWithoutInvokingAccessors as i, mutableBuiltInTagWithoutInvokingAccessors as l, deepOmit as n, hasIntrinsicPrototypeChain as o, deepEqual as r, isBuiltInObject as s, deepEqualExcept as t };
639
+ //# sourceMappingURL=deep-equal-except.js.map