@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.
- package/dist/chunks/kit-errors.d.ts +287 -108
- package/dist/chunks/kit-errors.js +250 -111
- package/dist/chunks/kit-errors.js.map +1 -1
- package/dist/chunks/{ports.js → read-stream-pages.js} +713 -225
- package/dist/chunks/read-stream-pages.js.map +1 -0
- package/dist/chunks/snapshot-store.d.ts +597 -115
- package/dist/index.d.ts +608 -437
- package/dist/index.js +1508 -637
- package/dist/index.js.map +1 -1
- package/dist/money.d.ts +1 -1
- package/dist/testing.d.ts +106 -15
- package/dist/testing.js +546 -52
- package/dist/testing.js.map +1 -1
- package/package.json +3 -2
- package/dist/chunks/ports.js.map +0 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
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
|
|
118
|
-
if (
|
|
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
|
|
123
|
-
return candidateName === intrinsicName &&
|
|
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/
|
|
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`.
|
|
653
|
-
*
|
|
654
|
-
*
|
|
655
|
-
*
|
|
656
|
-
*
|
|
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
|
|
660
|
-
"
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
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
|
|
683
|
-
if (
|
|
684
|
-
|
|
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
|
-
|
|
748
|
+
stampCooperativeBrand(shadow, mutatorShadowBrand);
|
|
749
|
+
Object.freeze(shadow);
|
|
750
|
+
shadows.set(key, shadow);
|
|
688
751
|
}
|
|
689
|
-
return
|
|
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
|
-
/**
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
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
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
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 (
|
|
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/
|
|
1275
|
+
//#region src/internal/structural/detach-state.ts
|
|
1205
1276
|
/**
|
|
1206
|
-
*
|
|
1207
|
-
* a
|
|
1208
|
-
*
|
|
1209
|
-
*
|
|
1210
|
-
*
|
|
1211
|
-
*
|
|
1212
|
-
*
|
|
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
|
|
1215
|
-
*
|
|
1216
|
-
*
|
|
1217
|
-
*
|
|
1218
|
-
*
|
|
1219
|
-
*
|
|
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
|
-
*
|
|
1222
|
-
*
|
|
1223
|
-
*
|
|
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
|
-
*
|
|
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
|
|
1228
|
-
|
|
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
|
-
*
|
|
1237
|
-
*
|
|
1238
|
-
*
|
|
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
|
|
1241
|
-
|
|
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
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
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,
|
|
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:
|
|
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 =
|
|
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
|
|
1462
|
-
* schema version. The stamp supplies only event
|
|
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 =
|
|
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
|
|
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:
|
|
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", "
|
|
1552
|
-
if (options?.aggregateType !== void 0) assertNonBlankEventField(options.aggregateType, "aggregateType", "
|
|
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
|
-
*
|
|
1566
|
-
*
|
|
1567
|
-
*
|
|
1568
|
-
*
|
|
1569
|
-
*
|
|
1570
|
-
*
|
|
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
|
|
1576
|
-
if (
|
|
1577
|
-
|
|
1578
|
-
if (
|
|
1579
|
-
|
|
1580
|
-
|
|
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
|
-
|
|
1588
|
-
|
|
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
|
-
*
|
|
1592
|
-
*
|
|
1593
|
-
*
|
|
1594
|
-
*
|
|
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
|
|
1598
|
-
if (value
|
|
1599
|
-
|
|
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)
|
|
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
|
-
|
|
1815
|
-
|
|
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
|