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

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,4 +1,4 @@
1
- import { J as assertNoHostileOwnProtoKey } from "./kit-errors.js";
1
+ import { C as InvalidEventStreamPageError, Y as assertNoHostileOwnProtoKey } from "./kit-errors.js";
2
2
  import { err, ok } from "@shirudo/result";
3
3
 
4
4
  //#region src/internal/structural/is-built-in.ts
@@ -49,7 +49,7 @@ const sharedArrayBufferByteLengthGet = typeof SharedArrayBuffer === "undefined"
49
49
  const regExpSourceGet = intrinsicGetter(RegExp.prototype, "source");
50
50
  const booleanValueOf = Boolean.prototype.valueOf;
51
51
  const numberValueOf = Number.prototype.valueOf;
52
- const stringValueOf = String.prototype.valueOf;
52
+ const stringValueOf$1 = String.prototype.valueOf;
53
53
  const bigIntValueOf = BigInt.prototype.valueOf;
54
54
  const functionToString = Function.prototype.toString;
55
55
  const PROBE_KEY = {};
@@ -91,9 +91,9 @@ const intrinsicConstructorSources = new Map([
91
91
  "BigUint64Array"
92
92
  ].flatMap((name) => {
93
93
  const intrinsic = Object.getOwnPropertyDescriptor(globalThis, name)?.value;
94
- return typeof intrinsic === "function" ? [[name, functionToString.call(intrinsic)]] : [];
94
+ return typeof intrinsic === "function" ? [[name, /* @__PURE__ */ new Set([functionToString.call(intrinsic), `function ${name}() { [native code] }`])]] : [];
95
95
  }));
