@shirudo/ddd-kit 3.0.0-rc.3 → 3.0.0-rc.5

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.
@@ -1,8 +1,652 @@
1
- import { L as assertNoHostileOwnProtoKey } from "./errors.js";
2
- import { c as isIntrinsicConstructorPrototype, i as builtInTagWithoutInvokingAccessors, l as mutableBuiltInTagWithoutInvokingAccessors, o as hasIntrinsicPrototypeChain, r as deepEqual, t as deepEqualExcept } from "./deep-equal-except.js";
1
+ import { J as assertNoHostileOwnProtoKey } from "./kit-errors.js";
3
2
  import { err, ok } from "@shirudo/result";
4
3
 
5
- //#region src/value-object/value-object.ts
4
+ //#region src/internal/structural/is-built-in.ts
5
+ /**
6
+ * Set of `Object.prototype.toString.call(x)` tags that the library treats
7
+ * as built-in atomic types. Members of this set are compared/cloned by
8
+ * reference (or with type-specific logic) rather than walked structurally.
9
+ *
10
+ * Detection is tag-based, since `Object.prototype.toString` gives the same
11
+ * answer across realms (an iframe's `Date` has the same tag as the main
12
+ * window's `Date`), and then brand-verified via internal-slot probes,
13
+ * because `Symbol.toStringTag` lets any plain object claim a built-in tag.
14
+ * The previous strategy also checked `globalThis[name] === constructor`
15
+ * and a `proto !== Object.prototype` heuristic; both broke for cross-realm
16
+ * objects and the latter additionally misclassified ordinary user classes
17
+ * as built-ins.
18
+ */
19
+ const BUILT_IN_TAGS = /* @__PURE__ */ new Set([
20
+ "[object Date]",
21
+ "[object RegExp]",
22
+ "[object Map]",
23
+ "[object Set]",
24
+ "[object WeakMap]",
25
+ "[object WeakSet]",
26
+ "[object Promise]",
27
+ "[object Error]",
28
+ "[object Boolean]",
29
+ "[object Number]",
30
+ "[object String]",
31
+ "[object BigInt]",
32
+ "[object ArrayBuffer]",
33
+ "[object SharedArrayBuffer]",
34
+ "[object DataView]"
35
+ ]);
36
+ function intrinsicGetter(proto, prop) {
37
+ const get = Object.getOwnPropertyDescriptor(proto, prop)?.get;
38
+ if (!get) throw new Error(`missing intrinsic getter for ${prop}`);
39
+ return get;
40
+ }
41
+ const dateGetTime = Date.prototype.getTime;
42
+ const mapSizeGet = intrinsicGetter(Map.prototype, "size");
43
+ const setSizeGet = intrinsicGetter(Set.prototype, "size");
44
+ const weakMapHas = WeakMap.prototype.has;
45
+ const weakSetHas = WeakSet.prototype.has;
46
+ const dataViewByteLengthGet = intrinsicGetter(DataView.prototype, "byteLength");
47
+ const arrayBufferByteLengthGet = intrinsicGetter(ArrayBuffer.prototype, "byteLength");
48
+ const sharedArrayBufferByteLengthGet = typeof SharedArrayBuffer === "undefined" ? void 0 : intrinsicGetter(SharedArrayBuffer.prototype, "byteLength");
49
+ const regExpSourceGet = intrinsicGetter(RegExp.prototype, "source");
50
+ const booleanValueOf = Boolean.prototype.valueOf;
51
+ const numberValueOf = Number.prototype.valueOf;
52
+ const stringValueOf = String.prototype.valueOf;
53
+ const bigIntValueOf = BigInt.prototype.valueOf;
54
+ const functionToString = Function.prototype.toString;
55
+ const PROBE_KEY = {};
56
+ const intrinsicConstructorSources = new Map([
57
+ "Object",
58
+ "Array",
59
+ "Date",
60
+ "RegExp",
61
+ "Map",
62
+ "Set",
63
+ "WeakMap",
64
+ "WeakSet",
65
+ "Promise",
66
+ "Error",
67
+ "EvalError",
68
+ "RangeError",
69
+ "ReferenceError",
70
+ "SyntaxError",
71
+ "TypeError",
72
+ "URIError",
73
+ "AggregateError",
74
+ "Boolean",
75
+ "Number",
76
+ "String",
77
+ "BigInt",
78
+ "ArrayBuffer",
79
+ "SharedArrayBuffer",
80
+ "DataView",
81
+ "Int8Array",
82
+ "Uint8Array",
83
+ "Uint8ClampedArray",
84
+ "Int16Array",
85
+ "Uint16Array",
86
+ "Int32Array",
87
+ "Uint32Array",
88
+ "Float32Array",
89
+ "Float64Array",
90
+ "BigInt64Array",
91
+ "BigUint64Array"
92
+ ].flatMap((name) => {
93
+ const intrinsic = Object.getOwnPropertyDescriptor(globalThis, name)?.value;
94
+ return typeof intrinsic === "function" ? [[name, functionToString.call(intrinsic)]] : [];
95
+ }));
96
+ const intrinsicConstructorSourceSet = new Set(intrinsicConstructorSources.values());
97
+ const ERROR_INTRINSIC_NAMES = [
98
+ "Error",
99
+ "EvalError",
100
+ "RangeError",
101
+ "ReferenceError",
102
+ "SyntaxError",
103
+ "TypeError",
104
+ "URIError",
105
+ "AggregateError"
106
+ ];
107
+ function isIntrinsicConstructorPrototype(prototype, expectedName) {
108
+ const constructorDescriptor = Object.getOwnPropertyDescriptor(prototype, "constructor");
109
+ const candidateConstructor = constructorDescriptor?.value;
110
+ if (constructorDescriptor === void 0 || !("value" in constructorDescriptor) || typeof candidateConstructor !== "function") return false;
111
+ let candidateSource;
112
+ try {
113
+ candidateSource = functionToString.call(candidateConstructor);
114
+ } catch {
115
+ return false;
116
+ }
117
+ const expectedSource = expectedName === void 0 ? void 0 : intrinsicConstructorSources.get(expectedName);
118
+ if (expectedSource !== void 0 && candidateSource !== expectedSource || expectedSource === void 0 && !intrinsicConstructorSourceSet.has(candidateSource)) return false;
119
+ const nameDescriptor = Object.getOwnPropertyDescriptor(candidateConstructor, "name");
120
+ const candidateName = nameDescriptor !== void 0 && "value" in nameDescriptor && typeof nameDescriptor.value === "string" ? nameDescriptor.value : void 0;
121
+ const intrinsicName = expectedName ?? candidateName;
122
+ const intrinsicSource = intrinsicName === void 0 ? void 0 : intrinsicConstructorSources.get(intrinsicName);
123
+ return candidateName === intrinsicName && intrinsicSource !== void 0 && candidateSource === intrinsicSource && Object.getOwnPropertyDescriptor(candidateConstructor, "prototype")?.value === prototype;
124
+ }
125
+ /**
126
+ * Accepts an intrinsic prototype, optionally behind transparent
127
+ * `Symbol.toStringTag` override layers. A user-defined subclass has its own
128
+ * non-native constructor and is therefore rejected before reaching the
129
+ * intrinsic prototype.
130
+ */
131
+ function hasIntrinsicPrototypeChain(value, expectedName) {
132
+ const visited = /* @__PURE__ */ new WeakSet();
133
+ let prototype = Object.getPrototypeOf(value);
134
+ while (prototype !== null && !visited.has(prototype)) {
135
+ visited.add(prototype);
136
+ if (Object.hasOwn(prototype, "constructor")) return isIntrinsicConstructorPrototype(prototype, expectedName);
137
+ const ownKeys = Reflect.ownKeys(prototype);
138
+ if (ownKeys.length !== 1 || ownKeys[0] !== Symbol.toStringTag) return false;
139
+ prototype = Object.getPrototypeOf(prototype);
140
+ }
141
+ return false;
142
+ }
143
+ /**
144
+ * Tags that `deepEqual` compares BY REFERENCE (its unhandled-built-in
145
+ * fallback) and that `deepOmit` must therefore ALIAS rather than clone:
146
+ * a clone would break `deepEqualExcept(x, x)` reflexivity. Single source
147
+ * of truth so the two modules cannot drift: if `deepEqual` ever learns a
148
+ * by-value comparison for one of these, remove it here and add a clone
149
+ * case in `deepOmit`'s `cloneBuiltIn` in the same change.
150
+ */
151
+ const REFERENCE_COMPARED_TAGS = /* @__PURE__ */ new Set([
152
+ "[object Error]",
153
+ "[object ArrayBuffer]",
154
+ "[object SharedArrayBuffer]",
155
+ "[object Promise]",
156
+ "[object WeakMap]",
157
+ "[object WeakSet]"
158
+ ]);
159
+ /**
160
+ * Intrinsic tags of OPAQUE exotics: objects whose internal state no
161
+ * structural walk can observe (boxed Symbols, generator objects,
162
+ * WeakRefs, FinalizationRegistry handles). `deepEqual` compares them by
163
+ * identity and `deepOmit` passes them through by reference; treating
164
+ * them as (empty) plain objects would make ALL such exotics equal to
165
+ * each other. Deliberately a curated INTRINSIC list, not "every unknown
166
+ * tag": a user class exposing its own `Symbol.toStringTag` (e.g.
167
+ * "Money") keeps structural comparison, and a plain object spoofing one
168
+ * of these intrinsic tags gets the identity semantics of the thing it
169
+ * claims to be.
170
+ */
171
+ const OPAQUE_EXOTIC_TAGS = /* @__PURE__ */ new Set([
172
+ "[object Symbol]",
173
+ "[object Generator]",
174
+ "[object AsyncGenerator]",
175
+ "[object WeakRef]",
176
+ "[object FinalizationRegistry]"
177
+ ]);
178
+ /** True when `tag` names an opaque intrinsic; see {@link OPAQUE_EXOTIC_TAGS}. */
179
+ function isOpaqueExoticTag(tag) {
180
+ return OPAQUE_EXOTIC_TAGS.has(tag);
181
+ }
182
+ function findPropertyDescriptor(value, key) {
183
+ const visited = /* @__PURE__ */ new WeakSet();
184
+ let current = value;
185
+ while (current !== null && !visited.has(current)) {
186
+ visited.add(current);
187
+ const descriptor = Object.getOwnPropertyDescriptor(current, key);
188
+ if (descriptor !== void 0) return descriptor;
189
+ current = Object.getPrototypeOf(current);
190
+ }
191
+ }
192
+ function builtInTagWithoutInvokingAccessors(value) {
193
+ if (ArrayBuffer.isView(value)) return hasBrand(value, "[object DataView]") ? "[object DataView]" : "[object TypedArray]";
194
+ const descriptor = findPropertyDescriptor(value, Symbol.toStringTag);
195
+ if (descriptor !== void 0 && !("value" in descriptor)) return builtInTagFromBrand(value);
196
+ const tag = Object.prototype.toString.call(value);
197
+ if (BUILT_IN_TAGS.has(tag) && hasBrand(value, tag)) return tag;
198
+ return descriptor === void 0 ? void 0 : builtInTagFromBrand(value);
199
+ }
200
+ function builtInTagFromBrand(value) {
201
+ if (hasBrand(value, "[object Date]")) return "[object Date]";
202
+ if (hasBrand(value, "[object RegExp]")) return "[object RegExp]";
203
+ if (hasBrand(value, "[object Map]")) return "[object Map]";
204
+ if (hasBrand(value, "[object Set]")) return "[object Set]";
205
+ if (hasBrand(value, "[object WeakMap]")) return "[object WeakMap]";
206
+ if (hasBrand(value, "[object WeakSet]")) return "[object WeakSet]";
207
+ if (hasBrand(value, "[object DataView]")) return "[object DataView]";
208
+ if (hasBrand(value, "[object ArrayBuffer]")) return "[object ArrayBuffer]";
209
+ if (hasBrand(value, "[object SharedArrayBuffer]")) return "[object SharedArrayBuffer]";
210
+ if (hasBrand(value, "[object Boolean]")) return "[object Boolean]";
211
+ if (hasBrand(value, "[object Number]")) return "[object Number]";
212
+ if (hasBrand(value, "[object String]")) return "[object String]";
213
+ if (hasBrand(value, "[object BigInt]")) return "[object BigInt]";
214
+ if (hasNativePrototype(value, "Promise")) return "[object Promise]";
215
+ if (hasNativePrototype(value, "Error")) return "[object Error]";
216
+ }
217
+ function hasNativePrototype(value, expectedName) {
218
+ const visited = /* @__PURE__ */ new WeakSet();
219
+ let prototype = Object.getPrototypeOf(value);
220
+ while (prototype !== null && !visited.has(prototype)) {
221
+ visited.add(prototype);
222
+ if (isIntrinsicConstructorPrototype(prototype, expectedName)) return true;
223
+ prototype = Object.getPrototypeOf(prototype);
224
+ }
225
+ return false;
226
+ }
227
+ /**
228
+ * Verifies that `obj` genuinely is the type its tag claims, via an
229
+ * internal-slot probe. Promise and Error have no side-effect-free standard
230
+ * probe, so their visible tags remain conservative; masked instances are
231
+ * identified separately through their native prototype chain.
232
+ */
233
+ function hasBrand(obj, tag) {
234
+ try {
235
+ switch (tag) {
236
+ case "[object Date]":
237
+ dateGetTime.call(obj);
238
+ return true;
239
+ case "[object RegExp]":
240
+ regExpSourceGet.call(obj);
241
+ return true;
242
+ case "[object Map]":
243
+ mapSizeGet.call(obj);
244
+ return true;
245
+ case "[object Set]":
246
+ setSizeGet.call(obj);
247
+ return true;
248
+ case "[object WeakMap]":
249
+ weakMapHas.call(obj, PROBE_KEY);
250
+ return true;
251
+ case "[object WeakSet]":
252
+ weakSetHas.call(obj, PROBE_KEY);
253
+ return true;
254
+ case "[object DataView]":
255
+ dataViewByteLengthGet.call(obj);
256
+ return true;
257
+ case "[object ArrayBuffer]":
258
+ arrayBufferByteLengthGet.call(obj);
259
+ return true;
260
+ case "[object SharedArrayBuffer]":
261
+ if (!sharedArrayBufferByteLengthGet) return false;
262
+ sharedArrayBufferByteLengthGet.call(obj);
263
+ return true;
264
+ case "[object Boolean]":
265
+ booleanValueOf.call(obj);
266
+ return true;
267
+ case "[object Number]":
268
+ numberValueOf.call(obj);
269
+ return true;
270
+ case "[object String]":
271
+ stringValueOf.call(obj);
272
+ return true;
273
+ case "[object BigInt]":
274
+ bigIntValueOf.call(obj);
275
+ return true;
276
+ case "[object Promise]": return hasNativePrototype(obj, "Promise");
277
+ case "[object Error]": return ERROR_INTRINSIC_NAMES.some((name) => hasNativePrototype(obj, name));
278
+ default: return false;
279
+ }
280
+ } catch {
281
+ return false;
282
+ }
283
+ }
284
+ /**
285
+ * Returns `true` when `obj` is a built-in JavaScript type that should be
286
+ * treated atomically (compared/cloned as a unit, not walked structurally).
287
+ * Cross-realm safe, and brand-verified: a plain object spoofing a built-in
288
+ * tag via `Symbol.toStringTag` returns `false` and is walked structurally
289
+ * like any other plain object instead of crashing type-specific code.
290
+ *
291
+ * @param obj - The object to classify
292
+ * @param tag - The result of `Object.prototype.toString.call(obj)`, passed
293
+ * in so callers that already computed it don't pay twice
294
+ */
295
+ function isBuiltInObject(obj, tag) {
296
+ if (ArrayBuffer.isView(obj)) return true;
297
+ if (tag.endsWith("Array]")) return false;
298
+ return BUILT_IN_TAGS.has(tag) && hasBrand(obj, tag);
299
+ }
300
+ /**
301
+ * Brand-verified `WeakMap` check that holds across realms. `instanceof
302
+ * WeakMap` binds to the constructor of one realm, so a WeakMap from a `vm`
303
+ * context or an iframe reads as a foreign value. The internal-slot probe
304
+ * answers the same in every realm, and a plain object cannot spoof it
305
+ * through `Symbol.toStringTag`.
306
+ */
307
+ function isWeakMap(value) {
308
+ return typeof value === "object" && value !== null && hasBrand(value, "[object WeakMap]");
309
+ }
310
+
311
+ //#endregion
312
+ //#region src/internal/structural/deep-equal.ts
313
+ const objProto = Object.prototype;
314
+ const objToString = objProto.toString;
315
+ const objHasOwn = objProto.hasOwnProperty;
316
+ /**
317
+ * SameValueZero: `===` plus NaN-equals-NaN (and `+0 === -0`, unlike
318
+ * `Object.is`). The numeric semantics `deepEqual` documents for primitives,
319
+ * applied consistently inside TypedArrays, Dates and Number wrappers.
320
+ */
321
+ function sameValueZero(a, b) {
322
+ return a === b || Number.isNaN(a) && Number.isNaN(b);
323
+ }
324
+ /**
325
+ * Performs a deep equality check between two values.
326
+ *
327
+ * This function compares values recursively, handling:
328
+ * - Primitives (with special handling for NaN)
329
+ * - Arrays (nested arrays supported)
330
+ * - Objects (plain objects and class instances)
331
+ * - TypedArrays (Uint8Array, Int32Array, etc.)
332
+ * - DataView
333
+ * - Maps and Sets
334
+ * - Dates and RegExp
335
+ * - Wrapper objects (Boolean, Number, String)
336
+ * - Circular references (detected and handled)
337
+ *
338
+ * @param a - The first value to compare
339
+ * @param b - The second value to compare
340
+ * @returns `true` if the values are deeply equal, `false` otherwise
341
+ *
342
+ * @example
343
+ * ```ts
344
+ * deepEqual([1, 2, 3], [1, 2, 3]); // true
345
+ * deepEqual({ a: 1, b: [2, 3] }, { a: 1, b: [2, 3] }); // true
346
+ * deepEqual(NaN, NaN); // true
347
+ * deepEqual([1, 2], [1, 2, 3]); // false
348
+ * ```
349
+ */
350
+ function deepEqual(a, b) {
351
+ return deepEqualInner(a, b, /* @__PURE__ */ new WeakMap());
352
+ }
353
+ /**
354
+ * Internal recursive function for deep equality comparison.
355
+ *
356
+ * @internal
357
+ */
358
+ function deepEqualInner(a, b, visited) {
359
+ if (a === b) return true;
360
+ const typeA = typeof a;
361
+ const typeB = typeof b;
362
+ if (typeA !== "object" || a === null || typeB !== "object" || b === null) {
363
+ if (typeA === "number" && typeB === "number") return Number.isNaN(a) && Number.isNaN(b);
364
+ return false;
365
+ }
366
+ const objA = a;
367
+ const objB = b;
368
+ let cachedBs = visited.get(objA);
369
+ if (cachedBs?.has(objB)) return true;
370
+ if (!cachedBs) {
371
+ cachedBs = /* @__PURE__ */ new WeakSet();
372
+ visited.set(objA, cachedBs);
373
+ }
374
+ cachedBs.add(objB);
375
+ if (ArrayBuffer.isView(objA) || ArrayBuffer.isView(objB)) {
376
+ if (!ArrayBuffer.isView(objA) || !ArrayBuffer.isView(objB)) return false;
377
+ const tagA = objToString.call(objA);
378
+ if (tagA !== objToString.call(objB)) return false;
379
+ if (tagA === "[object DataView]") {
380
+ const viewA = objA;
381
+ const viewB = objB;
382
+ if (viewA.byteLength !== viewB.byteLength) return false;
383
+ const len = viewA.byteLength;
384
+ for (let i = 0; i < len; i++) if (viewA.getUint8(i) !== viewB.getUint8(i)) return false;
385
+ return true;
386
+ }
387
+ const arrA = objA;
388
+ const arrB = objB;
389
+ const len = arrA.length;
390
+ if (len !== arrB.length) return false;
391
+ for (let i = 0; i < len; i++) if (!sameValueZero(arrA[i], arrB[i])) return false;
392
+ return true;
393
+ }
394
+ if (Array.isArray(objA) || Array.isArray(objB)) {
395
+ if (!Array.isArray(objA) || !Array.isArray(objB)) return false;
396
+ if (objA.length !== objB.length) return false;
397
+ const keysA = Reflect.ownKeys(objA).filter((key) => key !== "length");
398
+ const keysB = Reflect.ownKeys(objB).filter((key) => key !== "length");
399
+ if (keysA.length !== keysB.length) return false;
400
+ const arrA = objA;
401
+ const arrB = objB;
402
+ for (const key of keysA) {
403
+ if (!objHasOwn.call(objB, key)) return false;
404
+ if (!deepEqualInner(arrA[key], arrB[key], visited)) return false;
405
+ }
406
+ return true;
407
+ }
408
+ const tagA = objToString.call(objA);
409
+ const tagB = objToString.call(objB);
410
+ if (tagA !== tagB) return false;
411
+ const builtInA = isBuiltInObject(objA, tagA);
412
+ if (builtInA !== isBuiltInObject(objB, tagB)) return false;
413
+ if (!builtInA) {
414
+ if (isOpaqueExoticTag(tagA)) return objA === objB;
415
+ return comparePlainObjects(objA, objB, visited);
416
+ }
417
+ switch (tagA) {
418
+ case "[object Map]": {
419
+ const mapA = objA;
420
+ const mapB = objB;
421
+ if (mapA.size !== mapB.size) return false;
422
+ for (const [key, valA] of mapA) {
423
+ if (!mapB.has(key)) return false;
424
+ if (!deepEqualInner(valA, mapB.get(key), visited)) return false;
425
+ }
426
+ return true;
427
+ }
428
+ case "[object Set]": {
429
+ const setA = objA;
430
+ const setB = objB;
431
+ if (setA.size !== setB.size) return false;
432
+ for (const value of setA) if (!setB.has(value)) return false;
433
+ return true;
434
+ }
435
+ case "[object Date]": return sameValueZero(objA.getTime(), objB.getTime());
436
+ case "[object RegExp]": {
437
+ const regA = objA;
438
+ const regB = objB;
439
+ return regA.source === regB.source && regA.flags === regB.flags;
440
+ }
441
+ case "[object Boolean]":
442
+ case "[object Number]":
443
+ case "[object String]":
444
+ case "[object BigInt]": return sameValueZero(objA.valueOf(), objB.valueOf());
445
+ default: return objA === objB;
446
+ }
447
+ }
448
+ /**
449
+ * Plain / custom objects: compare own enumerable string keys + own symbol
450
+ * keys and their values. Used both as the final fallback and for objects
451
+ * whose built-in-looking tag failed brand verification.
452
+ */
453
+ function comparePlainObjects(objA, objB, visited) {
454
+ const recA = objA;
455
+ const recB = objB;
456
+ const stringKeysA = Object.getOwnPropertyNames(objA);
457
+ const stringKeysB = Object.getOwnPropertyNames(objB);
458
+ if (stringKeysA.length !== stringKeysB.length) return false;
459
+ const symbolKeysA = Object.getOwnPropertySymbols(objA);
460
+ const symbolKeysB = Object.getOwnPropertySymbols(objB);
461
+ if (symbolKeysA.length !== symbolKeysB.length) return false;
462
+ const symbolKeysBSet = new Set(symbolKeysB);
463
+ for (const key of stringKeysA) if (!objHasOwn.call(objB, key)) return false;
464
+ for (const key of symbolKeysA) if (!symbolKeysBSet.has(key)) return false;
465
+ for (const key of stringKeysA) if (!deepEqualInner(recA[key], recB[key], visited)) return false;
466
+ for (const key of symbolKeysA) if (!deepEqualInner(recA[key], recB[key], visited)) return false;
467
+ return true;
468
+ }
469
+
470
+ //#endregion
471
+ //#region src/internal/structural/deep-omit.ts
472
+ /**
473
+ * Creates a deep copy of `value` with certain keys removed according to the
474
+ * provided rules.
475
+ *
476
+ * Walks the object tree and skips keys that match `ignoreKeys` /
477
+ * `ignoreKeyPredicate`. Built-in atomic types that `deepEqual` compares by
478
+ * value (Date, RegExp, Map, Set, TypedArrays, DataView) are cloned by type
479
+ * rather than walked, since their internal structure has no key filtering to
480
+ * apply. Types that `deepEqual` compares by reference (Error, ArrayBuffer,
481
+ * SharedArrayBuffer, Promise, WeakMap, WeakSet) are passed through by
482
+ * reference, so `deepEqualExcept(x, x)` stays reflexive. Cycles are
483
+ * preserved: a cycle `a → a` clones to `a' → a'`. Arrays retain sparse
484
+ * holes and all non-ignored own properties, including symbol keys.
485
+ *
486
+ * **Shared references.** Without `ignoreKeyPredicate`, an object reached
487
+ * via several paths dedupes to a single clone. With a predicate, each
488
+ * path gets its own clone, because the predicate may decide differently per
489
+ * path, so memoising the first path's result would be wrong. This is
490
+ * inherently exponential for diamond-shaped sharing (a node reachable
491
+ * via 2^n paths is cloned 2^n times); the walk aborts with a descriptive
492
+ * error after {@link PATH_SENSITIVE_VISIT_BUDGET} node visits instead of
493
+ * hanging the process.
494
+ *
495
+ * **Prototype-pollution safety.** `__proto__` and `constructor` keys
496
+ * encountered as *own* properties of the input (typical of `JSON.parse`
497
+ * output) are copied as inert data properties via `Object.defineProperty`
498
+ * so the clone graph cannot bleed into `Object.prototype`.
499
+ *
500
+ * **Class instances.** When the input is a class instance, the clone is
501
+ * built via `Object.create(proto)` so the prototype is preserved, but the
502
+ * constructor is NOT re-invoked, so class invariants enforced by the
503
+ * constructor are not re-checked. `deepOmit` is therefore best used for
504
+ * comparison/serialisation (`voEqualsExcept`, `deepEqualExcept`), not as
505
+ * a general-purpose clone for behaviour-carrying objects.
506
+ *
507
+ * @param value - The value to create a deep copy from
508
+ * @param options - Options specifying which keys to ignore
509
+ * @returns A deep copy of `value` with specified keys removed
510
+ */
511
+ function deepOmit(value, options) {
512
+ const visited = /* @__PURE__ */ new WeakMap();
513
+ return omitInternal(value, options, options.ignoreKeys ? new Set(options.ignoreKeys) : void 0, [], visited, options.ignoreKeyPredicate ? { visits: 0 } : void 0);
514
+ }
515
+ /**
516
+ * Maximum object-node visits for a single path-sensitive `deepOmit` walk.
517
+ * Per-path cloning expands exponentially on diamond-shaped sharing; past
518
+ * this bound the walk throws instead of hanging the process. One million
519
+ * visits covers any realistically tree-shaped input.
520
+ */
521
+ const PATH_SENSITIVE_VISIT_BUDGET = 1e6;
522
+ function omitInternal(value, options, ignoreKeys, path, visited, budget) {
523
+ if (value === null) return value;
524
+ if (typeof value !== "object") return value;
525
+ const obj = value;
526
+ if (visited.has(obj)) return visited.get(obj);
527
+ 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.`);
528
+ if (Array.isArray(obj)) {
529
+ const arr = obj;
530
+ const clone = new Array(arr.length);
531
+ const lengthDescriptor = Object.getOwnPropertyDescriptor(arr, "length");
532
+ visited.set(obj, clone);
533
+ for (const key of Reflect.ownKeys(arr)) {
534
+ if (key === "length") continue;
535
+ const segment = arrayPathSegment(key);
536
+ if (typeof segment !== "number" && shouldIgnoreKey(key, path, ignoreKeys, options)) continue;
537
+ const descriptor = Object.getOwnPropertyDescriptor(arr, key);
538
+ if (descriptor === void 0) continue;
539
+ path.push(segment);
540
+ if ("value" in descriptor) descriptor.value = omitInternal(descriptor.value, options, ignoreKeys, path, visited, budget);
541
+ Object.defineProperty(clone, key, descriptor);
542
+ path.pop();
543
+ }
544
+ if (lengthDescriptor !== void 0) Object.defineProperty(clone, "length", lengthDescriptor);
545
+ if (budget) visited.delete(obj);
546
+ return clone;
547
+ }
548
+ const tag = Object.prototype.toString.call(obj);
549
+ if (isBuiltInObject(obj, tag)) {
550
+ const builtInClone = cloneBuiltIn(obj, tag);
551
+ visited.set(obj, builtInClone);
552
+ return builtInClone;
553
+ }
554
+ if (isOpaqueExoticTag(tag)) {
555
+ visited.set(obj, obj);
556
+ return obj;
557
+ }
558
+ const clone = Object.create(Object.getPrototypeOf(obj));
559
+ visited.set(obj, clone);
560
+ const stringKeys = Object.getOwnPropertyNames(obj);
561
+ const symbolKeys = Object.getOwnPropertySymbols(obj);
562
+ for (const key of [...stringKeys, ...symbolKeys]) {
563
+ if (shouldIgnoreKey(key, path, ignoreKeys, options)) continue;
564
+ path.push(key);
565
+ assignOwn(clone, key, omitInternal(obj[key], options, ignoreKeys, path, visited, budget), Object.getOwnPropertyDescriptor(obj, key)?.enumerable ?? true);
566
+ path.pop();
567
+ }
568
+ if (budget) visited.delete(obj);
569
+ return clone;
570
+ }
571
+ function arrayPathSegment(key) {
572
+ if (typeof key === "symbol") return key;
573
+ const index = Number(key);
574
+ return Number.isInteger(index) && index >= 0 && index < 4294967295 && String(index) === key ? index : key;
575
+ }
576
+ /**
577
+ * Assigns `value` as an OWN data property on `target` without going through
578
+ * any inherited setter; critically, it never invokes the `__proto__` setter
579
+ * even when `key === "__proto__"`. Required to defeat prototype-pollution
580
+ * payloads that ship `__proto__` as a parsed-JSON own key.
581
+ */
582
+ function assignOwn(target, key, value, enumerable = true) {
583
+ Object.defineProperty(target, key, {
584
+ value,
585
+ writable: true,
586
+ enumerable,
587
+ configurable: true
588
+ });
589
+ }
590
+ /**
591
+ * Clones a built-in atomic type by case. Falls back to `structuredClone`
592
+ * for anything not explicitly enumerated (e.g. DataView, TypedArrays,
593
+ * Boolean/Number/String wrappers, all of which `deepEqual` compares by
594
+ * value). Types that `deepEqual` compares BY REFERENCE (the shared
595
+ * {@link REFERENCE_COMPARED_TAGS} set) are passed through by reference
596
+ * instead; cloning them would make `deepEqualExcept(x, x)` false.
597
+ * Promise/WeakMap/WeakSet additionally cannot be cloned at all
598
+ * (`structuredClone` rejects them).
599
+ */
600
+ function cloneBuiltIn(obj, tag) {
601
+ if (REFERENCE_COMPARED_TAGS.has(tag)) return obj;
602
+ switch (tag) {
603
+ case "[object Date]": return new Date(obj.getTime());
604
+ case "[object RegExp]": {
605
+ const re = obj;
606
+ const copy = new RegExp(re.source, re.flags);
607
+ copy.lastIndex = re.lastIndex;
608
+ return copy;
609
+ }
610
+ case "[object Map]": return new Map(obj);
611
+ case "[object Set]": return new Set(obj);
612
+ default: return structuredClone(obj);
613
+ }
614
+ }
615
+ function shouldIgnoreKey(key, path, ignoreKeys, options) {
616
+ if (ignoreKeys?.has(key)) return true;
617
+ if (options.ignoreKeyPredicate?.(key, path.slice())) return true;
618
+ return false;
619
+ }
620
+
621
+ //#endregion
622
+ //#region src/internal/structural/deep-equal-except.ts
623
+ /**
624
+ * Performs a deep equality comparison between two values after omitting specified keys.
625
+ *
626
+ * This function first removes the specified keys from both values using `deepOmit`,
627
+ * then performs a deep equality check using `deepEqual`.
628
+ *
629
+ * @param a - The first value to compare
630
+ * @param b - The second value to compare
631
+ * @param options - Options specifying which keys to omit before comparison
632
+ * @returns `true` if the values are deeply equal after omitting specified keys, `false` otherwise
633
+ *
634
+ * @example
635
+ * ```ts
636
+ * const obj1 = { id: 1, name: "Alice", updatedAt: "2024-01-01" };
637
+ * const obj2 = { id: 2, name: "Alice", updatedAt: "2024-01-02" };
638
+ *
639
+ * deepEqualExcept(obj1, obj2, { ignoreKeys: ["id", "updatedAt"] }); // true
640
+ * ```
641
+ */
642
+ function deepEqualExcept(a, b, options) {
643
+ const prunedA = deepOmit(a, options);
644
+ const prunedB = deepOmit(b, options);
645
+ return deepEqual(prunedA, prunedB);
646
+ }
647
+
648
+ //#endregion
649
+ //#region src/domain/value-object/value-object.ts
6
650
  /**
7
651
  * `Object.freeze` does not protect internal slots: a frozen Date still
8
652
  * accepts `setTime`, a frozen Map still accepts `set`. To make the
@@ -50,12 +694,14 @@ const shadowDescriptor = {
50
694
  enumerable: false,
51
695
  configurable: false
52
696
  };
697
+ /** Returns whether the shadows were installed. */
53
698
  function shadowMutators(obj, typeName, methods) {
54
- if (!Object.isExtensible(obj)) return;
699
+ if (!Object.isExtensible(obj)) return false;
55
700
  for (const method of methods) {
56
701
  shadowDescriptor.value = mutationThrower(typeName, method);
57
702
  Object.defineProperty(obj, method, shadowDescriptor);
58
703
  }
704
+ return true;
59
705
  }
60
706
  /**
61
707
  * Deep freezes an object and all its nested properties recursively, then
@@ -79,43 +725,80 @@ function shadowMutators(obj, typeName, methods) {
79
725
  * until the list is updated. Treat the mutator blocking as a guard rail,
80
726
  * not a security boundary.
81
727
  *
82
- * Limitation: ArrayBuffer views (TypedArrays, DataView) are passed through
83
- * unfrozen, because the spec forbids freezing a view with elements, and
84
- * freezing cannot protect the underlying buffer. Their contents remain mutable.
85
- */
86
- function deepFreeze(obj, visited = /* @__PURE__ */ new WeakSet()) {
87
- if (obj === null || typeof obj !== "object") return obj;
88
- if (ArrayBuffer.isView(obj)) return obj;
89
- if (visited.has(obj)) return obj;
90
- visited.add(obj);
91
- const mutableBuiltInTag = mutableBuiltInTagWithoutInvokingAccessors(obj);
92
- if (mutableBuiltInTag !== void 0) {
93
- if (mutableBuiltInTag === "[object Date]") shadowMutators(obj, "Date", DATE_MUTATORS);
94
- else if (mutableBuiltInTag === "[object Map]") {
728
+ * Two kinds of built-in pass through unfrozen. ArrayBuffer views
729
+ * (TypedArrays, DataView): the spec forbids freezing a view with elements,
730
+ * and a freeze cannot protect the underlying buffer, so their contents stay
731
+ * mutable. RegExp: pattern and flags live in immutable internal slots, and
732
+ * the only own data property, `lastIndex`, is scan state that every global
733
+ * or sticky match writes. A frozen RegExp protects nothing and throws on
734
+ * the first such match, so the RegExp keeps matching instead. A view or a
735
+ * RegExp passes through whole: an expando property on it stays open, and
736
+ * the walk does not enter its subtree.
737
+ */
738
+ const DEEP_FROZEN = /* @__PURE__ */ new WeakSet();
739
+ const KIT_SHADOWED = /* @__PURE__ */ new WeakSet();
740
+ function deepFreeze(obj) {
741
+ const walk = {
742
+ cyclic: false,
743
+ sealed: [],
744
+ done: /* @__PURE__ */ new Map(),
745
+ inProgress: /* @__PURE__ */ new Set()
746
+ };
747
+ freezeDeep(obj, walk);
748
+ if (!walk.cyclic) for (const object of walk.sealed) DEEP_FROZEN.add(object);
749
+ return obj;
750
+ }
751
+ /** Freezes `obj` and its subtree; returns whether the subtree is sealed. */
752
+ function freezeDeep(obj, walk) {
753
+ if (obj === null || typeof obj !== "object") return true;
754
+ if (DEEP_FROZEN.has(obj)) return true;
755
+ if (ArrayBuffer.isView(obj)) return true;
756
+ const finished = walk.done.get(obj);
757
+ if (finished !== void 0) return finished;
758
+ if (walk.inProgress.has(obj)) {
759
+ walk.cyclic = true;
760
+ return true;
761
+ }
762
+ const builtInTag = builtInTagWithoutInvokingAccessors(obj);
763
+ if (builtInTag === "[object RegExp]") return true;
764
+ walk.inProgress.add(obj);
765
+ let sealed = true;
766
+ if (builtInTag === "[object Date]" || builtInTag === "[object Map]" || builtInTag === "[object Set]") {
767
+ let shadowed = KIT_SHADOWED.has(obj);
768
+ if (builtInTag === "[object Date]") shadowed = shadowMutators(obj, "Date", DATE_MUTATORS) || shadowed;
769
+ else if (builtInTag === "[object Map]") {
95
770
  for (const [key, value] of obj) {
96
- deepFreeze(key, visited);
97
- deepFreeze(value, visited);
771
+ if (!freezeDeep(key, walk)) sealed = false;
772
+ if (!freezeDeep(value, walk)) sealed = false;
98
773
  }
99
- shadowMutators(obj, "Map", [
774
+ shadowed = shadowMutators(obj, "Map", [
100
775
  "set",
101
776
  "delete",
102
777
  "clear"
103
- ]);
104
- } else if (mutableBuiltInTag === "[object Set]") {
105
- for (const member of obj) deepFreeze(member, visited);
106
- shadowMutators(obj, "Set", [
778
+ ]) || shadowed;
779
+ } else if (builtInTag === "[object Set]") {
780
+ for (const member of obj) if (!freezeDeep(member, walk)) sealed = false;
781
+ shadowed = shadowMutators(obj, "Set", [
107
782
  "add",
108
783
  "delete",
109
784
  "clear"
110
- ]);
785
+ ]) || shadowed;
111
786
  }
787
+ if (shadowed) KIT_SHADOWED.add(obj);
788
+ else sealed = false;
112
789
  }
113
790
  const keys = Reflect.ownKeys(obj);
114
791
  for (const key of keys) {
115
792
  const value = obj[key];
116
- if (value !== null && typeof value === "object") deepFreeze(value, visited);
793
+ if (value !== null && typeof value === "object") {
794
+ if (!freezeDeep(value, walk)) sealed = false;
795
+ }
117
796
  }
118
- return Object.freeze(obj);
797
+ Object.freeze(obj);
798
+ walk.inProgress.delete(obj);
799
+ walk.done.set(obj, sealed);
800
+ if (sealed) walk.sealed.push(obj);
801
+ return sealed;
119
802
  }
120
803
  /**
121
804
  * Deep clone used by `vo()` and the `ValueObject` constructor.
@@ -452,7 +1135,54 @@ var ValueObject = class {
452
1135
  };
453
1136
 
454
1137
  //#endregion
455
- //#region src/aggregate/clock.ts
1138
+ //#region src/internal/cooperative-brand.ts
1139
+ /**
1140
+ * A cooperative brand marks an object that a kit constructor produced, so
1141
+ * a second loaded copy of the kit (duplicate npm dependency, dual CJS/ESM
1142
+ * load, plugin bundle) recognizes it. A module-private WeakSet cannot do
1143
+ * that: it is bound to one loaded copy. The brand is a `Symbol.for` key
1144
+ * with the value `true`. The property is non-enumerable, so it never
1145
+ * leaks into spreads, JSON, or equality, and non-writable and
1146
+ * non-configurable, so nothing edits it after the stamp.
1147
+ *
1148
+ * The probe reads the brand as an OWN property with exactly these
1149
+ * attributes and requires the carrier to be frozen. An object that
1150
+ * inherits a branded object through its prototype can carry mutable own
1151
+ * overrides; a branded object that is still open can change after the
1152
+ * stamp. Neither is what the constructor produced. Every stamp site
1153
+ * therefore freezes the carrier right after {@link stampCooperativeBrand}.
1154
+ *
1155
+ * The brand is forgeable BY DESIGN. It catches accidental hand-rolled
1156
+ * literals; it is not a security boundary against code in the same
1157
+ * process that fakes it on purpose.
1158
+ *
1159
+ * Internal utility (not exported from the package barrels).
1160
+ */
1161
+ function stampCooperativeBrand(target, brand) {
1162
+ Object.defineProperty(target, brand, {
1163
+ value: true,
1164
+ enumerable: false,
1165
+ writable: false,
1166
+ configurable: false
1167
+ });
1168
+ }
1169
+ /**
1170
+ * Whether `value` carries `brand` as an own, non-enumerable, non-writable,
1171
+ * non-configurable `true` and is frozen. A Proxy trap that throws reads
1172
+ * as unbranded.
1173
+ */
1174
+ function hasCooperativeBrand(value, brand) {
1175
+ if (value === null || typeof value !== "object" && typeof value !== "function") return false;
1176
+ try {
1177
+ const marker = Reflect.getOwnPropertyDescriptor(value, brand);
1178
+ return marker?.value === true && marker.enumerable === false && marker.writable === false && marker.configurable === false && Object.isFrozen(value);
1179
+ } catch {
1180
+ return false;
1181
+ }
1182
+ }
1183
+
1184
+ //#endregion
1185
+ //#region src/domain/event/clock.ts
456
1186
  /** Immutable library default captured by the default domain-event factory. */
457
1187
  const defaultClockFactory = () => /* @__PURE__ */ new Date();
458
1188
  /** Internal defensive event-clock read. */
@@ -464,7 +1194,7 @@ function readClock(factory) {
464
1194
  }
465
1195
 
466
1196
  //#endregion
467
- //#region src/aggregate/domain-event-errors.ts
1197
+ //#region src/domain/event/domain-event-errors.ts
468
1198
  /**
469
1199
  * Stable contract error for malformed domain-event data.
470
1200
  *
@@ -494,7 +1224,7 @@ var SnapshotTimeValidationError = class extends TypeError {
494
1224
  };
495
1225
 
496
1226
  //#endregion
497
- //#region src/aggregate/domain-event.ts
1227
+ //#region src/domain/event/domain-event.ts
498
1228
  const defaultEventIdFactory = () => crypto.randomUUID();
499
1229
  /**
500
1230
  * Creates an immutable, instance-bound domain-event factory.
@@ -517,20 +1247,39 @@ const defaultEventIdFactory = () => crypto.randomUUID();
517
1247
  * recordPendingEvents(order, domainEvents);
518
1248
  * ```
519
1249
  */
1250
+ /**
1251
+ * Fills the origin of a factory into options that name none.
1252
+ *
1253
+ * A call site that states its own source keeps it: an explicit fact about one
1254
+ * event outranks the default of the factory that mints it.
1255
+ */
1256
+ function withFactorySource(options, source) {
1257
+ const given = options ?? {};
1258
+ if (source === void 0 || given.metadata?.source !== void 0) return given;
1259
+ return {
1260
+ ...given,
1261
+ metadata: {
1262
+ ...given.metadata,
1263
+ source
1264
+ }
1265
+ };
1266
+ }
520
1267
  function createDomainEventFactory(options = {}) {
521
1268
  const eventIdFactory = options.eventIdFactory ?? defaultEventIdFactory;
522
1269
  const clock = options.clock ?? defaultClockFactory;
523
- const create = ((type, payload, createOptions) => mintDomainEvent(type, payload, createOptions, eventIdFactory, clock));
1270
+ const { source } = options;
1271
+ const create = ((type, payload, createOptions) => mintDomainEvent(type, payload, withFactorySource(createOptions, source), eventIdFactory, clock));
524
1272
  const createStamp = (stampOptions = {}) => {
525
1273
  const explicitOccurredAt = stampOptions.occurredAt === void 0 ? void 0 : copyValidEventDate(stampOptions.occurredAt);
526
1274
  if (stampOptions.eventId !== void 0) assertNonBlankEventField(stampOptions.eventId, "eventId", "EVENT_ID_INVALID");
527
1275
  const eventId = stampOptions.eventId ?? eventIdFactory();
528
1276
  assertNonBlankEventField(eventId, "eventId", "EVENT_ID_INVALID");
529
- const owned = deepFreeze({
1277
+ const stamp = {
530
1278
  eventId,
531
1279
  occurredAt: explicitOccurredAt ?? readEventClock(clock),
532
- metadata: guardedMetadataClone(stampOptions.metadata)
533
- });
1280
+ metadata: cloneOwnedEventData(withFactorySource(stampOptions, source).metadata, "metadata")
1281
+ };
1282
+ const owned = deepFreeze(stamp);
534
1283
  FACTORY_OWNED_EVENT_STAMPS.add(owned);
535
1284
  return owned;
536
1285
  };
@@ -548,7 +1297,7 @@ function createDomainEventFactory(options = {}) {
548
1297
  const defaultDomainEventFactory = createDomainEventFactory();
549
1298
  /**
550
1299
  * Creates a domain event with default values.
551
- * Sets occurredAt to current date and version to 1 if not provided.
1300
+ * Sets occurredAt to current date and schemaVersion to 1 if not provided.
552
1301
  *
553
1302
  * **Input ownership.** The event is deeply frozen, and `payload` and
554
1303
  * `metadata` are deep-cloned first, so the caller's own objects are never
@@ -559,14 +1308,14 @@ const defaultDomainEventFactory = createDomainEventFactory();
559
1308
  * over.
560
1309
  *
561
1310
  * **For aggregate-internal events, prefer `this.createEvent(...)` on
562
- * `AggregateRoot` / `EventSourcedAggregate`.** That helper auto-injects
1311
+ * `StateStoredAggregate` / `EventSourcedAggregate`.** That helper auto-injects
563
1312
  * `aggregateId` (from `this.id`) and `aggregateType` (from the
564
1313
  * aggregate's declared `aggregateType` property), which downstream
565
1314
  * consumers (outbox dispatchers, projection handlers, audit logs)
566
- * route by. The `withCommit` harvest boundary now validates both fields
567
- * are present and throws if they're missing, so a direct
568
- * `createDomainEvent(...)` call inside an aggregate that forgets the
569
- * options is caught at runtime. Record pending decisions in the application
1315
+ * route by. The commit boundary validates that both fields are present
1316
+ * and throws if they are missing, so a direct `createDomainEvent(...)`
1317
+ * call inside an aggregate that forgets the options is caught at
1318
+ * runtime. Record pending decisions in the application
570
1319
  * shell before repository persistence or outbox harvest.
571
1320
  *
572
1321
  * Use `createDomainEvent(...)` directly for events that don't belong to
@@ -585,46 +1334,39 @@ const defaultDomainEventFactory = createDomainEventFactory();
585
1334
  * const event = createDomainEvent("OrderCreated", { orderId: "123" });
586
1335
  * ```
587
1336
  */
588
- const MINTED_EVENTS = /* @__PURE__ */ new WeakSet();
1337
+ const RECORDED_EVENTS = /* @__PURE__ */ new WeakSet();
589
1338
  const UNCOMMITTED_EVENTS = /* @__PURE__ */ new WeakSet();
590
1339
  const FACTORY_OWNED_EVENT_STAMPS = /* @__PURE__ */ new WeakSet();
591
- const MINT_BRAND = Symbol.for("@shirudo/ddd-kit.mintedEvent");
1340
+ const RECORDED_BRAND = Symbol.for("@shirudo/ddd-kit.mintedEvent");
592
1341
  const UNCOMMITTED_BRAND = Symbol.for("@shirudo/ddd-kit.uncommittedEvent");
593
- function stampMintBrand(event) {
594
- Object.defineProperty(event, MINT_BRAND, {
595
- value: true,
596
- enumerable: false,
597
- writable: false,
598
- configurable: false
599
- });
1342
+ function stampRecordedBrand(event) {
1343
+ stampCooperativeBrand(event, RECORDED_BRAND);
600
1344
  }
601
1345
  function stampUncommittedBrand(event) {
602
- Object.defineProperty(event, UNCOMMITTED_BRAND, {
603
- value: true,
604
- enumerable: false,
605
- writable: false,
606
- configurable: false
607
- });
1346
+ stampCooperativeBrand(event, UNCOMMITTED_BRAND);
608
1347
  }
609
1348
  function isFactoryOwnedDomainEventStamp(stamp) {
610
1349
  return FACTORY_OWNED_EVENT_STAMPS.has(stamp);
611
1350
  }
612
1351
  /**
613
- * Whether `event` came out of {@link createDomainEvent} (or a helper
614
- * built on it, such as the aggregate `createEvent` helper), i.e. is deeply frozen with
615
- * defensively copied payload and metadata. Two tiers: events of THIS
616
- * loaded copy of the kit are verified unforgeably via the module's
1352
+ * Whether `event` is a recorded domain event: it came out of
1353
+ * {@link createDomainEvent}, {@link createDomainEventFromFacts}, or
1354
+ * {@link recordDomainEvent}, so it is deeply frozen with defensively copied
1355
+ * payload and metadata. An uncommitted decision carries the other brand; see
1356
+ * {@link isUncommittedDomainEvent}. Two tiers: events of THIS
1357
+ * loaded copy of the kit are verified through the module-private
617
1358
  * WeakSet; events minted by ANOTHER copy (duplicate dependency, dual
618
- * CJS/ESM load) are recognized cooperatively via a global-registry
619
- * brand. Module-internal export for the aggregate recording paths;
620
- * not part of the package entries.
1359
+ * CJS/ESM load) are recognized through a cooperative `Symbol.for`
1360
+ * brand that code in the same process can fake. The gate catches
1361
+ * accidents, not adversaries. Module-internal export for the aggregate
1362
+ * recording paths; not part of the package entries.
621
1363
  */
622
- function isMintedEvent(event) {
623
- return MINTED_EVENTS.has(event) || event[MINT_BRAND] === true;
1364
+ function isRecordedDomainEvent(event) {
1365
+ return RECORDED_EVENTS.has(event) || hasCooperativeBrand(event, RECORDED_BRAND);
624
1366
  }
625
1367
  /** Whether a value was created by {@link createUncommittedDomainEvent}. */
626
1368
  function isUncommittedDomainEvent(event) {
627
- return UNCOMMITTED_EVENTS.has(event) || event[UNCOMMITTED_BRAND] === true;
1369
+ return UNCOMMITTED_EVENTS.has(event) || hasCooperativeBrand(event, UNCOMMITTED_BRAND);
628
1370
  }
629
1371
  function createUncommittedDomainEvent(type, payload, options) {
630
1372
  assertProducerOwnedEventFields(type, options);
@@ -633,7 +1375,7 @@ function createUncommittedDomainEvent(type, payload, options) {
633
1375
  aggregateId: options?.aggregateId,
634
1376
  aggregateType: options?.aggregateType,
635
1377
  payload: cloneOwnedEventData(payload, "payload"),
636
- version: options?.version ?? 1
1378
+ schemaVersion: options?.schemaVersion ?? 1
637
1379
  };
638
1380
  stampUncommittedBrand(event);
639
1381
  const uncommitted = deepFreeze(event);
@@ -659,7 +1401,7 @@ function recordDomainEvent(event, stamp) {
659
1401
  if (isFactoryOwnedDomainEventStamp(stamp)) return mintRecordedEvent(event, stamp.eventId, stamp.occurredAt, stamp.metadata);
660
1402
  assertNonBlankEventField(stamp.eventId, "eventId", "EVENT_ID_INVALID");
661
1403
  const occurredAt = deepFreeze(copyValidEventDate(stamp.occurredAt));
662
- const metadata = guardedMetadataClone(stamp.metadata);
1404
+ const metadata = cloneOwnedEventData(stamp.metadata, "metadata");
663
1405
  return mintRecordedEvent(event, stamp.eventId, occurredAt, metadata === void 0 ? void 0 : deepFreeze(metadata));
664
1406
  }
665
1407
  /**
@@ -678,27 +1420,27 @@ function mintRecordedEvent(event, eventId, occurredAt, metadata) {
678
1420
  aggregateType: event.aggregateType,
679
1421
  payload: event.payload,
680
1422
  occurredAt,
681
- version: event.version,
1423
+ schemaVersion: event.schemaVersion,
682
1424
  metadata
683
1425
  };
684
- stampMintBrand(recorded);
1426
+ stampRecordedBrand(recorded);
685
1427
  Object.freeze(recorded);
686
- MINTED_EVENTS.add(recorded);
1428
+ RECORDED_EVENTS.add(recorded);
687
1429
  return recorded;
688
1430
  }
689
1431
  /**
690
- * Brands, freezes, and registers a kit-derived copy of a minted event
691
- * (e.g. the address-stamped copy `apply()` creates) as minted itself.
1432
+ * Brands, freezes, and registers a kit-derived copy of a recorded event
1433
+ * (e.g. the address-stamped copy `apply()` creates) as recorded itself.
692
1434
  * The copy shares the already-frozen payload/metadata of its source,
693
1435
  * so the mint guarantee carries over. Stamping the cooperative brand
694
1436
  * before freezing keeps the copy recognizable by another loaded kit
695
1437
  * instance as well as by this instance's WeakSet. Module-internal
696
1438
  * export; not part of the package entries.
697
1439
  */
698
- function adoptMintedEvent(copy) {
699
- stampMintBrand(copy);
1440
+ function adoptRecordedDomainEvent(copy) {
1441
+ stampRecordedBrand(copy);
700
1442
  Object.freeze(copy);
701
- MINTED_EVENTS.add(copy);
1443
+ RECORDED_EVENTS.add(copy);
702
1444
  return copy;
703
1445
  }
704
1446
  function createDomainEvent(type, payload, options) {
@@ -720,7 +1462,7 @@ function mintDomainEvent(type, payload, options, eventIdFactory, clock) {
720
1462
  const eventId = options?.eventId ?? eventIdFactory();
721
1463
  assertNonBlankEventField(eventId, "eventId", "EVENT_ID_INVALID");
722
1464
  const occurredAt = options?.occurredAt === void 0 ? readEventClock(clock) : copyValidEventDate(options.occurredAt);
723
- const version = options?.version ?? 1;
1465
+ const schemaVersion = options?.schemaVersion ?? 1;
724
1466
  const event = {
725
1467
  eventId,
726
1468
  type,
@@ -728,18 +1470,18 @@ function mintDomainEvent(type, payload, options, eventIdFactory, clock) {
728
1470
  aggregateType: options?.aggregateType,
729
1471
  payload: cloneOwnedEventData(payload, "payload"),
730
1472
  occurredAt,
731
- version,
732
- metadata: guardedMetadataClone(options?.metadata)
1473
+ schemaVersion,
1474
+ metadata: cloneOwnedEventData(options?.metadata, "metadata")
733
1475
  };
734
- stampMintBrand(event);
1476
+ stampRecordedBrand(event);
735
1477
  const minted = deepFreeze(event);
736
- MINTED_EVENTS.add(minted);
1478
+ RECORDED_EVENTS.add(minted);
737
1479
  return minted;
738
1480
  }
739
1481
  function assertProducerOwnedEventFields(type, options) {
740
1482
  assertNonBlankEventField(type, "type", "EVENT_TYPE_INVALID");
741
- const version = options?.version ?? 1;
742
- if (!Number.isSafeInteger(version) || typeof version !== "number" || version < 1) throw new DomainEventValidationError("EVENT_SCHEMA_VERSION_INVALID", "version", "domain-event version must be a safe integer greater than or equal to 1");
1483
+ const schemaVersion = options?.schemaVersion ?? 1;
1484
+ if (!Number.isSafeInteger(schemaVersion) || typeof schemaVersion !== "number" || schemaVersion < 1) throw new DomainEventValidationError("EVENT_SCHEMA_VERSION_INVALID", "schemaVersion", "domain-event schemaVersion must be a safe integer greater than or equal to 1");
743
1485
  if (options?.aggregateId !== void 0) assertNonBlankEventField(options.aggregateId, "aggregateId", "EVENT_ADDRESS_INVALID");
744
1486
  if (options?.aggregateType !== void 0) assertNonBlankEventField(options.aggregateType, "aggregateType", "EVENT_ADDRESS_INVALID");
745
1487
  }
@@ -766,7 +1508,9 @@ function readEventClock(clock) {
766
1508
  */
767
1509
  function cloneOwnedEventData(value, field) {
768
1510
  if (typeof value === "function") throw new TypeError(`createDomainEvent: ${field} must not be a function: domain events are plain data`);
1511
+ if (value === null && field === "metadata") throw new TypeError("createDomainEvent: metadata must be an object or undefined; received null");
769
1512
  if (value === null || typeof value !== "object") return value;
1513
+ assertNoHostileOwnProtoKey(value, field === "payload" ? "Event payload" : "Event metadata");
770
1514
  assertNoBinaryData(value, field);
771
1515
  try {
772
1516
  return structuredClone(value);
@@ -815,7 +1559,7 @@ function assertNoBinaryData(value, field, visited = /* @__PURE__ */ new WeakSet(
815
1559
  * const newEvent = createDomainEvent(
816
1560
  * "OrderShipped",
817
1561
  * { orderId: "123" },
818
- * { metadata: copyMetadata(previousEvent, { causationId: previousEvent.type }) }
1562
+ * { metadata: copyMetadata(previousEvent, { causationId: previousEvent.eventId }) }
819
1563
  * );
820
1564
  * ```
821
1565
  */
@@ -857,19 +1601,9 @@ function mergeMetadata(...metadataObjects) {
857
1601
  }
858
1602
  return merged;
859
1603
  }
860
- /**
861
- * Clones event metadata with the loud `__proto__` rejection applied at
862
- * the SOURCE: structuredClone preserves an own `__proto__` data key, so
863
- * without this guard a hostile envelope would ride into the frozen
864
- * event and re-arm downstream.
865
- */
866
- function guardedMetadataClone(metadata) {
867
- if (metadata !== void 0) assertNoHostileOwnProtoKey(metadata, "Event metadata");
868
- return cloneOwnedEventData(metadata, "metadata");
869
- }
870
1604
 
871
1605
  //#endregion
872
- //#region src/events/ports.ts
1606
+ //#region src/messaging/outbox/ports.ts
873
1607
  /**
874
1608
  * Discriminates a {@link DispatchTrackingOutbox} from a plain
875
1609
  * {@link Outbox} at runtime. The single source of truth for the check;
@@ -887,5 +1621,5 @@ function isDispatchTrackingOutbox(outbox) {
887
1621
  }
888
1622
 
889
1623
  //#endregion
890
- export { deepFreeze as _, createDomainEvent as a, voEqualsExcept as b, createUncommittedDomainEvent as c, isUncommittedDomainEvent as d, mergeMetadata as f, ValueObject as g, SnapshotTimeValidationError as h, copyMetadata as i, defaultDomainEventFactory as l, DomainEventValidationError as m, adoptMintedEvent as n, createDomainEventFactory as o, recordDomainEvent as p, adoptUncommittedDomainEvent as r, createDomainEventFromFacts as s, isDispatchTrackingOutbox as t, isMintedEvent as u, vo as v, voWithValidation as x, voEquals as y };
1624
+ export { isWeakMap as A, voWithValidation as C, findPropertyDescriptor as D, deepEqual as E, isBuiltInObject as O, voEqualsExcept as S, deepOmit as T, stampCooperativeBrand as _, createDomainEvent as a, vo as b, createUncommittedDomainEvent as c, isUncommittedDomainEvent as d, mergeMetadata as f, hasCooperativeBrand as g, SnapshotTimeValidationError as h, copyMetadata as i, isIntrinsicConstructorPrototype as k, defaultDomainEventFactory as l, DomainEventValidationError as m, adoptRecordedDomainEvent as n, createDomainEventFactory as o, recordDomainEvent as p, adoptUncommittedDomainEvent as r, createDomainEventFromFacts as s, isDispatchTrackingOutbox as t, isRecordedDomainEvent as u, ValueObject as v, deepEqualExcept as w, voEquals as x, deepFreeze as y };
891
1625
  //# sourceMappingURL=ports.js.map