96
- const intrinsicConstructorSourceSet = new Set(intrinsicConstructorSources.values());
96
+ const intrinsicConstructorSourceSet = new Set([...intrinsicConstructorSources.values()].flatMap((sources) => [...sources]));
97
97
  const ERROR_INTRINSIC_NAMES = [
98
98
  "Error",
99
99
  "EvalError",
@@ -114,13 +114,13 @@ function isIntrinsicConstructorPrototype(prototype, expectedName) {
114
114
  } catch {
115
115
  return false;
116
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;
117
+ const expectedSources = expectedName === void 0 ? void 0 : intrinsicConstructorSources.get(expectedName);
118
+ if (expectedSources !== void 0 && !expectedSources.has(candidateSource) || expectedSources === void 0 && !intrinsicConstructorSourceSet.has(candidateSource)) return false;
119
119
  const nameDescriptor = Object.getOwnPropertyDescriptor(candidateConstructor, "name");
120
120
  const candidateName = nameDescriptor !== void 0 && "value" in nameDescriptor && typeof nameDescriptor.value === "string" ? nameDescriptor.value : void 0;
121
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;
122
+ const intrinsicSources = intrinsicName === void 0 ? void 0 : intrinsicConstructorSources.get(intrinsicName);
123
+ return candidateName === intrinsicName && intrinsicSources !== void 0 && intrinsicSources.has(candidateSource) && Object.getOwnPropertyDescriptor(candidateConstructor, "prototype")?.value === prototype;
124
124
  }
125
125
  /**
126
126
  * Accepts an intrinsic prototype, optionally behind transparent
@@ -197,6 +197,10 @@ function builtInTagWithoutInvokingAccessors(value) {
197
197
  if (BUILT_IN_TAGS.has(tag) && hasBrand(value, tag)) return tag;
198
198
  return descriptor === void 0 ? void 0 : builtInTagFromBrand(value);
199
199
  }
200
+ /** The type name in a tag: `"Map"` for `"[object Map]"`. */
201
+ function builtInTypeName(tag) {
202
+ return tag.slice(8, -1);
203
+ }
200
204
  function builtInTagFromBrand(value) {
201
205
  if (hasBrand(value, "[object Date]")) return "[object Date]";
202
206
  if (hasBrand(value, "[object RegExp]")) return "[object RegExp]";
@@ -268,7 +272,7 @@ function hasBrand(obj, tag) {
268
272
  numberValueOf.call(obj);
269
273
  return true;
270
274
  case "[object String]":
271
- stringValueOf.call(obj);
275
+ stringValueOf$1.call(obj);
272
276
  return true;
273
277
  case "[object BigInt]":
274
278
  bigIntValueOf.call(obj);
@@ -646,47 +650,106 @@ function deepEqualExcept(a, b, options) {
646
650
  }
647
651
 
648
652
  //#endregion
649
- //#region src/domain/value-object/value-object.ts
653
+ //#region src/internal/cooperative-brand.ts
654
+ /**
655
+ * A cooperative brand marks an object that a kit constructor produced, so
656
+ * a second loaded copy of the kit (duplicate npm dependency, dual CJS/ESM
657
+ * load, plugin bundle) recognizes it. A module-private WeakSet cannot do
658
+ * that: it is bound to one loaded copy. The brand is a `Symbol.for` key
659
+ * with the value `true`. The property is non-enumerable, so it never
660
+ * leaks into spreads, JSON, or equality, and non-writable and
661
+ * non-configurable, so nothing edits it after the stamp.
662
+ *
663
+ * The probe reads the brand as an OWN property with exactly these
664
+ * attributes and requires the carrier to be frozen. An object that
665
+ * inherits a branded object through its prototype can carry mutable own
666
+ * overrides; a branded object that is still open can change after the
667
+ * stamp. Neither is what the constructor produced. Every stamp site
668
+ * therefore freezes the carrier right after {@link stampCooperativeBrand}.
669
+ *
670
+ * The brand is forgeable BY DESIGN. It catches accidental hand-rolled
671
+ * literals; it is not a security boundary against code in the same
672
+ * process that fakes it on purpose.
673
+ *
674
+ * Internal utility (not exported from the package barrels).
675
+ */
676
+ function stampCooperativeBrand(target, brand) {
677
+ Object.defineProperty(target, brand, {
678
+ value: true,
679
+ enumerable: false,
680
+ writable: false,
681
+ configurable: false
682
+ });
683
+ }
684
+ /**
685
+ * Whether `value` carries `brand` as an own, non-enumerable, non-writable,
686
+ * non-configurable `true` and is frozen. A Proxy trap that throws reads
687
+ * as unbranded.
688
+ */
689
+ function hasCooperativeBrand(value, brand) {
690
+ if (value === null || typeof value !== "object" && typeof value !== "function") return false;
691
+ try {
692
+ const marker = Reflect.getOwnPropertyDescriptor(value, brand);
693
+ return marker?.value === true && marker.enumerable === false && marker.writable === false && marker.configurable === false && Object.isFrozen(value);
694
+ } catch {
695
+ return false;
696
+ }
697
+ }
698
+
699
+ //#endregion
700
+ //#region src/internal/structural/shadowed-mutators.ts
650
701
  /**
651
702
  * `Object.freeze` does not protect internal slots: a frozen Date still
652
- * accepts `setTime`, a frozen Map still accepts `set`. To make the
653
- * "deeply immutable" guarantee real, the mutator methods are shadowed
654
- * with own throwing functions BEFORE the freeze. The shadows are
655
- * non-enumerable, so they are invisible to `Object.keys`/spread (deep
656
- * equality is unaffected) and `structuredClone` drops them (a `vo()`
657
- * round-trip never sees them).
703
+ * accepts `setTime`, a frozen Map still accepts `set`. A deep freeze
704
+ * therefore shadows these mutators with own throwing functions. The
705
+ * shadows are non-enumerable, so they are invisible to `Object.keys` and
706
+ * spread, and `structuredClone` drops them. A walk that checks a copy
707
+ * accepts them as machinery, not data.
658
708
  */
659
- const DATE_MUTATORS = [
660
- "setTime",
661
- "setMilliseconds",
662
- "setUTCMilliseconds",
663
- "setSeconds",
664
- "setUTCSeconds",
665
- "setMinutes",
666
- "setUTCMinutes",
667
- "setHours",
668
- "setUTCHours",
669
- "setDate",
670
- "setUTCDate",
671
- "setMonth",
672
- "setUTCMonth",
673
- "setFullYear",
674
- "setUTCFullYear",
675
- "setYear"
676
- ];
677
- const mutationThrowers = /* @__PURE__ */ new Map();
678
- const mapEntries = Map.prototype.entries;
679
- const setValues = Set.prototype.values;
680
- function mutationThrower(typeName, method) {
709
+ const mutatorsByTag = /* @__PURE__ */ new Map([
710
+ ["[object Date]", [
711
+ "setTime",
712
+ "setMilliseconds",
713
+ "setUTCMilliseconds",
714
+ "setSeconds",
715
+ "setUTCSeconds",
716
+ "setMinutes",
717
+ "setUTCMinutes",
718
+ "setHours",
719
+ "setUTCHours",
720
+ "setDate",
721
+ "setUTCDate",
722
+ "setMonth",
723
+ "setUTCMonth",
724
+ "setFullYear",
725
+ "setUTCFullYear",
726
+ "setYear"
727
+ ]],
728
+ ["[object Map]", [
729
+ "set",
730
+ "delete",
731
+ "clear"
732
+ ]],
733
+ ["[object Set]", [
734
+ "add",
735
+ "delete",
736
+ "clear"
737
+ ]]
738
+ ]);
739
+ const mutatorShadowBrand = Symbol.for("@shirudo/ddd-kit/mutator-shadow/v1");
740
+ const shadows = /* @__PURE__ */ new Map();
741
+ function shadowOf(typeName, method) {
681
742
  const key = `${typeName}.${method}`;
682
- let thrower = mutationThrowers.get(key);
683
- if (!thrower) {
684
- thrower = function throwFrozenMutation() {
743
+ let shadow = shadows.get(key);
744
+ if (shadow === void 0) {
745
+ shadow = function throwFrozenMutation() {
685
746
  throw new TypeError(`Cannot call ${method}() on a ${typeName} inside a deeply frozen value`);
686
747
  };
687
- mutationThrowers.set(key, thrower);
748
+ stampCooperativeBrand(shadow, mutatorShadowBrand);
749
+ Object.freeze(shadow);
750
+ shadows.set(key, shadow);
688
751
  }
689
- return thrower;
752
+ return shadow;
690
753
  }
691
754
  const shadowDescriptor = {
692
755
  value: void 0,
@@ -694,15 +757,36 @@ const shadowDescriptor = {
694
757
  enumerable: false,
695
758
  configurable: false
696
759
  };
697
- /** Returns whether the shadows were installed. */
698
- function shadowMutators(obj, typeName, methods) {
699
- if (!Object.isExtensible(obj)) return false;
700
- for (const method of methods) {
701
- shadowDescriptor.value = mutationThrower(typeName, method);
702
- Object.defineProperty(obj, method, shadowDescriptor);
760
+ /**
761
+ * Installs the mutator shadows on a Date, a Map, or a Set whose tag is
762
+ * `tag`. Returns whether it installed them: a built-in of another kind has
763
+ * no mutator shadows, and a non-extensible one cannot take them.
764
+ */
765
+ function installMutatorShadows(object, tag) {
766
+ const mutators = mutatorsByTag.get(tag);
767
+ if (mutators === void 0 || !Object.isExtensible(object)) return false;
768
+ const typeName = builtInTypeName(tag);
769
+ for (const method of mutators) {
770
+ shadowDescriptor.value = shadowOf(typeName, method);
771
+ Object.defineProperty(object, method, shadowDescriptor);
703
772
  }
704
773
  return true;
705
774
  }
775
+ /**
776
+ * Whether the own property `key` of a built-in whose tag is `tag` is a
777
+ * mutator shadow: a locked, non-enumerable function with the cooperative
778
+ * brand of a shadow, under a mutator key of that built-in. The brand
779
+ * catches accidents, not a forgery.
780
+ */
781
+ function isMutatorShadow(tag, key, descriptor) {
782
+ const shadow = descriptor.value;
783
+ return (mutatorsByTag.get(tag)?.includes(key) ?? false) && typeof shadow === "function" && descriptor.enumerable === false && descriptor.writable === false && descriptor.configurable === false && hasCooperativeBrand(shadow, mutatorShadowBrand);
784
+ }
785
+
786
+ //#endregion
787
+ //#region src/domain/value-object/value-object.ts
788
+ const mapEntries$1 = Map.prototype.entries;
789
+ const setValues$1 = Set.prototype.values;
706
790
  const VALUE_OBJECT_CLASS = Symbol.for("@shirudo/ddd-kit/value-object-class/v1");
707
791
  function recordValueObjectClass(instance, valueObjectClass) {
708
792
  Object.defineProperty(instance, VALUE_OBJECT_CLASS, {
@@ -795,27 +879,14 @@ function freezeDeep(obj, walk) {
795
879
  walk.inProgress.add(obj);
796
880
  let sealed = true;
797
881
  if (builtInTag === "[object Date]" || builtInTag === "[object Map]" || builtInTag === "[object Set]") {
798
- let shadowed = KIT_SHADOWED.has(obj);
799
- if (builtInTag === "[object Date]") shadowed = shadowMutators(obj, "Date", DATE_MUTATORS) || shadowed;
800
- else if (builtInTag === "[object Map]") {
801
- for (const [key, value] of obj) {
802
- if (!freezeDeep(key, walk)) sealed = false;
803
- if (!freezeDeep(value, walk)) sealed = false;
804
- }
805
- shadowed = shadowMutators(obj, "Map", [
806
- "set",
807
- "delete",
808
- "clear"
809
- ]) || shadowed;
810
- } else if (builtInTag === "[object Set]") {
882
+ if (builtInTag === "[object Map]") for (const [key, value] of obj) {
883
+ if (!freezeDeep(key, walk)) sealed = false;
884
+ if (!freezeDeep(value, walk)) sealed = false;
885
+ }
886
+ else if (builtInTag === "[object Set]") {
811
887
  for (const member of obj) if (!freezeDeep(member, walk)) sealed = false;
812
- shadowed = shadowMutators(obj, "Set", [
813
- "add",
814
- "delete",
815
- "clear"
816
- ]) || shadowed;
817
888
  }
818
- if (shadowed) KIT_SHADOWED.add(obj);
889
+ if (installMutatorShadows(obj, builtInTag) || KIT_SHADOWED.has(obj)) KIT_SHADOWED.add(obj);
819
890
  else sealed = false;
820
891
  }
821
892
  const keys = Reflect.ownKeys(obj);
@@ -880,7 +951,7 @@ function cloneForVo(value, visited) {
880
951
  if (tag === "[object Map]") {
881
952
  const clone = /* @__PURE__ */ new Map();
882
953
  visited.set(obj, clone);
883
- for (const [key, entry] of mapEntries.call(obj)) {
954
+ for (const [key, entry] of mapEntries$1.call(obj)) {
884
955
  if (!isPrimitiveValue(key)) throw new TypeError("vo() Map keys must be primitive values to preserve value equality");
885
956
  clone.set(key, cloneForVo(entry, visited));
886
957
  }
@@ -889,7 +960,7 @@ function cloneForVo(value, visited) {
889
960
  if (tag === "[object Set]") {
890
961
  const clone = /* @__PURE__ */ new Set();
891
962
  visited.set(obj, clone);
892
- for (const member of setValues.call(obj)) {
963
+ for (const member of setValues$1.call(obj)) {
893
964
  if (!isPrimitiveValue(member)) throw new TypeError("vo() Set members must be primitive values to preserve value equality");
894
965
  clone.add(member);
895
966
  }
@@ -1201,50 +1272,279 @@ var ValueObject = class {
1201
1272
  };
1202
1273
 
1203
1274
  //#endregion
1204
- //#region src/internal/cooperative-brand.ts
1275
+ //#region src/internal/structural/detach-state.ts
1205
1276
  /**
1206
- * A cooperative brand marks an object that a kit constructor produced, so
1207
- * a second loaded copy of the kit (duplicate npm dependency, dual CJS/ESM
1208
- * load, plugin bundle) recognizes it. A module-private WeakSet cannot do
1209
- * that: it is bound to one loaded copy. The brand is a `Symbol.for` key
1210
- * with the value `true`. The property is non-enumerable, so it never
1211
- * leaks into spreads, JSON, or equality, and non-writable and
1212
- * non-configurable, so nothing edits it after the stamp.
1277
+ * Returns a copy of `state` that shares no object with the original.
1278
+ * Throws a `TypeError` that names the path when the graph carries a value
1279
+ * a structured clone would lose or silently degrade. A class instance, a
1280
+ * subclass of a built-in included, loses the methods on its prototype. A
1281
+ * symbol-keyed, non-enumerable, or accessor property and an expando on a
1282
+ * built-in are dropped. A function or a symbol value throws a raw
1283
+ * `DataCloneError`. An Error, a Promise, a WeakMap, or a WeakSet cannot be
1284
+ * detached at all. A SharedArrayBuffer and a view over one keep sharing
1285
+ * their memory. A Proxy fails in the walk or in the clone; a failure in
1286
+ * the clone is rethrown as a `TypeError` with the cause. An error that a
1287
+ * Proxy trap throws in the walk passes unchanged, so a coded error of the
1288
+ * caller keeps its code.
1213
1289
  *
1214
- * The probe reads the brand as an OWN property with exactly these
1215
- * attributes and requires the carrier to be frozen. An object that
1216
- * inherits a branded object through its prototype can carry mutable own
1217
- * overrides; a branded object that is still open can change after the
1218
- * stamp. Neither is what the constructor produced. Every stamp site
1219
- * therefore freezes the carrier right after {@link stampCooperativeBrand}.
1290
+ * The walk reads the graph through property descriptors and walks a Map
1291
+ * or a Set with the built-in iteration, so no accessor and no own iterator
1292
+ * of the state runs. A Proxy runs its traps during the walk. A trap can
1293
+ * change the graph, and an accessor that a trap adds runs during the
1294
+ * clone. A second walk checks the clone, so a value that a trap swapped in
1295
+ * and the content of a built-in whose prototype was replaced are rejected
1296
+ * too. A trap that changes the graph after the first walk can still hide a
1297
+ * value that the copy drops: the walks catch accidents, not a hostile
1298
+ * Proxy. The second walk also follows the steps of the first walk: the kind
1299
+ * of each object and the key of each data property, in walk order. A copy
1300
+ * that differs throws, because a built-in whose prototype was replaced is
1301
+ * a built-in again in the copy and loses its own properties. `subject`
1302
+ * names the state in the messages.
1220
1303
  *
1221
- * The brand is forgeable BY DESIGN. It catches accidental hand-rolled
1222
- * literals; it is not a security boundary against code in the same
1223
- * process that fakes it on purpose.
1304
+ * Plain objects (from any realm), arrays, Dates, Maps, Sets, bigints, and
1305
+ * typed arrays pass. A RegExp passes: pattern and flags survive the clone,
1306
+ * and `lastIndex` restores as 0. The scan state of a global or sticky
1307
+ * pattern is not domain data. On a built-in, the mutator shadows of a deep
1308
+ * freeze, the `lastIndex` of a RegExp, and the `length` of a boxed String
1309
+ * pass: they are machinery, not data. Every other non-enumerable property
1310
+ * throws. A non-enumerable symbol key passes anywhere: it is metadata by
1311
+ * convention.
1224
1312
  *
1225
- * Internal utility (not exported from the package barrels).
1313
+ * The concrete entity uses it for a detached read DTO of a plain-data
1314
+ * state. The snapshot model uses it for the captured DTO and the restored
1315
+ * state. A state that carries a class-based child is mapped to plain data
1316
+ * first, in the entity or in the model.
1226
1317
  */
1227
- function stampCooperativeBrand(target, brand) {
1228
- Object.defineProperty(target, brand, {
1229
- value: true,
1230
- enumerable: false,
1231
- writable: false,
1232
- configurable: false
1233
- });
1318
+ function detachState(state, subject = "detachState: state") {
1319
+ return detach(state, subject, "clone");
1234
1320
  }
1235
1321
  /**
1236
- * Whether `value` carries `brand` as an own, non-enumerable, non-writable,
1237
- * non-configurable `true` and is frozen. A Proxy trap that throws reads
1238
- * as unbranded.
1322
+ * {@link detachState} that also rejects what `deepFreeze` cannot make
1323
+ * immutable: binary data (a typed array, a DataView, an ArrayBuffer, or a
1324
+ * SharedArrayBuffer) and a RegExp. The walk rejects such a value at its
1325
+ * tag, before it reads the indices of a typed array. Kit modules only.
1239
1326
  */
1240
- function hasCooperativeBrand(value, brand) {
1241
- if (value === null || typeof value !== "object" && typeof value !== "function") return false;
1327
+ function detachFreezableState(state, subject) {
1328
+ return detach(state, subject, "freeze");
1329
+ }
1330
+ function detach(state, subject, rule) {
1331
+ const recording = new StepRecording();
1332
+ assertDetachable(state, "", newWalk(subject, rule, recording));
1333
+ let copy;
1242
1334
  try {
1243
- const marker = Reflect.getOwnPropertyDescriptor(value, brand);
1244
- return marker?.value === true && marker.enumerable === false && marker.writable === false && marker.configurable === false && Object.isFrozen(value);
1245
- } catch {
1246
- return false;
1335
+ copy = structuredClone(state);
1336
+ } catch (cause) {
1337
+ throw new TypeError(`${subject} holds a Proxy or a host object that cannot be cloned, or a view over a detached or shrunk buffer; the cause names the value; map it to plain data`, { cause });
1338
+ }
1339
+ const comparison = new StepComparison(recording, subject);
1340
+ assertDetachable(copy, "", newWalk(subject, rule, comparison));
1341
+ comparison.assertComplete();
1342
+ return copy;
1343
+ }
1344
+ const INDEX_KEY = /^(0|[1-9]\d*)$/;
1345
+ const BINARY_DATA_TAGS = /* @__PURE__ */ new Set([
1346
+ "[object TypedArray]",
1347
+ "[object DataView]",
1348
+ "[object ArrayBuffer]",
1349
+ "[object SharedArrayBuffer]"
1350
+ ]);
1351
+ const REGEXP_TAG = "[object RegExp]";
1352
+ const STRING_TAG = "[object String]";
1353
+ const mapEntries = Map.prototype.entries;
1354
+ const setValues = Set.prototype.values;
1355
+ const RECORD_STEP = Symbol("record");
1356
+ const ARRAY_STEP = Symbol("array");
1357
+ const builtInSteps = /* @__PURE__ */ new Map();
1358
+ function builtInStep(tag) {
1359
+ let kind = builtInSteps.get(tag);
1360
+ if (kind === void 0) {
1361
+ kind = Symbol(tag);
1362
+ builtInSteps.set(tag, kind);
1247
1363
  }
1364
+ return kind;
1365
+ }
1366
+ /**
1367
+ * The steps of the walk over the original, in walk order.
1368
+ */
1369
+ var StepRecording = class {
1370
+ steps = [];
1371
+ take(next) {
1372
+ this.steps.push(next);
1373
+ }
1374
+ };
1375
+ /**
1376
+ * The walk over the copy compares each of its steps with the step of the
1377
+ * walk over the original at the same position. Structured clone keeps the
1378
+ * order of the keys, of Map entries, and of Set members, and it keeps
1379
+ * shared references, so the walk over an unchanged copy takes the same
1380
+ * steps. A step that differs, or a step that is missing, means that the
1381
+ * copy changed.
1382
+ */
1383
+ var StepComparison = class {
1384
+ recording;
1385
+ subject;
1386
+ #position = 0;
1387
+ constructor(recording, subject) {
1388
+ this.recording = recording;
1389
+ this.subject = subject;
1390
+ }
1391
+ take(next, path) {
1392
+ const expected = this.recording.steps[this.#position];
1393
+ if (expected !== next) throwChangedInCopy(this.subject, path, expected, next);
1394
+ this.#position += 1;
1395
+ }
1396
+ assertComplete() {
1397
+ if (this.#position < this.recording.steps.length) throwChangedInCopy(this.subject, "", this.recording.steps[this.#position], void 0);
1398
+ }
1399
+ };
1400
+ function newWalk(subject, rule, trace) {
1401
+ return {
1402
+ seen: /* @__PURE__ */ new WeakSet(),
1403
+ subject,
1404
+ rule,
1405
+ trace
1406
+ };
1407
+ }
1408
+ function step(walk, next, path) {
1409
+ walk.trace.take(next, path);
1410
+ }
1411
+ function throwChangedInCopy(subject, path, expected, found) {
1412
+ throw new TypeError(`${subject}${path} changed in the copy: the state has ${describeStep(expected)} there, and the copy has ${describeStep(found)}; a built-in whose prototype was replaced is a built-in again in the copy and loses its own properties, and a Proxy trap can change the graph; map it to plain data`);
1413
+ }
1414
+ function describeStep(step) {
1415
+ if (step === void 0) return "nothing";
1416
+ if (typeof step === "string") return `the key "${step}"`;
1417
+ if (step === RECORD_STEP) return "a record";
1418
+ if (step === ARRAY_STEP) return "an array";
1419
+ const name = builtInTypeName(step.description);
1420
+ return /^[AEIOU]/.test(name) ? `an ${name}` : `a ${name}`;
1421
+ }
1422
+ function assertDetachable(value, path, walk) {
1423
+ if (typeof value === "function") throw new TypeError(`${walk.subject}${path} is a function; map it to plain data`);
1424
+ if (typeof value === "symbol") throw new TypeError(`${walk.subject}${path} is a symbol; map it to plain data`);
1425
+ if (value === null || typeof value !== "object") return;
1426
+ const object = value;
1427
+ if (walk.seen.has(object)) return;
1428
+ walk.seen.add(object);
1429
+ if (Array.isArray(object)) {
1430
+ if (!hasIntrinsicPrototypeChain(object, "Array")) throwClassInstance(object, path, walk.subject);
1431
+ step(walk, ARRAY_STEP, path);
1432
+ assertOwnPropertiesDetachable(object, path, walk, "array");
1433
+ return;
1434
+ }
1435
+ const tag = builtInTagWithoutInvokingAccessors(object);
1436
+ if (tag !== void 0) {
1437
+ if (walk.rule === "freeze") assertFreezable(tag, path, walk.subject);
1438
+ if (!hasIntrinsicPrototypeChain(object)) throwClassInstance(object, path, walk.subject);
1439
+ step(walk, builtInStep(tag), path);
1440
+ if (tag === "[object Map]") {
1441
+ let index = 0;
1442
+ for (const [key, entry] of mapEntries.call(object)) {
1443
+ assertDetachable(key, `${path}<map key #${index}>`, walk);
1444
+ assertDetachable(entry, `${path}<map value #${index}>`, walk);
1445
+ index++;
1446
+ }
1447
+ } else if (tag === "[object Set]") {
1448
+ let index = 0;
1449
+ for (const member of setValues.call(object)) {
1450
+ assertDetachable(member, `${path}<set member #${index}>`, walk);
1451
+ index++;
1452
+ }
1453
+ } else if (tag === "[object Promise]" || tag === "[object WeakMap]" || tag === "[object WeakSet]") throw new TypeError(`${walk.subject}${path} is a ${builtInTypeName(tag)} and cannot be detached`);
1454
+ else if (tag === "[object Error]") throw new TypeError(`${walk.subject}${path} is an Error; map it to plain data`);
1455
+ else if (sharesMemory(object, tag)) throw new TypeError(`${walk.subject}${path} is backed by a SharedArrayBuffer and the copy would share its memory; map it to plain data`);
1456
+ assertOwnPropertiesDetachable(object, path, walk, tag);
1457
+ return;
1458
+ }
1459
+ const prototype = Object.getPrototypeOf(object);
1460
+ if (!(prototype === null || isIntrinsicConstructorPrototype(prototype, "Object") && Object.getPrototypeOf(prototype) === null)) throwClassInstance(object, path, walk.subject);
1461
+ step(walk, RECORD_STEP, path);
1462
+ assertOwnPropertiesDetachable(object, path, walk, "record");
1463
+ }
1464
+ /**
1465
+ * Audits the own properties the clone would copy or drop. The clone keeps
1466
+ * the enumerable own keys of a record and of an array, expandos included,
1467
+ * and drops every own key of another built-in. `length` on an array is
1468
+ * its own machinery, not data, and so are the keys that
1469
+ * {@link isBuiltInMachinery} names. The index keys of a built-in below its
1470
+ * content length are its content and pass as such. Another index key of a
1471
+ * built-in is an expando. `owner` is the kind of `object`: an array, a
1472
+ * record, or the tag of a built-in.
1473
+ */
1474
+ function assertOwnPropertiesDetachable(object, path, walk, owner) {
1475
+ const tag = owner === "array" || owner === "record" ? void 0 : owner;
1476
+ const contentLength = tag === void 0 ? 0 : contentLengthOf(object, tag);
1477
+ for (const key of Reflect.ownKeys(object)) {
1478
+ const descriptor = Object.getOwnPropertyDescriptor(object, key);
1479
+ if (descriptor === void 0) continue;
1480
+ if (typeof key === "symbol") {
1481
+ if (!descriptor.enumerable) continue;
1482
+ throw new TypeError(`${walk.subject}${path} has a symbol-keyed property; map it to plain data`);
1483
+ }
1484
+ if (owner === "array" && key === "length") continue;
1485
+ const isIndex = INDEX_KEY.test(key);
1486
+ const memberPath = isIndex ? `${path}[${key}]` : `${path}.${key}`;
1487
+ if (!descriptor.enumerable) {
1488
+ if (tag !== void 0 && isBuiltInMachinery(tag, key, descriptor)) continue;
1489
+ throw new TypeError(`${walk.subject}${memberPath} is not enumerable and the clone would drop it; map it to plain data`);
1490
+ }
1491
+ if (tag !== void 0 && isIndex && Number(key) < contentLength) continue;
1492
+ if (!("value" in descriptor)) throw new TypeError(`${walk.subject}${memberPath} is an accessor property; map it to plain data`);
1493
+ if (tag !== void 0) throw new TypeError(`${walk.subject}${memberPath} is an expando on a ${constructorName(Object.getPrototypeOf(object)) ?? "built-in"} and the clone would drop it; map it to plain data`);
1494
+ step(walk, key, memberPath);
1495
+ assertDetachable(descriptor.value, memberPath, walk);
1496
+ }
1497
+ }
1498
+ /**
1499
+ * Whether an own non-enumerable property of a built-in is machinery, not
1500
+ * data: a function that shadows a mutator of a frozen Date, Map, or Set,
1501
+ * the scan position of a RegExp, or the length of a boxed String. The
1502
+ * clone drops the shadow and rebuilds the other two from the internal
1503
+ * slots.
1504
+ */
1505
+ function isBuiltInMachinery(tag, key, descriptor) {
1506
+ return isMutatorShadow(tag, key, descriptor) || tag === REGEXP_TAG && key === "lastIndex" || tag === STRING_TAG && key === "length";
1507
+ }
1508
+ /**
1509
+ * The number of index keys that hold the content of a built-in: the
1510
+ * elements of a typed array and the characters of a boxed String.
1511
+ */
1512
+ function contentLengthOf(object, tag) {
1513
+ if (tag === "[object TypedArray]") return typedArrayLength.call(object);
1514
+ if (tag === STRING_TAG) return stringValueOf.call(object).length;
1515
+ return 0;
1516
+ }
1517
+ const typedArrayLength = Object.getOwnPropertyDescriptor(Object.getPrototypeOf(Uint8Array.prototype), "length")?.get;
1518
+ const stringValueOf = String.prototype.valueOf;
1519
+ const typedArrayBuffer = Object.getOwnPropertyDescriptor(Object.getPrototypeOf(Uint8Array.prototype), "buffer")?.get;
1520
+ const dataViewBuffer = Object.getOwnPropertyDescriptor(DataView.prototype, "buffer")?.get;
1521
+ function sharesMemory(object, tag) {
1522
+ if (tag === "[object SharedArrayBuffer]") return true;
1523
+ if (tag === "[object TypedArray]") return isSharedArrayBuffer(typedArrayBuffer.call(object));
1524
+ if (tag === "[object DataView]") return isSharedArrayBuffer(dataViewBuffer.call(object));
1525
+ return false;
1526
+ }
1527
+ function isSharedArrayBuffer(buffer) {
1528
+ return builtInTagWithoutInvokingAccessors(buffer) === "[object SharedArrayBuffer]";
1529
+ }
1530
+ function assertFreezable(tag, path, subject) {
1531
+ if (BINARY_DATA_TAGS.has(tag)) throw new TypeError(`${subject}${path} is binary data; binary buffers (a typed array, a DataView, an ArrayBuffer, or a SharedArrayBuffer) stay mutable under freezing and JSON does not keep them; encode the data as a string, for example base64`);
1532
+ if (tag === REGEXP_TAG) throw new TypeError(`${subject}${path} is a RegExp; a freeze cannot make it immutable, because compile() changes the pattern of a frozen RegExp before it throws, and a frozen global or sticky pattern throws on a match; keep its source and flags as strings`);
1533
+ }
1534
+ function throwClassInstance(object, path, subject) {
1535
+ const name = constructorName(Object.getPrototypeOf(object)) ?? "anonymous class";
1536
+ throw new TypeError(`${subject}${path} is a class instance (${name}); map it to plain data`);
1537
+ }
1538
+ /**
1539
+ * The name of the constructor of `prototype`, read through property
1540
+ * descriptors, so that no accessor runs.
1541
+ */
1542
+ function constructorName(prototype) {
1543
+ if (prototype === null) return void 0;
1544
+ const owner = Object.getOwnPropertyDescriptor(prototype, "constructor")?.value;
1545
+ if (typeof owner !== "function") return void 0;
1546
+ const name = Object.getOwnPropertyDescriptor(owner, "name")?.value;
1547
+ return typeof name === "string" && name !== "" ? name : void 0;
1248
1548
  }
1249
1549
 
1250
1550
  //#endregion
@@ -1313,28 +1613,15 @@ const defaultEventIdFactory = () => crypto.randomUUID();
1313
1613
  * recordPendingEvents(order, domainEvents);
1314
1614
  * ```
1315
1615
  */
1316
- /**
1317
- * Fills the origin of a factory into options that name none.
1318
- *
1319
- * A call site that states its own source keeps it: an explicit fact about one
1320
- * event outranks the default of the factory that mints it.
1321
- */
1322
- function withFactorySource(options, source) {
1323
- const given = options ?? {};
1324
- if (source === void 0 || given.metadata?.source !== void 0) return given;
1325
- return {
1326
- ...given,
1327
- metadata: {
1328
- ...given.metadata,
1329
- source
1330
- }
1331
- };
1332
- }
1333
1616
  function createDomainEventFactory(options = {}) {
1617
+ return buildDomainEventFactory(options, "DomainEventFactory.create");
1618
+ }
1619
+ /** `createOperation` is the name that the errors of `create` give. */
1620
+ function buildDomainEventFactory(options, createOperation) {
1334
1621
  const eventIdFactory = options.eventIdFactory ?? defaultEventIdFactory;
1335
1622
  const clock = options.clock ?? defaultClockFactory;
1336
1623
  const { source } = options;
1337
- const create = ((type, payload, createOptions) => mintDomainEvent(type, payload, withFactorySource(createOptions, source), eventIdFactory, clock));
1624
+ const create = ((type, payload, createOptions) => mintDomainEvent(type, payload, createOptions, eventIdFactory, clock, createOperation, source));
1338
1625
  const createStamp = (stampOptions = {}) => {
1339
1626
  const explicitOccurredAt = stampOptions.occurredAt === void 0 ? void 0 : copyValidEventDate(stampOptions.occurredAt);
1340
1627
  if (stampOptions.eventId !== void 0) assertNonBlankEventField(stampOptions.eventId, "eventId", "EVENT_ID_INVALID");
@@ -1343,7 +1630,7 @@ function createDomainEventFactory(options = {}) {
1343
1630
  const stamp = {
1344
1631
  eventId,
1345
1632
  occurredAt: explicitOccurredAt ?? readEventClock(clock),
1346
- metadata: cloneOwnedEventData(withFactorySource(stampOptions, source).metadata, "metadata")
1633
+ metadata: ownedEventMetadata(stampOptions.metadata, "DomainEventFactory.createStamp", source)
1347
1634
  };
1348
1635
  const owned = deepFreeze(stamp);
1349
1636
  FACTORY_OWNED_EVENT_STAMPS.add(owned);
@@ -1360,46 +1647,7 @@ function createDomainEventFactory(options = {}) {
1360
1647
  * {@link createDomainEvent}. It cannot be reconfigured; construct an instance
1361
1648
  * with {@link createDomainEventFactory} for custom policy.
1362
1649
  */
1363
- const defaultDomainEventFactory = createDomainEventFactory();
1364
- /**
1365
- * Creates a domain event with default values.
1366
- * Sets occurredAt to current date and schemaVersion to 1 if not provided.
1367
- *
1368
- * **Input ownership.** The event is deeply frozen, and `payload` and
1369
- * `metadata` are deep-cloned first, so the caller's own objects are never
1370
- * frozen in place and later mutation of them does not bleed into the
1371
- * event (same contract as `vo()`). The clone follows the plain-data event
1372
- * contract via `structuredClone`: functions, Promise, and WeakMap/WeakSet
1373
- * values throw a `TypeError`; symbol-keyed properties are not carried
1374
- * over.
1375
- *
1376
- * **For aggregate-internal events, prefer `this.createEvent(...)` on
1377
- * `StateStoredAggregate` / `EventSourcedAggregate`.** That helper auto-injects
1378
- * `aggregateId` (from `this.id`) and `aggregateType` (from the
1379
- * aggregate's declared `aggregateType` property), which downstream
1380
- * consumers (outbox dispatchers, projection handlers, audit logs)
1381
- * route by. The commit boundary validates that both fields are present
1382
- * and throws if they are missing, so a direct `createDomainEvent(...)`
1383
- * call inside an aggregate that forgets the options is caught at
1384
- * runtime. Record pending decisions in the application
1385
- * shell before repository persistence or outbox harvest.
1386
- *
1387
- * Use `createDomainEvent(...)` directly for events that don't belong to
1388
- * an aggregate: system events, integration events, configuration events,
1389
- * test fixtures. For those, set `aggregateId` / `aggregateType` in
1390
- * `options` if downstream consumers expect routing metadata.
1391
- *
1392
- * @param type - The event type
1393
- * @param payload - The event payload
1394
- * @param options - Optional event configuration (including `aggregateId`
1395
- * and `aggregateType` for routing)
1396
- * @returns A domain event
1397
- *
1398
- * @example
1399
- * ```typescript
1400
- * const event = createDomainEvent("OrderCreated", { orderId: "123" });
1401
- * ```
1402
- */
1650
+ const defaultDomainEventFactory = buildDomainEventFactory({}, "createDomainEvent");
1403
1651
  const RECORDED_EVENTS = /* @__PURE__ */ new WeakSet();
1404
1652
  const UNCOMMITTED_EVENTS = /* @__PURE__ */ new WeakSet();
1405
1653
  const FACTORY_OWNED_EVENT_STAMPS = /* @__PURE__ */ new WeakSet();
@@ -1435,12 +1683,20 @@ function isUncommittedDomainEvent(event) {
1435
1683
  return UNCOMMITTED_EVENTS.has(event) || hasCooperativeBrand(event, UNCOMMITTED_BRAND);
1436
1684
  }
1437
1685
  function createUncommittedDomainEvent(type, payload, options) {
1686
+ return mintUncommittedDomainEvent(type, payload, options, "createUncommittedDomainEvent");
1687
+ }
1688
+ /**
1689
+ * {@link createUncommittedDomainEvent} whose errors name `operation`, the
1690
+ * function that the caller called. Module-internal export; not part of the
1691
+ * package entries.
1692
+ */
1693
+ function mintUncommittedDomainEvent(type, payload, options, operation) {
1438
1694
  assertProducerOwnedEventFields(type, options);
1439
1695
  const event = {
1440
1696
  type,
1441
1697
  aggregateId: options?.aggregateId,
1442
1698
  aggregateType: options?.aggregateType,
1443
- payload: cloneOwnedEventData(payload, "payload"),
1699
+ payload: cloneOwnedEventData(payload, "payload", operation),
1444
1700
  schemaVersion: options?.schemaVersion ?? 1
1445
1701
  };
1446
1702
  stampUncommittedBrand(event);
@@ -1458,16 +1714,16 @@ function adoptUncommittedDomainEvent(copy) {
1458
1714
  /**
1459
1715
  * Attaches shell-owned recording data to an accepted aggregate decision.
1460
1716
  *
1461
- * The decision supplies the domain type, payload, source address, and payload
1462
- * schema version. The stamp supplies only event identity, recording time, and
1463
- * trace metadata.
1717
+ * The decision supplies the domain type, payload, source aggregate
1718
+ * identity, and payload schema version. The stamp supplies only event
1719
+ * identity, recording time, and trace metadata.
1464
1720
  */
1465
1721
  function recordDomainEvent(event, stamp) {
1466
1722
  if (!isUncommittedDomainEvent(event)) throw new TypeError("recordDomainEvent requires an event created by createUncommittedDomainEvent");
1467
1723
  if (isFactoryOwnedDomainEventStamp(stamp)) return mintRecordedEvent(event, stamp.eventId, stamp.occurredAt, stamp.metadata);
1468
1724
  assertNonBlankEventField(stamp.eventId, "eventId", "EVENT_ID_INVALID");
1469
1725
  const occurredAt = deepFreeze(copyValidEventDate(stamp.occurredAt));
1470
- const metadata = cloneOwnedEventData(stamp.metadata, "metadata");
1726
+ const metadata = ownedEventMetadata(stamp.metadata, "recordDomainEvent");
1471
1727
  return mintRecordedEvent(event, stamp.eventId, occurredAt, metadata === void 0 ? void 0 : deepFreeze(metadata));
1472
1728
  }
1473
1729
  /**
@@ -1496,7 +1752,8 @@ function mintRecordedEvent(event, eventId, occurredAt, metadata) {
1496
1752
  }
1497
1753
  /**
1498
1754
  * Brands, freezes, and registers a kit-derived copy of a recorded event
1499
- * (e.g. the address-stamped copy `apply()` creates) as recorded itself.
1755
+ * (e.g. the copy `apply()` stamps with the aggregate identity) as recorded
1756
+ * itself.
1500
1757
  * The copy shares the already-frozen payload/metadata of its source,
1501
1758
  * so the mint guarantee carries over. Stamping the cooperative brand
1502
1759
  * before freezing keeps the copy recognizable by another loaded kit
@@ -1515,7 +1772,7 @@ function createDomainEvent(type, payload, options) {
1515
1772
  function createDomainEventFromFacts(type, payload, options) {
1516
1773
  if (options?.eventId === void 0) missingExplicitEventId();
1517
1774
  if (options.occurredAt === void 0) missingExplicitOccurredAt();
1518
- return mintDomainEvent(type, payload, options, missingExplicitEventId, missingExplicitOccurredAt);
1775
+ return mintDomainEvent(type, payload, options, missingExplicitEventId, missingExplicitOccurredAt, "createDomainEventFromFacts");
1519
1776
  }
1520
1777
  function missingExplicitEventId() {
1521
1778
  throw new DomainEventValidationError("EVENT_ID_REQUIRED", "eventId", "createDomainEventFromFacts requires an explicit eventId");
@@ -1523,7 +1780,7 @@ function missingExplicitEventId() {
1523
1780
  function missingExplicitOccurredAt() {
1524
1781
  throw new DomainEventValidationError("EVENT_OCCURRED_AT_REQUIRED", "occurredAt", "createDomainEventFromFacts requires an explicit occurredAt");
1525
1782
  }
1526
- function mintDomainEvent(type, payload, options, eventIdFactory, clock) {
1783
+ function mintDomainEvent(type, payload, options, eventIdFactory, clock, operation, factorySource) {
1527
1784
  assertProducerOwnedEventFields(type, options);
1528
1785
  const eventId = options?.eventId ?? eventIdFactory();
1529
1786
  assertNonBlankEventField(eventId, "eventId", "EVENT_ID_INVALID");
@@ -1534,10 +1791,10 @@ function mintDomainEvent(type, payload, options, eventIdFactory, clock) {
1534
1791
  type,
1535
1792
  aggregateId: options?.aggregateId,
1536
1793
  aggregateType: options?.aggregateType,
1537
- payload: cloneOwnedEventData(payload, "payload"),
1794
+ payload: cloneOwnedEventData(payload, "payload", operation),
1538
1795
  occurredAt,
1539
1796
  schemaVersion,
1540
- metadata: cloneOwnedEventData(options?.metadata, "metadata")
1797
+ metadata: ownedEventMetadata(options?.metadata, operation, factorySource)
1541
1798
  };
1542
1799
  stampRecordedBrand(event);
1543
1800
  const minted = deepFreeze(event);
@@ -1548,8 +1805,8 @@ function assertProducerOwnedEventFields(type, options) {
1548
1805
  assertNonBlankEventField(type, "type", "EVENT_TYPE_INVALID");
1549
1806
  const schemaVersion = options?.schemaVersion ?? 1;
1550
1807
  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");
1551
- if (options?.aggregateId !== void 0) assertNonBlankEventField(options.aggregateId, "aggregateId", "EVENT_ADDRESS_INVALID");
1552
- if (options?.aggregateType !== void 0) assertNonBlankEventField(options.aggregateType, "aggregateType", "EVENT_ADDRESS_INVALID");
1808
+ if (options?.aggregateId !== void 0) assertNonBlankEventField(options.aggregateId, "aggregateId", "EVENT_AGGREGATE_IDENTITY_INVALID");
1809
+ if (options?.aggregateType !== void 0) assertNonBlankEventField(options.aggregateType, "aggregateType", "EVENT_AGGREGATE_IDENTITY_INVALID");
1553
1810
  }
1554
1811
  function assertNonBlankEventField(value, field, code) {
1555
1812
  if (typeof value !== "string" || value.trim().length === 0) throw new DomainEventValidationError(code, field, `domain-event ${field} must be a non-blank string`);
@@ -1562,59 +1819,47 @@ function readEventClock(clock) {
1562
1819
  return copyValidEventDate(clock());
1563
1820
  }
1564
1821
  /**
1565
- * Deep-clones caller-supplied event data (payload, metadata) before the
1566
- * event is frozen, so `createDomainEvent` never freezes or aliases the
1567
- * caller's own object graph. Primitives pass through unchanged.
1568
- *
1569
- * Uses `structuredClone`, which matches the documented plain-data event
1570
- * contract: functions, Promise, and WeakMap/WeakSet values throw a
1571
- * descriptive `TypeError` (they are not data); symbol-keyed properties
1572
- * are not carried over; a class instance would silently lose its
1573
- * prototype, which the plain-data contract already rules out.
1822
+ * Copies the caller's metadata and checks the copy. Metadata is a plain
1823
+ * record or absent; a null from a JSON envelope would mint and then fail
1824
+ * every metadata read far from the producer. A factory origin fills
1825
+ * `source` only after the check, and a call site that names its own source
1826
+ * keeps it: an explicit fact about one event outranks the default of the
1827
+ * factory that mints it.
1574
1828
  */
1575
- function cloneOwnedEventData(value, field) {
1576
- if (typeof value === "function") throw new TypeError(`createDomainEvent: ${field} must not be a function: domain events are plain data`);
1577
- if (value === null && field === "metadata") throw new TypeError("createDomainEvent: metadata must be an object or undefined; received null");
1578
- if (value === null || typeof value !== "object") return value;
1579
- assertNoHostileOwnProtoKey(value, field === "payload" ? "Event payload" : "Event metadata");
1580
- assertNoBinaryData(value, field);
1581
- try {
1582
- return structuredClone(value);
1583
- } catch (cause) {
1584
- throw new TypeError(`createDomainEvent: ${field} must be plain, structured-cloneable data (no functions, Promises, or WeakMap/WeakSet values): domain events are plain data`, { cause });
1829
+ function ownedEventMetadata(metadata, operation, factorySource) {
1830
+ if (metadata === void 0) return factorySource === void 0 ? void 0 : { source: factorySource };
1831
+ const owned = cloneOwnedEventData(metadata, "metadata", operation);
1832
+ if (!isPlainRecordCopy(owned)) {
1833
+ const received = typeof owned === "object" ? owned === null ? "null" : Object.prototype.toString.call(owned) : `a ${typeof owned}`;
1834
+ throw new TypeError(`${operation}: metadata must be an object or undefined, and a plain record; received ${received}`);
1585
1835
  }
1836
+ const record = owned;
1837
+ if (factorySource === void 0 || record.source !== void 0) return record;
1838
+ return {
1839
+ ...record,
1840
+ source: factorySource
1841
+ };
1586
1842
  }
1587
- function isBinaryData(value) {
1588
- return ArrayBuffer.isView(value) || value instanceof ArrayBuffer || typeof SharedArrayBuffer !== "undefined" && value instanceof SharedArrayBuffer;
1843
+ /**
1844
+ * True for a structured-clone copy of a plain record. A runtime such as
1845
+ * Vercel Edge returns the copy with the `Object.prototype` of another realm,
1846
+ * so the check compares the source text of the constructor, not the
1847
+ * prototype object.
1848
+ */
1849
+ function isPlainRecordCopy(copy) {
1850
+ if (typeof copy !== "object" || copy === null) return false;
1851
+ const prototype = Object.getPrototypeOf(copy);
1852
+ return prototype !== null && isIntrinsicConstructorPrototype(prototype, "Object") && Object.getPrototypeOf(prototype) === null;
1589
1853
  }
1590
1854
  /**
1591
- * Walks caller-supplied event data and rejects binary buffers anywhere
1592
- * in the graph (TypedArray, DataView, ArrayBuffer, SharedArrayBuffer):
1593
- * they are mutable by construction, so the deep-freeze that backs the
1594
- * mint guarantee cannot cover them. Runs before the structured clone,
1595
- * on the small plain-data graphs events are documented to carry.
1855
+ * Deep-clones caller-supplied event data (payload, metadata) before the
1856
+ * event is frozen, so a constructor never freezes or aliases the caller's
1857
+ * own object graph. {@link detachFreezableState} checks the data and the
1858
+ * clone; its JSDoc states the rule and its limits.
1596
1859
  */
1597
- function assertNoBinaryData(value, field, visited = /* @__PURE__ */ new WeakSet()) {
1598
- if (value === null || typeof value !== "object") return;
1599
- if (isBinaryData(value)) throw new TypeError(`createDomainEvent: ${field} must not contain binary buffers (TypedArray, DataView, ArrayBuffer, SharedArrayBuffer): they stay mutable under freezing and do not survive JSON. Encode binary as a string (base64/hex) or store it outside the event.`);
1600
- if (visited.has(value)) return;
1601
- visited.add(value);
1602
- if (value instanceof Map) {
1603
- for (const [k, v] of value) {
1604
- assertNoBinaryData(k, field, visited);
1605
- assertNoBinaryData(v, field, visited);
1606
- }
1607
- return;
1608
- }
1609
- if (value instanceof Set) {
1610
- for (const v of value) assertNoBinaryData(v, field, visited);
1611
- return;
1612
- }
1613
- if (Array.isArray(value)) {
1614
- for (const v of value) assertNoBinaryData(v, field, visited);
1615
- return;
1616
- }
1617
- for (const key of Object.keys(value)) assertNoBinaryData(value[key], field, visited);
1860
+ function cloneOwnedEventData(value, field, operation) {
1861
+ if (value !== null && typeof value === "object") assertNoHostileOwnProtoKey(value, `Event ${field} of ${operation}`);
1862
+ return detachFreezableState(value, `${operation}: ${field}`);
1618
1863
  }
1619
1864
  /**
1620
1865
  * Copies metadata from a source event to a new event.
@@ -1687,6 +1932,29 @@ function mergeMetadata(...metadataObjects) {
1687
1932
  function abortReason(signal, fallbackMessage) {
1688
1933
  return signal.reason ?? new Error(fallbackMessage);
1689
1934
  }
1935
+ /**
1936
+ * Waits for `promise`, but REJECTS with the signal's reason as soon as the
1937
+ * signal fires. The promise keeps running; only the wait ends. The abort
1938
+ * listener goes away when the promise settles.
1939
+ */
1940
+ function waitRejectingOnAbort(promise, signal, abortMessage) {
1941
+ if (signal === void 0) return promise;
1942
+ return new Promise((resolve, reject) => {
1943
+ if (signal.aborted) {
1944
+ reject(abortReason(signal, abortMessage));
1945
+ return;
1946
+ }
1947
+ const onAbort = () => reject(abortReason(signal, abortMessage));
1948
+ signal.addEventListener("abort", onAbort, { once: true });
1949
+ promise.then((value) => {
1950
+ signal.removeEventListener("abort", onAbort);
1951
+ resolve(value);
1952
+ }, (error) => {
1953
+ signal.removeEventListener("abort", onAbort);
1954
+ reject(error);
1955
+ });
1956
+ });
1957
+ }
1690
1958
 
1691
1959
  //#endregion
1692
1960
  //#region src/internal/validate.ts
@@ -1699,6 +1967,15 @@ function abortReason(signal, fallbackMessage) {
1699
1967
  function assertNonNegativeFinite(context, field, value) {
1700
1968
  if (!Number.isFinite(value) || value < 0) throw new Error(`${context}: ${field} must be a non-negative finite number, got ${value}`);
1701
1969
  }
1970
+ /**
1971
+ * The largest delay that `setTimeout` honors. A larger delay does not wait
1972
+ * longer: the runtime fires the timer after about 1 ms.
1973
+ */
1974
+ const MAX_TIMER_DELAY_MS = 2147483647;
1975
+ /** Guard for time options that a timer waits for, in milliseconds. */
1976
+ function assertTimerDelay(context, field, value) {
1977
+ if (!Number.isFinite(value) || value < 0 || value > 2147483647) throw new RangeError(`${context}: ${field} must be a finite number of milliseconds from 0 to ${MAX_TIMER_DELAY_MS}, got ${value}`);
1978
+ }
1702
1979
  /** Guard for count options that must be a whole number of at least 1. */
1703
1980
  function assertPositiveInteger(context, field, value) {
1704
1981
  if (!Number.isInteger(value) || value < 1) throw new Error(`${context}: ${field} must be an integer >= 1, got ${value}`);
@@ -1707,6 +1984,10 @@ function assertPositiveInteger(context, field, value) {
1707
1984
  function assertPositiveSafeInteger(context, field, value) {
1708
1985
  if (!Number.isSafeInteger(value) || value < 1) throw new RangeError(`${context}: ${field} must be a positive safe integer, got ${value}`);
1709
1986
  }
1987
+ /** Guard for stream positions that must fit exact JS integers. */
1988
+ function assertNonNegativeSafeInteger(context, field, value) {
1989
+ if (!Number.isSafeInteger(value) || value < 0) throw new RangeError(`${context}: ${field} must be a non-negative safe integer, got ${value}`);
1990
+ }
1710
1991
 
1711
1992
  //#endregion
1712
1993
  //#region src/internal/async/execution.ts
@@ -1741,11 +2022,12 @@ function ownerSignalOf(signal) {
1741
2022
  * enforce a native timeout no later than `context.deadlineAt`.
1742
2023
  */
1743
2024
  function runBoundedExecution(label, options, operation) {
1744
- if (options.deadlineAt === void 0) assertNonNegativeFinite(label, "timeoutMs", options.timeoutMs);
2025
+ if (options.deadlineAt === void 0) assertTimerDelay(label, "timeoutMs", options.timeoutMs);
1745
2026
  else assertNonNegativeFinite(label, "deadlineAt", options.deadlineAt);
1746
2027
  const startedAt = Date.now();
1747
2028
  const deadlineAt = options.deadlineAt ?? startedAt + options.timeoutMs;
1748
2029
  const timeoutMs = Math.max(0, deadlineAt - startedAt);
2030
+ if (options.deadlineAt !== void 0) assertTimerDelay(label, "the time until deadlineAt", timeoutMs);
1749
2031
  const timeoutError = () => new DOMException(`${label} timed out after ${timeoutMs}ms`, "TimeoutError");
1750
2032
  const controller = new AbortController();
1751
2033
  const context = Object.freeze({
@@ -1811,5 +2093,211 @@ function isDispatchTrackingOutbox(outbox) {
1811
2093
  }
1812
2094
 
1813
2095
  //#endregion
1814
- export { voWithValidation as A, hasCooperativeBrand as C, vo as D, deepFreeze as E, findPropertyDescriptor as F, hasIntrinsicPrototypeChain as I, isBuiltInObject as L, deepOmit as M, deepEqual as N, voEquals as O, builtInTagWithoutInvokingAccessors as P, isIntrinsicConstructorPrototype as R, SnapshotTimeValidationError as S, ValueObject as T, isRecordedDomainEvent as _, assertNonNegativeFinite as a, recordDomainEvent as b, abortReason as c, copyMetadata as d, createDomainEvent as f, defaultDomainEventFactory as g, createUncommittedDomainEvent as h, runBoundedExecution as i, deepEqualExcept as j, voEqualsExcept as k, adoptRecordedDomainEvent as l, createDomainEventFromFacts as m, DEFAULT_EXECUTION_TIMEOUT_MS as n, assertPositiveInteger as o, createDomainEventFactory as p, ownerSignalOf as r, assertPositiveSafeInteger as s, isDispatchTrackingOutbox as t, adoptUncommittedDomainEvent as u, isUncommittedDomainEvent as v, stampCooperativeBrand as w, DomainEventValidationError as x, mergeMetadata as y, isWeakMap as z };
1815
- //# sourceMappingURL=ports.js.map
2096
+ //#region src/persistence/event-store/event-stream-page-guards.ts
2097
+ function assertValidHead(stream, lastVersion, fromVersion, targetVersion) {
2098
+ if (Number.isSafeInteger(lastVersion) && lastVersion >= 1) return;
2099
+ throw new InvalidEventStreamPageError({
2100
+ identity: stream,
2101
+ reason: "invalid_head",
2102
+ fromVersion,
2103
+ targetVersion,
2104
+ lastVersion
2105
+ });
2106
+ }
2107
+ function assertHeadNotBehindFirstPage(stream, lastVersion, firstPageLastVersion, fromVersion, targetVersion) {
2108
+ if (lastVersion >= firstPageLastVersion) return;
2109
+ throw new InvalidEventStreamPageError({
2110
+ identity: stream,
2111
+ reason: "head_regressed",
2112
+ fromVersion,
2113
+ targetVersion,
2114
+ lastVersion,
2115
+ firstPageLastVersion
2116
+ });
2117
+ }
2118
+ function assertPageNotEmpty(stream, eventCount, fromVersion, targetVersion) {
2119
+ if (eventCount > 0) return;
2120
+ throw new InvalidEventStreamPageError({
2121
+ identity: stream,
2122
+ reason: "empty_page",
2123
+ fromVersion,
2124
+ targetVersion
2125
+ });
2126
+ }
2127
+ function assertPageWithinWindow(stream, eventCount, fromVersion, targetVersion) {
2128
+ if (eventCount <= targetVersion - fromVersion) return;
2129
+ throw new InvalidEventStreamPageError({
2130
+ identity: stream,
2131
+ reason: "page_past_target",
2132
+ fromVersion,
2133
+ targetVersion,
2134
+ eventCount
2135
+ });
2136
+ }
2137
+ function assertPageWithinLimit(stream, eventCount, limit, fromVersion, targetVersion) {
2138
+ if (eventCount <= limit) return;
2139
+ throw new InvalidEventStreamPageError({
2140
+ identity: stream,
2141
+ reason: "page_over_limit",
2142
+ fromVersion,
2143
+ targetVersion,
2144
+ eventCount,
2145
+ limit
2146
+ });
2147
+ }
2148
+
2149
+ //#endregion
2150
+ //#region src/persistence/event-store/read-stream-pages.ts
2151
+ /**
2152
+ * Pins the target version of a replay window on an existing stream, or
2153
+ * reports the window as unreachable.
2154
+ *
2155
+ * The target version is `toVersion` when given, else `lastVersion`. The
2156
+ * window is unreachable when `fromVersion` lies beyond the target version,
2157
+ * or the target version lies beyond `lastVersion`. The call never clamps
2158
+ * such a window to the head, so a request for version 10 of a stream that
2159
+ * ends at 7 never loads the latest state by accident.
2160
+ *
2161
+ * This is the decision `readStreamPages` makes on its first page. An
2162
+ * adapter that pages on its own makes it with this call, then hands its
2163
+ * pages to `reconstituteAggregateFromStreamPages`. Values that are not safe
2164
+ * integers, a negative `fromVersion`, and a `toVersion` or `lastVersion`
2165
+ * below 1 reject with `RangeError`: an existing stream holds at least one
2166
+ * event, and no replay can end at version 0.
2167
+ */
2168
+ function pinTargetVersion(options) {
2169
+ assertNonNegativeSafeInteger("pinTargetVersion", "fromVersion", options.fromVersion);
2170
+ if (options.toVersion !== void 0) assertPositiveSafeInteger("pinTargetVersion", "toVersion", options.toVersion);
2171
+ assertPositiveSafeInteger("pinTargetVersion", "lastVersion", options.lastVersion);
2172
+ return decideTargetVersion(options);
2173
+ }
2174
+ function decideTargetVersion(window) {
2175
+ const targetVersion = window.toVersion ?? window.lastVersion;
2176
+ if (window.fromVersion > targetVersion || targetVersion > window.lastVersion) return { reachable: false };
2177
+ return {
2178
+ reachable: true,
2179
+ targetVersion
2180
+ };
2181
+ }
2182
+ /**
2183
+ * Reads one stream in bounded pages up to a pinned target version.
2184
+ *
2185
+ * The call reads the first page. That page decides existence and reports
2186
+ * the head. The call then makes the window decision of
2187
+ * {@link pinTargetVersion}: the target version is `toVersion` when given,
2188
+ * else the head, and a window outside the stream is unreachable.
2189
+ *
2190
+ * The first iteration of `pages` yields the first page that the call read
2191
+ * and then reads the continuation pages, one page at a time. Every later
2192
+ * iteration reads all pages from the store again, so no two iterations
2193
+ * share an event object. Each of these reads passes the target version as
2194
+ * `toVersion` and starts after the events that the earlier pages
2195
+ * returned. Streams are append-only, so an iteration yields one stable
2196
+ * prefix even when another writer appends during the replay.
2197
+ *
2198
+ * The call checks every page against the `readStream` contract and throws
2199
+ * {@link InvalidEventStreamPageError} for a page that breaks it: a head
2200
+ * that is not a safe integer of at least 1, an empty page while events
2201
+ * remain in the window, a page with more events than its window has left
2202
+ * or than `limit`, and a continuation page that reports the stream absent
2203
+ * or a head below the head of the first page.
2204
+ *
2205
+ * Invalid options reject with `RangeError` before any page is read:
2206
+ * `limit` and `toVersion` must be positive safe integers, `fromVersion` a
2207
+ * non-negative one. The call passes `signal` to every page read, and it
2208
+ * checks the signal before each page read as well. An aborted signal
2209
+ * rejects with its `reason`.
2210
+ */
2211
+ async function readStreamPages(reader, stream, options) {
2212
+ assertPositiveSafeInteger("readStreamPages", "limit", options.limit);
2213
+ const fromVersion = options.fromVersion ?? 0;
2214
+ assertNonNegativeSafeInteger("readStreamPages", "fromVersion", fromVersion);
2215
+ if (options.toVersion !== void 0) assertPositiveSafeInteger("readStreamPages", "toVersion", options.toVersion);
2216
+ throwIfAborted(options.signal);
2217
+ const identity = {
2218
+ aggregateType: stream.aggregateType,
2219
+ aggregateId: stream.aggregateId
2220
+ };
2221
+ const first = await reader.readStream(identity, {
2222
+ fromVersion,
2223
+ limit: options.limit,
2224
+ ...options.toVersion === void 0 ? {} : { toVersion: options.toVersion },
2225
+ ...options.signal === void 0 ? {} : { signal: options.signal }
2226
+ });
2227
+ if (!first.exists) return {
2228
+ exists: false,
2229
+ reachable: false
2230
+ };
2231
+ assertValidHead(identity, first.lastVersion, fromVersion);
2232
+ const pinned = decideTargetVersion({
2233
+ fromVersion,
2234
+ toVersion: options.toVersion,
2235
+ lastVersion: first.lastVersion
2236
+ });
2237
+ if (!pinned.reachable) return {
2238
+ exists: true,
2239
+ reachable: false,
2240
+ fromVersion,
2241
+ lastVersion: first.lastVersion
2242
+ };
2243
+ if (fromVersion < pinned.targetVersion) assertPageNotEmpty(identity, first.events.length, fromVersion, pinned.targetVersion);
2244
+ assertPageWithinWindow(identity, first.events.length, fromVersion, pinned.targetVersion);
2245
+ assertPageWithinLimit(identity, first.events.length, options.limit, fromVersion, pinned.targetVersion);
2246
+ const window = {
2247
+ stream: identity,
2248
+ fromVersion,
2249
+ firstPageLastVersion: first.lastVersion,
2250
+ targetVersion: pinned.targetVersion,
2251
+ limit: options.limit,
2252
+ signal: options.signal
2253
+ };
2254
+ let unreadFirstPage = first.events;
2255
+ return {
2256
+ exists: true,
2257
+ reachable: true,
2258
+ stream: identity,
2259
+ fromVersion,
2260
+ targetVersion: pinned.targetVersion,
2261
+ pages: { [Symbol.asyncIterator]: () => {
2262
+ const firstPage = unreadFirstPage;
2263
+ unreadFirstPage = void 0;
2264
+ return readPinnedPages(reader, window, firstPage);
2265
+ } }
2266
+ };
2267
+ }
2268
+ async function* readPinnedPages(reader, window, firstPage) {
2269
+ let cursor = window.fromVersion;
2270
+ if (firstPage !== void 0) {
2271
+ if (firstPage.length > 0) yield firstPage;
2272
+ cursor += firstPage.length;
2273
+ }
2274
+ while (cursor < window.targetVersion) {
2275
+ throwIfAborted(window.signal);
2276
+ const page = await reader.readStream(window.stream, {
2277
+ fromVersion: cursor,
2278
+ toVersion: window.targetVersion,
2279
+ limit: window.limit,
2280
+ ...window.signal === void 0 ? {} : { signal: window.signal }
2281
+ });
2282
+ if (!page.exists) throw new InvalidEventStreamPageError({
2283
+ identity: window.stream,
2284
+ reason: "stream_vanished",
2285
+ fromVersion: cursor,
2286
+ targetVersion: window.targetVersion
2287
+ });
2288
+ assertValidHead(window.stream, page.lastVersion, cursor, window.targetVersion);
2289
+ assertHeadNotBehindFirstPage(window.stream, page.lastVersion, window.firstPageLastVersion, cursor, window.targetVersion);
2290
+ assertPageNotEmpty(window.stream, page.events.length, cursor, window.targetVersion);
2291
+ assertPageWithinWindow(window.stream, page.events.length, cursor, window.targetVersion);
2292
+ assertPageWithinLimit(window.stream, page.events.length, window.limit, cursor, window.targetVersion);
2293
+ yield page.events;
2294
+ cursor += page.events.length;
2295
+ }
2296
+ }
2297
+ function throwIfAborted(signal) {
2298
+ if (signal?.aborted) throw abortReason(signal, "readStreamPages aborted");
2299
+ }
2300
+
2301
+ //#endregion
2302
+ export { SnapshotTimeValidationError as A, stampCooperativeBrand as B, defaultDomainEventFactory as C, mintUncommittedDomainEvent as D, mergeMetadata as E, vo as F, isBuiltInObject as G, deepOmit as H, voEquals as I, isIntrinsicConstructorPrototype as K, voEqualsExcept as L, detachState as M, ValueObject as N, recordDomainEvent as O, deepFreeze as P, voWithValidation as R, createUncommittedDomainEvent as S, isUncommittedDomainEvent as T, deepEqual as U, deepEqualExcept as V, findPropertyDescriptor as W, adoptUncommittedDomainEvent as _, isDispatchTrackingOutbox as a, createDomainEventFactory as b, runBoundedExecution as c, assertPositiveInteger as d, assertPositiveSafeInteger as f, adoptRecordedDomainEvent as g, waitRejectingOnAbort as h, assertPageWithinWindow as i, detachFreezableState as j, DomainEventValidationError as k, MAX_TIMER_DELAY_MS as l, abortReason as m, readStreamPages as n, DEFAULT_EXECUTION_TIMEOUT_MS as o, assertTimerDelay as p, isWeakMap as q, assertPageNotEmpty as r, ownerSignalOf as s, pinTargetVersion as t, assertNonNegativeSafeInteger as u, copyMetadata as v, isRecordedDomainEvent as w, createDomainEventFromFacts as x, createDomainEvent as y, hasCooperativeBrand as z };
2303
+ //# sourceMappingURL=read-stream-pages.js.map